本文通過一個“
Hello World”級別的橫切性功能介紹
Spring1.X中
AOP的使用,并結合
Spring的經典的聲明式事務管理給出
Spring AOP配置中的經典方案。在
Spring2出來以后,
Spring1.X的
AOP使用方式已經“不合時宜”了,因此如果你是在新項目中采用
Spring AOP,建議使用
Spring2中的
AOP使用方式。關于
Spring2.X中
AOP的使用,參考該文的姊妹文章
Spring2.X中AOP的使用。
一提到AOP的應用,人們就會本能地提起日志功能,它就像一門語言的“Hello World”一樣被人們無數次提起。也許有人會疑問除了“不實用”的日志功能,AOP還能做些什么?可能在很多時候我們并不需要自己實現一個AOP功能,尤其是在擁有了很多優秀的AOP應用框架來解決通用的橫切性問題的情況下(比如Spring的事務管理、比如Acegi的安全管理、比如WebWork的攔截功能)。但問題總是層出不窮的,總會有些問題可能需要我們自己AOP一下。
在月言月,進入到該文的主題(寫完上面的一段使我想起俞平伯,那老頭很多文章的前幾段總是些不知所以的文字,直到“ 在月言月”一出,方進入文章主題;在看完文章后回頭一瞧,和主題相關的文字竟不到文章的半數!)。這個Sample要實現的AOP功能是最簡單的日志功能,在調用每個Service方法之前輸出被調用方法的簡單信息。
我們知道,Aspect=Pointcut+Advice(如果您不知道的話,需要看一看Spring文檔了)。在Spring1.X中,不能以AspectJ的語法書寫復雜的切入點表達式(這是因為Spring1.X中的切入點是Java類)。Spring1.X可以使用正則表達式切入點以聲明的方式來簡化編程。這里假定要求對Service層中所有Service方法在調用之前輸出和方法相關的信息。切入點聲明如下:
<bean id="businessServicePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<value>*</value>
</property>
</bean>
應該說明的是,在不使用通知器自動代理的情況下,聲明的切入點的作用域實在小的可憐,它不過是方法級別的。我覺得這也是Spring1.X中很不友好的一點,通過ProxyFactoryBean代理的方式實現AOP,需要對每個應用了AOP特性的Bean都做代理,即便是在Bean繼承的情況下也不友好。
下面要創建一個超簡單的Before通知:
public class LogAdvice implements MethodBeforeAdvice{
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
//no content
}
}
關于通知和切面的配置如下:
<bean id="logAdvice" class="hibernatesample.service.util.LogAdvice"></bean>
<bean id="logAspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="businessServicePointcut"></property>
<property name="advice" ref="logAdvice"></property>
</bean>
在spring1.X中,Advisor是Aspect的同義詞,這是spring1.X獨有的。坦率的說,我覺得這個詞真不應該出現,它會給AOP初學者造成很大的困惑,尤其是對AspectJ不了解的情況下。在spring1.X中,我們并不需要自己編寫一個Advisor,只需要使用Spring提供的Advisor封裝切入點和通知就行了。如果你不使用通知器自動代理并且要通知作用的類的所有方法,Advisor甚至是不需要的。好了,上面就是我們自定義的一個日志切面,我們還要加一個切面--經典的對于Service必不可少的事務切面。配置文件如下:
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
你也看到了,transactionInterceptor是個攔截器,它只是對方法級別上做攔截.將transactionInterceptor和logAspect以ProxyFactoryBean方式作用到Service上,配置文件如下:
<bean id="accountServiceTarget" class="hibernatesample.service.impl.AccountServiceImpl">
<property name="accountDAO" ref="accountDAO"></property>
</bean>
<bean id="accountService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>hibernatesample.service.AccountService</value>
</property>
<property name="interceptorNames">
<list>
<value>logAspect</value>
<value>transactionInterceptor</value>
</list>
</property>
<property name="target"><ref bean="accountServiceTarget"/></property>
</bean>
如果我再定義一個Service,我還是需要在使用了ProxyFactoryBean的Service Bean中拷貝如下的內容而不同的是,它們之間只是更換了target屬性:
<property name="proxyInterfaces">
<value>hibernatesample.service.AccountService</value>
</property>
<property name="interceptorNames">
<list>
<value>logAspect</value>
<value>transactionInterceptor</value>
</list>
</property>
一個好的解決配置重復的辦法是使用Spring配置中繼承特性,但此繼承可不是OO中的繼承,它只是將父Bean中的未聲明的屬性推到子Bean聲明(target了),而父Bean去聲明那些配置子Bean相同的內容(就是上面的內容)。簡化后的內容如下:
<bean id="baseServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
<property name="interceptorNames">
<list>
<value>logAspect</value>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<bean id="accountService" parent="baseServiceProxy">
<property name="proxyInterfaces"><value>hibernatesample.service.AccountService</value></property>
<property name="target">
<bean class="hibernatesample.service.impl.AccountServiceImpl">
<property name="accountDAO" ref="accountDAO"></property>
</bean>
</property>
</bean>
對于事務聲明,除了采用通用的ProxyFactoryBean來攔截transactionInterceptor外,也可以采用TransactionProxyFactoryBean,它應該是變相的ProxyFactoryBean+transactionInterceptor。另外,可以使用TransactionProxyFactoryBean的postInterceptors或者preInterceptors屬性來配置其他切面(通知)。
<bean id="baseServiceProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
<property name="postInterceptors">
<ref bean="logAspect"/>
</property>
</bean>
在簡化Spring1.X文件配置方面,更好的選擇是使用自動代理。這里介紹一下兩個很好用的自動代理方式。
第一種自動代理是使用BeanNameAutoProxyCreator,只需要指定它的beanNames和interceptorNames,便可將interceptorNames列表中的切面(攔截器、通知)作用到匹配beanNames的Bean。注意的是,beanNames可不是類名,而是配置文件中Bean名。示例如下:
<bean id="serviceNameAutoProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<value>*Service</value>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<value>logAspect</value>
</list>
</property>
</bean>
第二種自動代理方式是使用DefaultAdvisorAutoProxyCreator。這在思想上已經是切面級的,而不是通知(攔截器)級的;也就是說,只有切面是一等公民,通知和攔截器已經不能脫離切入點而獨立工作了。還是上面的例子,使用DefaultAdvisorAutoProxyCreator的配置如下:
<bean id="businessServicePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<value>hibernatesample.service.*</value>
</property>
</bean>
<bean id="logAdvice" class="hibernatesample.service.util.LogAdvice"></bean>
<bean id="logAspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="businessServicePointcut"></property>
<property name="advice" ref="logAdvice"></property>
</bean>
<bean id="serviceClassFilter" class="hibernatesample.service.util.ServiceClassFilter"></bean>
<bean id="txAdvisor"
class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor">
<ref bean="transactionInterceptor"/>
</property>
<property name="classFilter" ref="serviceClassFilter"></property>
</bean>
<bean id="accountService" class="hibernatesample.service.impl.AccountServiceImpl">
<property name="accountDAO" ref="accountDAO"></property>
</bean>
<bean id="advisorAutoProxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>
相比于前面的配置方式,使用DefaultAdvisorAutoProxyCreator在配置文件上有了不少變化。首先是businessServicePointcut,其patterns可是要類(或包)級別的了。變化最大的就是事務聲明了。這里引入了TransactionAttributeSourceAdvisor ,由于transactionInterceptor的transactionAttributes只是方法級別的,因此需要一個classFilter來在類(包)級別上做匹配(這樣切入點就完整了),引入的 ServiceClassFilter的定義如下:
public class ServiceClassFilter implements ClassFilter{
public boolean matches(Class c) {
String name = c.getSimpleName();
if(name.indexOf("Service") == -1)return false;
String pg = c.getPackage().getName();
if(!pg.startsWith("hibernatesample.service"))return false;
return true;
}
}
好了,關于Spring1.X的AOP的介紹到此結束了。在我重新整理Spring1.X的AOP時,我更加覺得,Spring的配置文件太靈活了,同樣的功能會有很多種配置方式,完整的、簡潔的不一而足。對于開發者來說,需要根據自己的習慣來確定如何有效地操縱Spring1.X的AOP實現。