??? ??? 本文通過一個“
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實現。