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 DataSurceTransactionManager JtaTransactionManager JdbcTemplate和org.springframework.jdbc.object包中的所有類 IBATIS DataSourceTransactionManager JtaTransactionManager SqlMapClientTemplate和SqlClientTemplate Hibernate HibernateTransactionManager JtaTransactionManager HibernateTemplate和HibernateInterceptor JDO JdoTransactionManager JtaTransactionManager JdoTemplate和JdoInterceptor ApacheOJB PersistenceBrokerTransactionManager JtaTransactionManager 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列表中。
類型自動代理創建器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));
}
}
}
}
使用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定義。否則將加大項目後期的維護成本。作為一個架構設計者更是應該把通用性比較強的方面進行統一規劃。