今天終於搞定將 txProxyTemplate 移植到 aop:config。原本常見的 transaction 設定是用 txProxyTemplate:
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager" /> </property> <property name="transactionAttributes"> <props> <prop key="save*">PROPAGATION_REQUIRED, -org.bioinfo.util.BusinessException</prop> <prop key="update*">PROPAGATION_REQUIRED, -org.bioinfo.util.BusinessException</prop> <prop key="delete*">PROPAGATION_REQUIRED, -org.bioinfo.util.BusinessException</prop> <prop key="remove*">PROPAGATION_REQUIRED, -org.bioinfo.util.BusinessException</prop> <prop key="tx*">PROPAGATION_REQUIRED, -org.bioinfo.util.BusinessException</prop> <prop key="*">PROPAGATION_REQUIRED,readOnly, -org.bioinfo.util.BusinessException</prop> </props> </property> <property name="preInterceptors"> <list> <ref bean="operationLogInterceptor"/> </list> </property> </bean> <bean id="operationLogInterceptor" scope="singleton" class="org.bioinfo.util.OperationLogInterceptor"> </bean>
上面除了一般 transaction 設定外,我還加了一專門做 log 的 interceptor。而一般 service 只要直接套用就好了:
<bean id="orderService" parent="txProxyTemplate"> <property name="target"> <bean class="bendon.order.impl.OrderServiceImpl"> <property name="shareOrderDAO" ref="shareOrderDAO" /> </bean> </property> </bean>
ok, 開始 migrate,第一個步驟是將 aspectjweaver.jar 放進 classpath 裡 (在 spring-framework/lib/aspectj 裡) 有了這個才能在 spring 啟動時 weave aspect。建一個 applicationContext-aop.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd default-lazy-init="true"> <aop:aspectj-autoproxy /> <aop:config> <aop:pointcut id="txServiceFacades" expression="execution( public * bendon..*ServiceImpl.*(..) )" /> <aop:aspect id="operationLogAspect" ref="operationLogAdvice"> <aop:around method="log" pointcut-ref="txServiceFacades"/> </aop:aspect> </aop:config> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut-ref="txServiceFacades"/> </aop:config> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="save*" rollback-for="org.bioinfo.util.BusinessException" /> <tx:method name="update*" rollback-for="org.bioinfo.util.BusinessException" /> <tx:method name="delete*" rollback-for="org.bioinfo.util.BusinessException" /> <tx:method name="remove*" rollback-for="org.bioinfo.util.BusinessException" /> <tx:method name="tx*" rollback-for="org.bioinfo.util.BusinessException" /> <tx:method name="*" rollback-for="org.bioinfo.util.BusinessException" read-only="true" /> </tx:attributes> </tx:advice> <bean id="operationLogAdvice" class="org.bioinfo.util.log.OperationLogAdvice" scope="singleton"> </bean> </beans>
最上面的 <aop:aspectj-autoproxy> 會替需要 weave aspect 的 bean 自動加 proxy。接下來定義 point cut: "txServiceFacades" 顧名思意,這個 point cut 包含所有的 需要 transactoin 的 service facade。這裡我的 point cut 語法是:
execution( public * bendon..*ServiceImpl.*(..) )
這表示在 bendon 以下的所有 package,其 class 名稱以 ServiceImpl 為結尾的class 中,所有的 public method (無論參數和回傳為何)。這是我專案中唯一找的出來的 pointcut 了。如果一開始就用 package 隔開就會穩當的多,例如像這樣 execution( public * bendon.service..*.*(..)) 表示 bendon.service 以下的所有 class 的 public method。
接下來看第二個 aop:config ,裡面有一個 advisor -- 設定成當遇到 txServiceFacades pointcut 時,便會執行 "txAdvice" 所定義的指令。txAdvice 應該就很簡單了,跟 txProxyTemplate 裡的 transactionAttributes 長的很像,用意也是一樣的。整個 advisor 我們可以簡短解釋成-- 當程式通過 *ServiceImpl 的 public method 時,會依 method 名稱啟動相對應的 transaction 設定。
回到第一個 aop:config,下方有另一個 aspect: "operationLogAspect",它的目的是記錄 method 的 invoke 和 return。為此,我們設計了一個"operationLogAdvice" 的 bean,內部實做以 log4j 記錄,並將它設為 aspect 的 advice。而 pointcut 我們還是套用 txServiceFacades pointcut,因為有 transaction 的操作是我們所關心的,要多加 log。最後設定當遇到 pointcut 時,執行 operationLogAdvice 的 "log" method。
在這個設定檔裡,operationLogAdvice 在 txAdvice 之前,因此在同一個 pointcut 上 operationLogAdvice 會先執行,然後才執行 txAdvice。OK,經過 aspectj 的加持,我們現在 txProxyTemplate 可以簡化成:
<bean id="orderService" class="bendon.order.impl.OrderServiceImpl"> <property name="shareOrderDAO" ref="shareOrderDAO" /> </bean>
呃... 少了幾個字,變得比較乾淨一點... 好像... 沒什麼了不起... 至於速度上:
txProxyTemplate | aop:config | |
---|---|---|
總 bean 數 | 39 | 44 |
Proxied 的 bean 數 | 8 | 8 |
loading 費時: | ~9 sec | ~11 sec |
哇咧,loading 出全部的 bean 還變慢... hhmmm... 這到底值不值得?不過總算是又減少了一些 xml 的設定,算是不壞吧。不過我最想要的是 service facade 和 DAO 的 xml 變成:
是的~~ 一片空白,zero xml!這應該是最高境界吧~~ 要達成這個邪惡的目標就得自己寫 annotation 和客製 BeanFactory 了。有空再來試試吧~
最後附上 operationLogAdvice 給大家參考:
/** * @author ingram * */ public class OperationLogAdvice { private final static Logger logger = Logger .getLogger(OperationLogAdvice.class); // do not declare this as static (out of memory error due to classloader) private final ThreadLocal<RuntimeException> loggedRuntimeException = new ThreadLocal<RuntimeException>(); private int maxArgumentLength = Integer.MAX_VALUE; public Object log(ProceedingJoinPoint pjp) throws Throwable { if (pjp == null) { if (logger.isDebugEnabled()) { logger.debug("[invoke] null"); } return null; } Logger targetLogger = Logger.getLogger(pjp.getTarget().getClass()); if (targetLogger.isDebugEnabled()) { targetLogger.debug("[invoke] " + pjp.getSignature().getName() + " " + buildArgumentsString(pjp.getArgs())); } try { Object o = pjp.proceed(); if (targetLogger.isDebugEnabled()) { targetLogger.debug("[ done ] " + pjp.getSignature().getName() + " return: " + buildReturnString(o)); } return o; } catch (RuntimeException e) { if (loggedRuntimeException.get() == null) { targetLogger.error(e.getMessage(), e); loggedRuntimeException.set(e); } throw e; } } private String buildReturnString(Object o) { if (o == null) { return null; } String all = o.toString(); if (all.length() > maxArgumentLength) { return all.substring(0, maxArgumentLength) + "...trimed"; } return all; } private String buildArgumentsString(Object[] args) { String all = Arrays.toString(args); if (all.length() > maxArgumentLength) { return all.substring(0, maxArgumentLength) + "...trimed]"; } return all; } public void setMaxArgumentLength(int length) { this.maxArgumentLength = length; } }
使用方法是在 log4j.xml 裡加上
<category name="foo.bar"><priority value="DEBUG" /></category>
一行即可。foo.bar 則是要 log 的 package 名稱
[Update] <aop:aspectj-autoproxy /> 這個 tag 只有在 使用 @AspectJ 時才會用到,如果沒用可以拿掉