锁定老帖子 主题:让Spring架构减化事务配置
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-04-19
注:原创文章,本文曾发表于it168 Spring颠覆了以前的编程模式,引入了IOC等全新的概念,广受大家的喜爱。目前大多数j2ee项目都已经采用Spring框架。Spring最大的问题是太多的配置文件,使得你不仅需要维护程序代码,还需要额外去维护相关的配置文件。最典型的就是事务配置(注:这里的“事务配置”都指“声明式事务配置”),在Spring中进行事务配置除了定义对象自身的bean外,还需要定义一个进行事务代理的bean.如果你有n个类需要引入事务,那么你就必须定义2n个bean。维护这些bean的代价是十分昂贵的,所以必须要对事务配置进行减化。如果你是基于Spring进行架构设计,那么作为一个好的架构设计师,应该把一些公共的方面进行简化,让项目的开发人员只关心项目的业务逻辑,而不要花费太多的精力去关心业务逻辑之外的太多东西。所以作为一个好的架构就应该把事务管理进行简化,让程序员花在编程之外的工作最小化。 1. Spring声明式事务配置的几种方法 在Spring中进行事务控制首先要选择适当的事务管理器,其次为程序选择划分事务的策略。如果只有单个事务性资源,可以从“单一资源”的PlatformTransactionManger实现当中选择一个,这些实现有:DataSourceTransactionManager,HibernateTransactionManager, JdoTransactionManager,PersistenceBrokerTransactionManager和JmsTransactionManager。根据你所采用的数据库持久化技术选择。如果你的项目运行于支持JTA的服务器,那么将选择JtaTransactionManger,将会支持多资源事务。 下表将为你选择适当的事务管理器提供参考。 技术 事务管理器 内建的事务支持 JDBC DataSurceTransactionManagerJtaTransactionManager JdbcTemplate和org.springframework.jdbc.object包中的所有类 IBATIS DataSourceTransactionManagerJtaTransactionManager SqlMapClientTemplate和SqlClientTemplate Hibernate HibernateTransactionManagerJtaTransactionManager HibernateTemplate和HibernateInterceptor JDO JdoTransactionManagerJtaTransactionManager JdoTemplate和JdoInterceptor ApacheOJB PersistenceBrokerTransactionManagerJtaTransactionManager PersistenceBrokerTemplate JMS JmsTransactionManager JmsTemplate 在划分事务时,我们需要进行事务定义,也就是配置事务的属性。事务的属性有传播行业,隔离级别,超时值及只读标志。TransactionAttribute接口指定哪些异常将导致一个回滚,哪些应该一次性提交。 (1) 使用ProxyFactoryBean 和TransactionInterceptor <!--定义本地数据源--> <bean id="dataSource" name="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- !定义单个jdbc数据源的事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!—定义拦截器--> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager"> <ref bean="transactionManager"/> </property> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> <!—定义业务对象--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService.target" class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl"> <property name="userInfoDAO" ref="com.prs.application.ehld.sample.integration.dao.userInfoDAO"> </property> </bean> <!—定义业务对象的事务代理对象--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService" class="org.springframeword.aop.framework.ProxyFacgtoryBean"> <property name="target" ref="com.prs.application.ehld.sample.biz.service.sampleService.target"> </property> <property name="interceptorNames"> <value>transactionInterceptor</value> </property> </bean> 通过ProxyFacgtoryBean和TransactionInterceptor组合使用,可以对事务进行更多的控制。所有需要事务控制的对象可以共享一个transactionInterceptor的事务属性。 (2) 使用TransactionProxyFactoryBean <!—定义业务对象--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService.target" class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl"> <property name="userInfoDAO" ref="com.prs.application.ehld.sample.integration.dao.userInfoDAO"> </property> </bean> <!—定义业务对象的事务代理对象--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <property name="transactionManager"> <ref bean="transactionManager"/> </property> <property name="target" ref="com.prs.application.ehld.sample.biz.service.sampleService.target" /> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> 使用TransactionProxyFactoryBean需要为每一个代理对象都去定义自己的事务属性。 (3) 使用TransactionProxyFactoryBean及abstract属性来简化配置 这种方工也是目前使用得最多的一种声明式事务配置方法 <!--事务控制代理抽象定义 --> <bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <property name="transactionManager"> <ref bean="transactionManager"/> </property> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> <!—定义业务对象--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService.target" class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl"> <property name="userInfoDAO" ref="com.prs.application.ehld.sample.integration.dao.userInfoDAO"> </property> </bean> <!—定义业务对象的事务代理对象--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService" parent="baseTransactionProxy"> <property name="target" ref="com.prs.application.ehld.sample.biz.service.sampleService.target"> </property> </bean> 使用abstract属性,可以让代理对象可以共享一个定义好的事务属性,使配置简化。 (4)使用BeanNameAutoProxyCreator <!—定义拦截器--> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager"> <ref bean="transactionManager"/> </property> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> <!—定义bean别名自动代理创建器--> <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <value>transactionInterceptor</value> </property> <property name="beanNames"> <list> <idref local="com.prs.application.ehld.sample.biz.service.sampleService"/> </list> </property> </bean> <!—定义业务对象--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService" class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl"> <property name="userInfoDAO" ref="com.prs.application.ehld.sample.integration.dao.userInfoDAO"> </property> </bean> 使用BeanNameAutoProxyCreator可以由框架来提供适当的代理,由一个transactionInterceptor统一定义事务属性,只需要把需要事务控制的bean加到beannames的列表。 对于需要大量声明式事务的bean,BeanNameAutoProxyCreator是十分方便的。减少了代理bean的定义,还可以灵活的决定一个bean是否进行事务控制。 上面四种方法是在Spring中常见的声明式事务配置方法,其中使用TransactionProxyFactoryBean及abstract属性进行配置是最常见的简化方法。 我们暂且把需要进行事务控制的bean叫事务bean.把依赖和调用它的bean叫做业务bean。对事务bean进行代理叫做事务代理bean. 1. 使用ProxyFactoryBean 和TransactionInterceptor,可以由一个TransactionInterceptor统一定义事务属性,对于每一个事务bean都需要再定义一个事务代理bean。如果有n个事务bean,那么就需要定义和维护2n个bean。并且注入到业务bean的不是事务bean本身,而是要求用事务代理bean注入。这增加了理解的难度。 2. 使用TransactionProxyFactoryBean需要为每一个事务代理bean都定义自己的事务属性,除了需要维护2n个bean外,还需要为每一个事务代理bean定义事务属性。可以说是最麻烦的。同样需要把事务代理bean注入到业务bean,增加了理解的难度和项目的复杂度。 3. 使用TransactionProxyFactoryBean及abstract属性是对使用TransactionProxyFactoryBean的一种简单化配置,可以让所有的事务bean共享一致的事务属性定义。需要维护2n个bean,需要把事务代理bean注入到业务bean。 4. 使用BeanNameAutoProxyCreator最适合在框架中使用,只需要维护n个bean。也无需要事务代理bean。直接把事务bean注入业务bean中。但是它必须把需要事务控制的bean加到beanNames列表中。 2.类型自动代理创建器BeanClassTypeAutoProxyCreator 得于BeanNameAutoProxyCreator的启示,BeanNameAutoProxyCreator可以实现框架来实现自动代理。它只是把需要代理的bean加入beanNames属性列表。大大的简化了代理的配置,减少了代理bean的定义,使用事务bean注入业务对象,而不是代理bean注入,更合乎事务逻辑。BeanNameAutoProxyCreator仍然需要开发人员除了定义业务bean外,还需要关心事务的定义,当然已经简单了很多。如果能实现一个BeanClassTypeAutoProxyCreator,为它指定一个可以代理的ClassType列表,那么在context中所有属于ClassType和其子类的bean都自动获得代理。 实现思路: 1.BeanNameAutoProxyCreator继承了AbstractAutoProxyCreator,去实现方法: protected abstract Object[] getAdvicesAndAdvisorsForBean( Class beanClass, String beanName, TargetSource customTargetSource) 在BeanNameAutoProxyCreator中的实现是判断beanName 是存在于beanNames列表,如果能找到则Object[]不对空。否则返回null。 所以BeanClassTypeAutoProxyCreator也应该继承AbstractAutoProxyCreator。 getAdvicesAndAdvisorsForBean方法的实现可以参照BeanNameAutoProxyCreator方法的实现 2.BeanClassTypeAutoProxyCreator需要有一个进行代理的ClassType列表,在bean进行初始化后就在context中查找类型为ClassType列表中类型的所有beanName.从而获得一个beanNames列表。 获得beanNames列表后就可以像BeanNameAutoProxyCreator一样实现自动代理了。 3.要想获得当前context,我们可以实现ApplicationContextAware接口。让BeanClassTypeAutoProxyCreator的bean可以获得当前context. 4. 要在bean进行初始化动作,可以实现InitializingBean接口,实现afterPropertiesSet,在这个方法中在context中根据classType查找获得相关的beanName的列表。 5. 写一个空接口,里面没有任何方法。需要事务代理的类实现这个空接口。 这样,只需要把这个空接口的全类名作为BeanClassTypeAutoProxyCreator的classTypes参数值,然后所有需要代理的类都去实现这个接口就可以自动获得代理了。无再需要任何配置。这样就可以让程序员专心于业务逻辑的开发,而无需要去关心事务控制方法,就像是没有使用事务一样。 完整的实现类如下: BeanClassTypeAutoProxyCreator.java /** * 根据类型自动代理Creator * * @author yuanguangdong date: Jul 13, 2004 */ public class BeanClassTypeAutoProxyCreator extends AbstractAutoProxyCreator implements ApplicationContextAware, InitializingBean { /** Logger that is available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); /** ApplicationContext this object runs in */ private ApplicationContext applicationContext; /** MessageSourceAccessor for easy message access */ private MessageSourceAccessor messageSourceAccessor; /**被代理的bean别名列表**/ private List beanNames; /**被代理的classType列表**/ private List classTypes; //--------------------------------------------------------- //实现AbstractAutoProxyCreator的抽像方法 //--------------------------------------------------------- /** * @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean(java.lang.Class, * java.lang.String, org.springframework.aop.TargetSource) */ protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) throws BeansException { if (this.beanNames != null) { if (this.beanNames.contains(beanName)) { return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS; } } return DO_NOT_PROXY; } //------------------------------------------------------- //实现ApplicationContextAware接口方法 //------------------------------------------------------- /** * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ public void setApplicationContext(ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); initApplicationContext(); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } } /** * Determine whether this application object needs to run in an * ApplicationContext. * <p> * Default is "false". Can be overridden to enforce running in a context * (i.e. to throw IllegalStateException on accessors if outside a context). * * @see #getApplicationContext * @see #getMessageSourceAccessor */ protected boolean isContextRequired() { return true; } /** * Determine the context class that any context passed to * <code>setApplicationContext</code> must be an instance of. Can be * overridden in subclasses. * * @see #setApplicationContext */ protected Class requiredContextClass() { return ApplicationContext.class; } /** * Return the ApplicationContext instance used by this object. */ public final ApplicationContext getApplicationContext() throws IllegalStateException { if (this.applicationContext == null && isContextRequired()) { throw new IllegalStateException( "ApplicationObjectSupport instance [" + this + "] does not run in an ApplicationContext"); } return applicationContext; } /** * Return a MessageSourceAccessor for the application context used by this * object, for easy message access. * * @throws IllegalStateException * if not running in an ApplicationContext */ protected final MessageSourceAccessor getMessageSourceAccessor() throws IllegalStateException { if (this.messageSourceAccessor == null && isContextRequired()) { throw new IllegalStateException( "ApplicationObjectSupport instance [" + this + "] does not run in an ApplicationContext"); } return this.messageSourceAccessor; } public void setClassTypes(String[] classTypes) { this.classTypes = Arrays.asList(classTypes); } /** * Subclasses can override this for custom initialization behavior. Gets * called by <code>setApplicationContext</code> after setting the context * instance. * <p> * Note: Does </i>not</i> get called on reinitialization of the context but * rather just on first initialization of this object's context reference. * * @throws ApplicationContextException * in case of initialization errors * @throws BeansException * if thrown by ApplicationContext methods * @see #setApplicationContext */ protected void initApplicationContext() throws BeansException { } //----------------------------------- //实现InitializingBean接口方法 //----------------------------------- /** * 查找指定classType的beanName列表 */ private List getBeanNames(String classType) { List beanNameList = null; try { String[] beanName = this.getApplicationContext() .getBeanNamesForType(Class.forName(classType), true, false); if (beanName != null) { beanNameList = Arrays.asList(beanName); } } catch (ClassNotFoundException ex) { throw new IllegalArgumentException("Class not found: " + ex.getMessage()); } return beanNameList; } /** * * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() throws Exception { if (classTypes != null && !classTypes.isEmpty()) { beanNames = new ArrayList(); for (int i = 0; i < classTypes.size(); i++) { String classType = (String) classTypes.get(i); List aList = getBeanNames(classType); beanNames.addAll(aList); } } if (logger.isDebugEnabled()) { for (int i = 0; i < beanNames.size(); i++) { logger.debug("printBean:" + (String) beanNames.get(i)); } } } } 3.使用BeanClassTypeAutoProxyCreator 3.1为了使用BeanClassTypeAutoProxyCreator,将为所有需要进行代理的类定一个接口。 package com.prs.application.ehld.biz.service; public interface BaseService { } 3.2 让需要代理的类实现或继承这个公共接口 package com.prs.application.ehld.sample.biz.service; public interface SampleService extends BaseService { public void setUserInfoDAO(UserInfoDAO userInfoDAO); public void insertUserInfo(UserInfoDTO userInfo) throws BusinessServiceException; } 3.3 配置事务代理 <!—定义拦截器--> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager"> <ref bean="transactionManager"/> </property> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> <!—定义类型自动代理创建器--> <bean id="autoClassTypeProxyCreator" class="com.prs.application.ehld.common.aotoproxy.BeanClassTypeAutoProxyCreator"> <property name="interceptorNames"> <value>transactionInterceptor</value> </property> <property name="classTypes"> <list> <value>com.prs.application.ehld.biz.service.BaseService</value> </list> </property> </bean> <!—定义事务bean--> <bean id="com.prs.application.ehld.sample.biz.service.sampleService" class="com.prs.application.ehld.sample.biz.service.impl.SampleServiceImpl"> <property name="userInfoDAO" ref="com.prs.application.ehld.sample.integration.dao.userInfoDAO"> </property> </bean> 效果:只需要定义BeanClassTypeAutoProxyCreator,把需要代理的类型BaseService作为classTypes的值。这样任何实现了BaseService接口的类都自动获得代理。使得程序员就像配置普通bean一样去配置一个需要事务代理的bean。使得程序员只需要去关心业务逻辑。而无需要去关注事务这些框架应该支持的事情。特别是当开发团队成员水平不一,或团队人员流动性大时,BeanClassTypeAutoProxyCreator就发挥了它的作用。一个好的架构设计应该对事务控制,异常处理,日志记录这些方面进行统一的规划和处理,才能保证系统的健壮性。 采用Spring框架进行项目开发,我们在获得它的IOC等好处,同时给我们增加了维护太多配置文件的负担。应该尽量减少bean的定义,更多采用嵌套bean定义。否则将加大项目后期的维护成本。作为一个架构设计者更是应该把通用性比较强的方面进行统一规划。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-04-19
你这个简化配置其实是针对spring1.x的版本,建议你看看spring2.x,可以进一步简化
|
|
返回顶楼 | |
发表时间:2007-04-19
spring2里对事务的配置相当简单,不需要修改普通bean的定义,
直接按照类名、方法名进行拦截。 比如: <aop:config> <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* study.*Service.*(..))"/> </aop:config> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="load*" read-only="true"/> <tx:method name="*" /> </tx:attributes> </tx:advice> 注意,不需要修改被管理了事务的bean的定义。 |
|
返回顶楼 | |
发表时间:2007-04-19
spring2确实简单,但是在web应用的配置上总是有一层需要自动感应(atuowiring =“true”),有点美中不足。
spring2里要是有命名空间就更好了,重名的问题就好解决了 当然现在也可以 包名+类名 |
|
返回顶楼 | |
发表时间:2007-04-19
的确这是在以spring1.0作为框架进行设计的。有点老了,呵呵
|
|
返回顶楼 | |
发表时间:2007-05-23
Lucas Lee 写道 spring2里对事务的配置相当简单,不需要修改普通bean的定义,
直接按照类名、方法名进行拦截。 比如: <aop:config> <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* study.*Service.*(..))"/> </aop:config> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="load*" read-only="true"/> <tx:method name="*" /> </tx:attributes> </tx:advice> 注意,不需要修改被管理了事务的bean的定义。 没用过spring2,有个问题请教一下 execution(* study.*Service.*(..)) 这里的匹配的是*Service类名还是bean名?感觉像是类名 如果是类名的话 是不是可以不用在spring配置文件中配置这些service bean了? |
|
返回顶楼 | |
发表时间:2007-05-23
klyuan 写道 的确这是在以spring1.0作为框架进行设计的。有点老了,呵呵
我以前也写过相关的文章: http://www.iteye.com/topic/41645 楼主写的文章有很精粹之处,欣赏你的辛苦写文,另外,代码<code>一下 |
|
返回顶楼 | |
发表时间:2007-05-25
楼主:
在使用BeanNameAutoProxyCreator时,如果Spring分成多个配置文件(parent.xml和child.xml),BeanNameAutoProxyCreator配置在parent.xml中,那么child.xml定义的bean则不能实现事物(不会产生经过代理后的对象),如何解决这一问题? |
|
返回顶楼 | |
发表时间:2007-05-26
只要在项目的命名规范能做好,用spring2的schema进行配置还是很方便的。这是expression的一个简单配置execution(* com..*Manager.save*(..))其中:“com..”表示以com为顶级package名的所有包;“*Manager”表示以Manager结尾的类;“save*(..)”表示以save开头,任意参数的方法。
|
|
返回顶楼 | |
发表时间:2007-05-26
xly_971223 写道 Lucas Lee 写道 spring2里对事务的配置相当简单,不需要修改普通bean的定义,
直接按照类名、方法名进行拦截。 比如: <aop:config> <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* study.*Service.*(..))"/> </aop:config> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="load*" read-only="true"/> <tx:method name="*" /> </tx:attributes> </tx:advice> 注意,不需要修改被管理了事务的bean的定义。 没用过spring2,有个问题请教一下 execution(* study.*Service.*(..)) 这里的匹配的是*Service类名还是bean名?感觉像是类名 如果是类名的话 是不是可以不用在spring配置文件中配置这些service bean了? 这里的*Service是表示以“Service”结尾的所有class。使用这种配置方式可以对项目中所有按此规范命名的class进行事务管理 |
|
返回顶楼 | |