五. Spring-Aop 引入的介紹 下面我們介紹一種通知“引入”,關于引入,如同它的名字一樣,給對象添加方法和屬性。呵呵,好厲害吧。它是通過CBLIB來動态生成類的,是以自己用的時候别忘了加載這個包。 代碼: 購物時候放東西的包包; public interface CustomerBag { void addBag(Object obj); void clean(); int getCount(); } 我們要給别的對象類添加的Mixin,嘿嘿。 public class CustomerMixin extends DelegatingIntroductionInterceptor implements CustomerBag { private static final long serialVersionUID = 5296015143432822715L; private ArrayList bag = new ArrayList(); public void addBag(Object obj) { bag.add(obj); } public void clean() { bag = new ArrayList(); } public ArrayList getBag() { return bag; } public int getCount() { return bag.size(); } } 我們的xml檔案: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="customer" class="demo.Customer" singleton="false" /> <bean id="customerMixin" class="demo.CustomerMixin" singleton="false" /> <bean id="customerMixinAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor" singleton="false"> <constructor-arg> <ref bean="customerMixin" /> </constructor-arg> </bean> <bean id="customerBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyTargetClass" value="true" /> <property name="singleton" value="false" /> <property name="interceptorNames"> <list> <value>customerMixinAdvisor</value> </list> </property> <property name="target"> <ref bean="customer" /> </property> </bean> </beans> 可以看到singleton="false"處處可見,因為我們要每個新的對象都有自己引入的狀态,不可能隻有一個對象來維持,那個我們肯定是不希望的。 修改我們的RunDemo類,添加一個方法: public static void customer() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/customer.xml"); // 這個類是有CGLIB動态生成的; Customer bag = (Customer) context.getBean("customerBean"); CustomerBag bag2 = (CustomerBag) bag; bag2.addBag(new Object()); System.out.println(bag.getName()); System.out.println(bag2.getCount()); } 在main中調用這個方法,運作,結果如下: 悠~遊! 1 在這裡我要說明一下,關于引入這個通知的使用我僅僅是看了一眼,具體的例子可能有不恰當之處還請高手們指點。 六. Spring-Aop 之 BeanNameAutoProxyCreator BeanNameAutoProxyCreator 看其名,大概知道其意。根據bean的名字自動比對攔截代理,讓我們看看它能帶來什麼? 代碼: public class PerformanceThresholdInterceptor implements MethodInterceptor { private final long thresholdInMillis; PerformanceThresholdInterceptor(long time) { thresholdInMillis = time; } public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println(" 開始測試時間."); long t = System.currentTimeMillis(); Object o = invocation.proceed(); t = System.currentTimeMillis() - t; if (t > thresholdInMillis) { System.out.println(" 警告:調用的方法所耗費的時間超過預警時間(" + thresholdInMillis + " 微秒)"); } System.out.println(" 測試時間結束."); return o; } } 這個又是自定義的,呵呵。我們繼承MethodInterceptor這換類,實作我們自己的方法過濾器。具體要實作的功能就是檢驗我們要調用的方法,和OnePerCustomerInterceptor這個類一樣。 接下來是我們的xml檔案: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="kwikEMartTarget" class="demo.ApuKwikEMart"></bean> <bean id="performanceThresholdInterceptor" class="demo.advice.PerformanceThresholdInterceptor"> <constructor-arg> <value>5000</value> </constructor-arg> </bean> <bean id="beanNameAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Target</value> </list> </property> <property name="interceptorNames"> <value>performanceThresholdInterceptor</value> </property> </bean> </beans> 在main方法中加入我們的方法: public static void beanNameProxy() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/beanNameProxy.xml"); KwikEMart akem = (KwikEMart) context.getBean("kwikEMartTarget"); try { akem.buyCheese(new Customer()); } catch (KwikEMartException e) { e.printStackTrace(); } } 運作我們的例子,結果如下: 開始測試時間. --我想買:奶酪! 測試時間結束. 看到了麼?Spring aop自動尋找Bean的名字為* Target 的類,進行方法過濾。呵呵,可能你會說這個有什麼用?自己寫不也一樣麼?其實如果系統變得龐大的話,自己配置也是十分耗費精力的。 七. Spring-Aop DefaultAdvisorAutoProxyCreator 接下來我們将介紹更加強大的一個代理器:DefaultAdvisorAutoProxyCreator。 DefaultAdvisorAutoProxyCreator 和BeanNameAutoProxyCreator不同的是,DefaultAdvisorAutoProxyCreator隻和Advisor 比對,是以我們寫一個Advisor到xml文檔中去。 XML 文檔如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="kwikEMartTarget" class="demo.ApuKwikEMart"></bean> <bean id="performanceThresholdInterceptor" class="demo.advice.PerformanceThresholdInterceptor"> <constructor-arg> <value>5000</value> </constructor-arg> </bean> <!-- 使用RegexpMethodPointcutAdvisor來比對切入點完成個一個Advisor; --> <bean id="regexpFilterPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="pattern"> <!-- 比對的名字為方法名; --> <value>.*buy.*</value> </property> <property name="advice"> <ref bean="performanceThresholdInterceptor"/> </property> </bean> <bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" /> </beans> 添加下面方法調用main方法中去: public static void defaultAdvisorProxy() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/defaultAdvisorProxy.xml"); KwikEMart akem = (KwikEMart) context.getBean("kwikEMartTarget"); try { akem.buyCheese(new Customer()); } catch (KwikEMartException e) { e.printStackTrace(); } } 運作,結果如下: 開始測試時間. --我想買:奶酪! 測試時間結束. 八. Spring-Aop TargetSources 介紹 1. 可熱交換的目标源 可熱交換的目标源主要是在你程式運作中切換目标對象,而此時調用者引用的對象也會自動切換。具體的概念你可以參考Spring-Reference關于它的介紹,我們主要在程式中體會它給我們帶來的改變。 修改我們的xml成為下面的樣子: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="kwikEMartTarget" class="demo.ApuKwikEMart" ></bean> <bean id="swapApuKwikEMart" class="demo.SwapApuKwikEMart" singleton="false"></bean> <bean id="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" /> <bean id="nameMatchfilterPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut"> <property name="mappedName"> <value>buy*</value> </property> </bean> <bean id="runDemofilterPointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <ref bean="nameMatchfilterPointcut" /> </property> <property name="advice"> <ref bean="onePerCustomerInterceptor" /> </property> </bean> <bean id="swappable" class="org.springframework.aop.target.HotSwappableTargetSource"> <constructor-arg><ref local="kwikEMartTarget"/></constructor-arg> </bean> <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="demo.KwikEMart" /> <property name="interceptorNames"> <list> <value>runDemofilterPointcutAdvisor</value> </list> </property> <property name="targetSource"> <ref bean="swappable" /> </property> </bean> </beans> 代碼: 切換後的對象 public class SwapApuKwikEMart implements KwikEMart { private boolean cheeseIsEmpty = false; private boolean pepperIsEmpty = false; private boolean squishIsEmpty = false; public Cheese buyCheese(Customer customer) throws KwikEMartException { if (cheeseIsEmpty) { throw new NoMoreCheeseException(); } Cheese s = new Cheese(); System.out.println("-- 我不是ApuKwikEMart,我想買:" + s); return s; } public Pepper buyPepper(Customer customer) throws KwikEMartException { if (pepperIsEmpty) { throw new NoMorePepperException(); } Pepper s = new Pepper(); System.out.println("-- 我不是ApuKwikEMart,我想買:" + s); return s; } public Squish buySquish(Customer customer) throws KwikEMartException { if (squishIsEmpty) { throw new NoMoreSquishException(); } Squish s = new Squish(); System.out.println("-- 我不是ApuKwikEMart,我想買:" + s); return s; } public void setCheeseIsEmpty(boolean cheeseIsEmpty) { this.cheeseIsEmpty = cheeseIsEmpty; } public void setPepperIsEmpty(boolean pepperIsEmpty) { this.pepperIsEmpty = pepperIsEmpty; } public void setSquishIsEmpty(boolean squishIsEmpty) { this.squishIsEmpty = squishIsEmpty; } } 添加下面代碼的引用到我們的main中。 public static void swapKwikEMart() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/swapKwikmart.xml"); // 如果你想通過類來引用這個的話,就要用到CGLIB.jar了,同時在代理工廠裡面設定: //<property name="proxyTargetClass" value="true" /> KwikEMart akem = (KwikEMart) context.getBean("kwikEMart"); try { akem.buySquish(new Customer()); akem.buyPepper(new Customer()); akem.buyCheese(new Customer()); } catch (KwikEMartException e) { // 異常已經被截獲了,不信你看控制台!~; } HotSwappableTargetSource swap = (HotSwappableTargetSource) context.getBean("swappable"); swap.swap(context.getBean("swapApuKwikEMart")); try { akem.buySquish(new Customer()); akem.buyPepper(new Customer()); akem.buyCheese(new Customer()); } catch (KwikEMartException e) { // 異常已經被截獲了,不信你看控制台!~; } } 運作,結果如下: 店員:悠~遊! ,Can I help you ? --我想買:果醬! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我想買:胡椒粉! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我想買:奶酪! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我不是ApuKwikEMart,我想買:果醬! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我不是ApuKwikEMart,我想買:胡椒粉! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我不是ApuKwikEMart,我想買:奶酪! 店員:OK! 悠~遊!.give you! 可以看到,我們切換後的對象已經被置換了。注意 singleton="false" ,通常情況下需要設定為false,以保證Spring在必要的時候可以建立一個新的目标執行個體。 2. 支援池的目标源 使用支援目标池的源提供了一種和無狀态 session Ejb 類似的程式設計方式,在無狀态的 Session Ejb 中,維護了一個相同執行個體的池,提供從池中擷取可用對象的方法。 這次我們用到了 Commons - pool 這個包,同時在運作時候還需要 commons-collections.jar, 需要把它們加載到環境變量中。 xml 檔案如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="kwikEMartTarget" class="demo.ApuKwikEMart" singleton="false"></bean> <bean id="commonsPool" class="org.springframework.aop.target.CommonsPoolTargetSource"> <property name="targetBeanName"> <value>kwikEMartTarget</value> </property> <property name="maxSize"> <value>10</value> </property> </bean> <bean id="methodInvokingFactoryBeanAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject"> <ref bean="commonsPool" /> </property> <property name="targetMethod"> <value>getPoolingConfigMixin</value> </property> </bean> <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="demo.KwikEMart" /> <property name="interceptorNames"> <list> <value>methodInvokingFactoryBeanAdvisor</value> </list> </property> <property name="targetSource"> <ref bean="commonsPool" /> </property> </bean> </beans> 同時,我們還需要 添加下面代碼的引用到我們的main中。 代碼: public static void getCommonPoolObject() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/kwikemart.xml"); KwikEMart akem = (KwikEMart) context.getBean("kwikEMart"); try { akem.buyCheese(new Customer()); } catch (KwikEMartException e) { e.printStackTrace(); } PoolingConfig pool=(PoolingConfig)akem; System.out.println(" 池的大小為: "+pool.getMaxSize()); } 運作,結果如下: --我想買:奶酪! 池的大小為:10 同時在我們的控制台裡可以看到這句話: 資訊: Creating Commons object pool 你可以得到對象,也可以銷毀對象。但是必須你的目标對象實作了 DisposableBean 接口,重寫銷毀的方法。然後通過下面方法調用: CommonsPoolTargetSource comPool = (CommonsPoolTargetSource) context.getBean("commonsPool"); try { comPool.destroyObject(akem); } catch (Exception e) { e.printStackTrace(); } 銷毀池則用: comPool.destroy() ; 九. Spring-Aop 相關及其他 其他相關的比較重要的是org.springframework.aop.framework.ProxyFactoryBean 類,幾乎我們上篇都用到了這個類,簡單介紹一下:
ProxyFactoryBean
屬性 | 描述 |
target | 代理的目标對象 |
proxyInterfaces | 代理實作的接口 |
interceptorNames | 在應用到的目标對象上添加的Advice的名字,可以是攔截器、advisor或者其他通知類型的名字(順序很重要哦) |
其他還有singleton,proxyTargetClass。 singleton :每次調用getBean()的時候傳回一個新的執行個體,例如我們使用引入的時候,有狀态的bean要設定為false哦。 proxyTargetClass :是否代理目标類,而不是實作接口。隻能在使用CBLIB的時使用。 proxyTargetClass 重點說一下,什麼意思呢?白話說的意思就是:如果你不設定proxyInterfaces這個,就必須設定這個方法,并且方法值為True。就是告訴CBLIB你要動态建立一個代理類來引用我們的目标。 在 Spring-Reference 中,提到了事務代理,我想那個相對于持久化處理時候在了解比較合适。 對于中繼資料的支援,因為我還沒有精力讀到哪裡。是以這個暫且擱下,有興趣的讀者可以自己查閱參考。 非常感謝你能讀完正篇文章,将不足的地方通過郵件傳遞給我,我将盡快改正。最後我希望,能夠讓每一個讀過的人都有所獲得,讓我們享受Spring給我們帶來的樂趣吧。 參考資料; Spring-Reference 中文版 第五章 Spring 之面向方面程式設計 http://www.jactiongroup.net/reference/html/ Spring AOP 中文教程 http://spring.jactiongroup.net/viewtopic.php?t=478 Spring In Action 中文版 第三章 AOP 入門 其他: 全文源碼及word教程下載下傳:點選下載下傳