6.2.4 代理接口
當目標Bean的實現類實現了接口后,Spring AOP可以為其創建JDK動態代理,而無須使用CGLIB創建的代理,這種代理稱為代理接口。
創建AOP代理必須指定兩個屬性:目標Bean和處理。實際上,很多AOP框架都以攔截器作為處理。因為Spring AOP與IoC容器的良好整合,因此配置代理Bean時,完全可以利用依賴注入來管理目標Bean和攔截器Bean。
下面的示例演示了基于AOP的權限認證,它是簡單的TestService接口,該接口模擬Service組件,該組件內包含兩個方法:
?? ● 查看數據。
?? ● 修改數據。
接口的源代碼如下:
//Service組件接口
public interface TestService
{
??? //查看數據
??? void view();
??? //修改數據
??? void modify();
}
該接口的實現類實現兩個方法。因為篇幅限制,本示例并未顯示出完整的查看數據和修改數據的持久層操作,僅僅在控制臺打印兩行信息。實際的項目實現中,兩個方法的實現則改成對持久層組件的調用,這不會影響示例程序的效果。實現類的源代碼如下:
TestService接口的實現類
public class TestServiceImpl implements TestService
{
??? //實現接口必須實現的方法
??? public void view()
??? {
??????? System.out.println("用戶查看數據");
??? }
??? //實現接口必須實現的方法
??? public void modify()
??? {
??????? System.out.println("用戶修改數據");
??? }
}
示例程序采用Around 處理作為攔截器,攔截器中使用依賴注入獲得當前用戶名。實際Web應用中,用戶應該從session中讀取。這不會影響示例代碼的效果。攔截器源代碼如下:
public class AuthorityInterceptor implements MethodInterceptor
{
??? //當前用戶名
??? private String user;
??? //依賴注入所必需的setter方法
??? public void setUser(String user)
??? {
??????? this.user = user;
??? }
??? public Object invoke(MethodInvocation invocation) throws Throwable
??? {
??????? //獲取當前攔截的方法名
??????? String methodName = invocation.getMethod().getName();
??????? //下面執行權限檢查
??????? //對既不是管理員,也不是注冊用戶的情況
??????? if (!user.equals("admin") && !user.equals("registedUser"))
??????? {
??????????? System.out.println("您無權執行該方法");
??????????? return null;
??????? }
??????? //對僅僅是注冊用戶,調用修改數據的情況
??????? else if (user.equals("registedUser") && methodName.equals
??????? ("modify"))
??????? {
??????????? System.out.println("您不是管理員,無法修改數據");
??????????? return null;
??????? }
??????? //對管理員或注冊用戶,查看數據的情況
??????? else
??????? {
??????????? return invocation.proceed();
??????? }
??? }
}
TestAction類依賴TestService。因篇幅關系,此處不給出TestAction的接口的源代碼,TestActionImpl的源代碼如下:
public class TestActionImpl
{
??? //將TestService作為成員變量,面向接口編程
??? private TestService ts;
??? //依賴注入的setter方法
??? public void setTs(TestService ts)
??? {
??????? this.ts = ts;
??? }
??? //修改數據
??? public void modify()
??? {
??????? ts.modify();
??? }
??? //查看數據
??? public void view()
??? {
??????? ts.view();
??? }
}
配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
?????? xsi:schemaLocation="http://www.springframework.org/schema/beans
?????? http://www.springframework.org/schema/beans/spring-beans.xsd">
??? <!-- 配置目標Bean -->
??? <bean id="serviceTarget" class="lee.TestServiceImpl"/>
??? <!-- 配置攔截器,攔截器作為處理使用 -->
??? <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
??????? <property name="user" value="admin"/>
??? </bean>
??? <!-- 配置代理工廠Bean,負責生成AOP代理 -->
??? <bean id="service" class="org.springframework.aop.framework.
??? ProxyFactoryBean">
??????? <!-- 指定AOP代理所實現的接口 -->
??????? <property name="proxyInterfaces" value="lee.TestService"/>
??????? <!-- 指定AOP代理所代理的目標Bean -->
??????? <property name="target" ref="serviceTarget"/>
??????? <!-- AOP代理所需要的攔截器列表 -->
??????? <property name="interceptorNames">
??????????? <list>
??????????????? <value>authorityInterceptor</value>
??????????? </list>
??????? </property>
??? </bean>
??? <!-- 配置Action Bean,該Action依賴TestService Bean -->
??? <bean id="testAction" class="lee.TestActionImpl">
??????? <!-- 此處注入的是依賴代理Bean -->
??????? <property name="ts" ref="service"/>
??? </bean>
</beans>
主程序請求testAction Bean,然后調用該Bean的兩個方法,主程序如下:
public class BeanTest
{
??? public static void main(String[] args)throws Exception
??? {
??????? //創建Spring容器實例
??????? ApplicationContext ctx = new FileSystemXmlApplicationContext
??????? ("bean.xml");
??????? //獲得TestAction bean
??????? TestAction ta = (TestAction)ctx.getBean("testAction");
??????? //調用bean的兩個測試方法
??????? ta.view();
??????? ta.modify();
??? }
}
程序執行結果如下:
[java] 用戶查看數據
[java] 用戶修改數據
代理似乎沒有發揮任何作用。因為配置文件中的當前用戶是admin,admin用戶具備訪問和修改數據的權限,因此代理并未阻止訪問。將配置文件中的admin修改成registed- User,再次執行程序,得到如下結果:
[java] 用戶查看數據
[java] 您不是管理員,無法修改數據
代理阻止了registedUser修改數據,查看數據可以執行。將registedUser修改成其他用戶,執行程序,看到如下結果:
[java] 您無權執行該方法
[java] 您無權執行該方法
代理阻止用戶對兩個方法的執行。基于AOP的權限檢查,可以降低程序的代碼量,因為無須每次調用方法之前,手動編寫權限檢查代碼;同時,權限檢查與業務邏輯分離,提高了程序的解耦。
示例中的目標Bean被暴露在容器中,可以被客戶端代碼直接訪問。為了避免客戶端代碼直接訪問目標Bean,可以將目標Bean定義成代理工廠的嵌套Bean,修改后的配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
?????? xsi:schemaLocation="http://www.springframework.org/schema/beans
?????? http://www.springframework.org/schema/beans/spring-beans.xsd">
??? <!-- 配置攔截器,攔截器作為處理使用 -->
??? <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
??????? <property name="user" value="admin"/>
??? </bean>
??? <!-- 配置代理工廠Bean,該工廠Bean將負責創建目標Bean的代理 -->
??? <bean id="service" class="org.springframework.aop.framework.
??? ProxyFactoryBean">
??????? <!-- 指定AOP代理所實現的接口 -->
??????? <property name="proxyInterfaces" value="lee.TestService"/>
??????? <property name="target">
??????????? <!-- 以嵌套Bean的形式定義目標Bean,避免客戶端直接訪問目標Bean -->
??? ??????? <bean class="lee.TestServiceImpl"/>
??????? </property>
??????? <!-- AOP代理所需要的攔截器列表 -->
??????? <property name="interceptorNames">
??????????? <list>
??????????????? <value>authorityInterceptor</value>
??????????? </list>
??????? </property>
??? </bean>
??? <!-- 配置Action Bean,該Action依賴TestService Bean -->
??? <bean id="testAction" class="lee.TestActionImpl">
??????? <!-- 此處注入的是依賴代理Bean -->
??????? <property name="ts" ref="service"/>
??? </bean>
</beans>
由上面介紹的內容可見,Spring的AOP是對JDK動態代理模式的深化。通過Spring AOP組件,允許通過配置文件管理目標Bean和AOP所需的處理。
下面將繼續介紹如何為沒有實現接口的目標Bean創建CGLIB代理。
6.2.5 代理類
如果目標類沒有實現接口,則無法創建JDK動態代理,只能創建CGLIB代理。如果需要沒有實現接口的Bean實例生成代理,配置文件中應該修改如下兩項:
?? ● 去掉<property name="proxyInterfaces"/>聲明。因為不再代理接口,因此,此處的配置沒有意義。
?? ● 增加<property name="proxyTargetClass">子元素,并設其值為true,通過該元素強制使用CGLIB代理,而不是JDK動態代理。
注意:最好面向接口編程,不要面向類編程。同時,即使在實現接口的情況下,也可強制使用CGLIB代理。
CGLIB代理在運行期間產生目標對象的子類,該子類通過裝飾器設計模式加入到Advice中。因為CGLIB代理是目標對象的子類,則必須考慮保證如下兩點:
?? ● 目標類不能聲明成final,因為final類不能被繼承,無法生成代理。
?? ● 目標方法也不能聲明成final,final方法不能被重寫,無法得到處理。
當然,為了需要使用CGLIB代理,應用中應添加CGLIB二進制Jar文件。
6.2.6 使用BeanNameAutoProxyCreator自動創建代理
這是一種自動創建事務代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator實例,該實例將會對指定名字的Bean實例自動創建代理。實際上,BeanNameAutoProxyCreator是一個Bean后處理器,理論上它會對容器中所有的Bean進行處理,實際上它只對指定名字的Bean實例創建代理。
BeanNameAutoProxyCreator根據名字自動生成事務代理,名字匹配支持通配符。
與ProxyFactoryBean一樣,BeanNameAutoProxyCreator需要一個interceptorNames屬性,該屬性名雖然是“攔截器”,但并不需要指定攔截器列表,它可以是Advisor或任何處理類型。
下面是使用BeanNameAutoProxyCreator的配置片段:
<!-- 定義事務攔截器bean -->
<bean id="transactionInterceptor"
??? class="org.springframework.transaction.interceptor.
??? TransactionInterceptor">
??? <!-- 事務攔截器bean需要依賴注入一個事務管理器 -->
??? <property name="transactionManager" ref="transactionManager"/>
??? <property name="transactionAttributes">
??????? <!-- 下面定義事務傳播屬性 -->
??????? <props>
??????????? <prop key="insert*">PROPAGATION_REQUIRED </prop>
??????????? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
??????????? <prop key="*">PROPAGATION_REQUIRED</prop>
??????? </props>
??? </property>
??? </bean>
<!-- 定義BeanNameAutoProxyCreator Bean,它是一個Bean后處理器,
??????? 負責為容器中特定的Bean創建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
??? BeanNameAutoProxyCreator">
??? <!-- 指定對滿足哪些bean name的bean自動生成業務代理 -->
??? <property name="beanNames">
??????? <list>
??????????? <!-- 下面是所有需要自動創建事務代理的Bean -->
??????? ??? <value>core-services-applicationControllerSevice</value>
??????? ??? <value>core-services-deviceService</value>
??????? ??? <value>core-services-authenticationService</value>
??????? ??? <value>core-services-packagingMessageHandler</value>
??????? ??? <value>core-services-sendEmail</value>
??????? ??? <value>core-services-userService</value>
??????????? <!-- 此處可增加其他需要自動創建事務代理的Bean -->
??????? </list>
??? </property>
??? <!-- 下面定義BeanNameAutoProxyCreator所需的攔截器 -->
??? <property name="interceptorNames">
??????? <list>
??????????? <value>transactionInterceptor</value>
??????????? <!-- 此處可增加其他新的Interceptor -->
??????? </list>
??? </property>
</bean>
上面的片段是使用BeanNameAutoProxyCreator自動創建事務代理的片段。Transaction- Interceptor用來定義事務攔截器,定義事務攔截器時傳入事務管理器Bean,也指定事務傳播屬性。
通過BeanNameAutoProxyCreator定義Bean后處理器,定義該Bean后處理器時,通過beanNames屬性指定有哪些目標Bean生成事務代理;還需要指定“攔截器鏈”,該攔截器鏈可以由任何處理組成。
定義目標Bean還可使用通配符,使用通配符的配置片段如下所示:
<!-- 定義BeanNameAutoProxyCreator Bean,它是一個Bean后處理器,
??????? 負責為容器中特定的Bean創建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
??? <!-- 指定對滿足哪些bean name的bean自動生成業務代理 -->
??? <property name="beanNames">
??????? <!-- 此處使用通配符確定目標bean -->
??????? <value>*DAO,*Service,*Manager</value>
??? </property>
??? <!-- 下面定義BeanNameAutoProxyCreator所需的事務攔截器 -->
??? <property name="interceptorNames">
??????? <list>
??????????? <value>transactionInterceptor</value>
??????????? <!-- 此處可增加其他新的Interceptor -->
??????? </list>
??? </property>
</bean>
上面的配置片段中,所有名字以DAO、Service、Manager結尾的bean,將由該“bean后處理器”為其創建事務代理。目標bean不再存在,取而代之的是目標bean的事務代理。通過這種方式,不僅可以極大地降低配置文件的繁瑣,而且可以避免客戶端代碼直接調用目標bean。
注意:雖然上面的配置片段是為目標對象自動生成事務代理。但這不是唯一的,如果有需要,可以為目標對象生成任何的代理。BeanNameAutoProxyCreator為目標對象生成怎樣的代理,取決于傳入怎樣的處理Bean,如果傳入事務攔截器,則生成事務代理Bean;否則將生成其他代理,在后面的實例部分,讀者將可以看到大量使用BeanNameAutoProxy- Creator創建的權限檢查代理。
6.2.7 使用DefaultAdvisorAutoProxyCreator自動創建代理
Spring還提供了另一個Bean后處理器,它也可為容器中的Bean自動創建代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更強大的自動代理生成器。它將自動應用于當前容器中的advisor,不需要在DefaultAdvisorAutoProxyCreator定義中指定目標Bean的名字字符串。
這種定義方式有助于配置的一致性,避免在自動代理創建器中重復配置目標Bean 名。
使用該機制包括:
?? ● 配置DefaultAdvisorAutoProxyCreator bean定義。
?? ● 配置任何數目的Advisor,必須是Advisor,不僅僅是攔截器或其他處理。因為,必須使用切入點檢查處理是否符合候選Bean定義。
DefaultAdvisorAutoProxyCreator計算Advisor包含的切入點,檢查處理是否應該被應用到業務對象,這意味著任何數目的Advisor都可自動應用到業務對象。如果Advisor中沒有切入點符合業務對象的方法,這個對象就不會被代理。如果增加了新的業務對象,只要它們符合切入點定義,DefaultAdvisorAutoProxyCreator將自動為其生成代理,無須額外?? 配置。
當有大量的業務對象需要采用相同處理,DefaultAdvisorAutoProxyCreator是非常有用的。一旦定義恰當,直接增加業務對象,而不需要額外的代理配置,系統自動為其增加???? 代理。
<beans>
??? <!-- 定義Hibernate局部事務管理器,可切換到JTA全局事務管理器 -->
??? <bean id="transactionManager"
???????? class="org.springframework.orm.hibernate3.
??????? HibernateTransactionManager">
??????? <!-- 定義事務管理器時,依賴注入SessionFactory -->
???????? <property name="sessionFactory" ref bean="sessionFactory"/>
??? </bean>
??? <!-- 定義事務攔截器 -->
??? <bean id="transactionInterceptor"
??????? class="org.springframework.transaction.interceptor.
??????? TransactionInterceptor">
???????? <property name="transactionManager" ref="transactionManager"/>
???????? <property name="transactionAttributeSource">
??????????? <props>
??????????????? <prop key="*">PROPAGATION_REQUIRED</prop>
????? ??????????? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
??????????? </props>
??????? </property>
??? </bean>
??? <!-- 定義DefaultAdvisorAutoProxyCreator Bean,這是一個Bean后處理器 -->
??? <bean class="org.springframework.aop.framework.autoproxy.
??? DefaultAdvisorAutoProxyCreator"/>
??? <!-- 定義事務Advisor -->
??? <bean class="org.springframework.transaction.interceptor.
??? TransactionAttributeSourceAdvisor">
??? ???? <property name="transactionInterceptor" ref=
??????? "transactionInterceptor"/>
??? </bean>
??? <!-- 定義額外的Advisor>
??? <bean id="customAdvisor" class="lee.MyAdvisor"/>
</beans>
DefaultAdvisorAutoProxyCreator支持過濾和排序。如果需要排序,可讓Advisor實現org.springframework.core.Ordered接口來確定順序。TransactionAttributeSourceAdvisor已經實現Ordered接口,因此可配置其順序,默認是不排序。
采用這樣的方式,一樣可以避免客戶端代碼直接訪問目標Bean,而且配置更加簡潔。只要目標Bean符合切入點檢查,Bean后處理器自動為目標Bean創建代理,無須依次指定目標Bean名。
注意:Spring也支持以編程方式創建AOP代理,但這種方式將AOP代理所需要的目標對象和處理Bean等對象的耦合降低到代碼層次,因此不推薦使用。如果讀者需要深入了解如何通過編程方式創建AOP代理,請參閱筆者所著的《Spring2.0寶典》。