天天看點

Spring Aop Step-By-Step 學習筆記(下)

五. 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教程下載下傳:點選下載下傳  

繼續閱讀