最近由于工作需要,要求掌握關于 Spring 方面的東西。是以花了兩個星期的時間來學習 Spring 的基本知識,主要包括 Ioc 和 Aop 兩方面。 本文為筆者的 Spring 在 Aop 方面的學習筆記,主要結合了 Spring In Action 第三章 和 Spring-Reference 第五章 為學習向導。根據自己的了解和書中的執行個體來一步一步完成對于在 Spring 中 Aop 方面的程式設計。其中基礎部分 Ioc 需要讀者自己參考資料了解,本文将不做描述。 說明:我将盡量縮短程式長度,在程式部分将減少注釋說明,重點要讀者自己根據上下文和程式結果了解體會,具體 api 資訊請讀者自己參考 Spring-api 文檔和相關資料。
一. 準備工作:1. 開發環境: l 适合人群: 要了解 Spring Ioc ,對 Spring- Aop 可以不了解或者僅僅熟悉 Aop 概念,未參與 Spring Aop 開發實戰的初學者。同時也希望高手對于本文的不足或了解錯誤之處給予指點,謝謝。 l 開發環境: JDK 1.4_2 l 開發工具: Eclipse 3.12 (未采用任何插件,主要是為初學者熟悉和了解 xml 文檔的配置) l 所需元件: Spring-Framework-1.2.8 下載下傳位址: http://prdownloads.sourceforge.net/springframework/spring-framework-1.2.8-with-dependencies.zip?use_mirror=ufpr 2. 建立工程: 首先用 Eclpse 建立一個普通 java 項目,導入 jar 檔案到編譯環境中,如下: a) Spring.jar 為 Spring 的核心 jar 檔案,必須; b) Commons-loggin.jar 日志檔案,必須; c) Cglib.jar 動态代理檔案,不是必須(本文需要); d) Jak-oro.jar 使用 Perl 和 Awk 正規表達式進行文本解析工具,不是必須(本文需要); 建立工程如下:

好了,下來我們開始我們的 Spring-aop 之旅;
二. Spring -Aop 入門AOP 全名 Aspect-oriented programming 。 Spring framework 是很有前途的 AOP 技術。作為一種非侵略性的,輕型的 AOP framework ,你無需使用預編譯器或其他的元标簽,便可以在 Java 程式中使用它。這意味着開發團隊裡隻需一人要對付 AOP framework ,其他人還是像往常一樣程式設計。 關鍵性概念: 1) Advice 是代碼的具體實作,例如一個實作日志記錄的代碼。 2) Pointcut 是在将 Advice 插入到程式的條件。 3) advisor 是把 pointcut 和 advice 的組合在一起裝配器。 圖例: 你的程式可能如上,現在要在三個流程上同時加入日志控制和權限控制,如下:
你的程式可能如上,現在要在三個流程上同時加入日志控制和權限控制,如下:
其中拿日志為例,日志控制和流程之間的穿插點處叫做連接配接點( Joinpoint ),而 Advice 就是我們日志處理的具體代碼, Pointcut 就是定義一個規則,對三個和業務有關的連接配接點進行過濾和比對(例如我們對于業務 1 不做日志處理)。 Advisor 就是将符合的規則的剩下的兩個連接配接點和具體的日志記錄代碼組合在一起。
三. Spring-Aop 前置通知、後置通知、環繞通知、異常通知實作我以 Spring In Action 提供的例子進行二次改造,完成我們自己的流程。業務流程很簡單,顧客到商店買東西,店員根據顧客的需要給于顧客提供服務。實作方法前插入,方法後插入,環繞,異常四種。 代碼: 建立一個使用者類; public class Customer { private String name = " 悠~遊!"; public String getName() { return name; } } 三個産品 public class Cheese { public String toString(){ return " 奶酪!"; } } public class Pepper { public String toString(){ return " 胡椒粉!"; } } public class Squish { public String toString(){ return " 果醬!"; } } 建立一個接口; public interface KwikEMart { Squish buySquish(Customer customer) throws KwikEMartException; Pepper buyPepper(Customer customer) throws KwikEMartException; Cheese buyCheese(Customer customer) throws KwikEMartException; } 實作這個接口,我們實作三個方法,買奶酪,買胡椒粉,買果醬; public class ApuKwikEMart implements KwikEMart { private boolean cheeseIsEmpty = false; private boolean pepperIsEmpty = false; private boolean squishIsEmpty = false; public Cheese buyCheese(Customer customer) throws NoMoreCheeseException{ if (cheeseIsEmpty) { throw new NoMoreCheeseException(); } Cheese s = new Cheese(); System.out.println("-- 我想買:" + s); return s; } public Pepper buyPepper(Customer customer) throws NoMorePepperException{ if (pepperIsEmpty) { throw new NoMorePepperException(); } Pepper s = new Pepper(); System.out.println("-- 我想買:" + s); return s; } public Squish buySquish(Customer customer) throws NoMoreSquishException{ if (squishIsEmpty) { throw new NoMoreSquishException(); } Squish s = new Squish(); System.out.println("-- 我想買:" + 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; } } 環繞通知的實作,必須實作invoke方法,通過調用invoke.proceed()手工調用對象方法: public class OnePerCustomerInterceptor implements MethodInterceptor { private Set customers=new HashSet(); public Object invoke(MethodInvocation invoke) throws Throwable { Customer customer=(Customer)invoke.getArguments()[0]; if(customers.contains(customer)){ throw new KwikEMartException("One per customer."); } System.out.println(" 店員:"+customer.getName() + " ,Can I help you ?"); Object squishee=invoke.proceed(); // 手工調用對象方法; System.out.println(" 店員:OK! " + customer.getName() + ".give you! " ); customers.add(squishee); return squishee; } } 前置通知的實作; public class WelcomeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { Customer customer = (Customer) args[0]; System.out.println(" 店員::Hello " + customer.getName() + " . How are you doing?"); } } public class Customer { private String name = " 悠~遊!"; public String getName() { return name; } } 後置通知實作; public class ThankYouAdvice implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { Customer customer = (Customer) args[0]; System.out.println(" 店員:Thank you " + customer.getName() + " . Come again! " ); } } 系統異常處理通知實作; public class KwikEmartExceptionAdvice implements ThrowsAdvice { public void afterThrowing(NoMoreSquishException e) { System.out.println(" 系統:NoMoreSquisheesException異常截獲了: " + e.getMessage()); } public void afterThrowing(NoMoreCheeseException e) { System.out.println(" 系統:NoMoreCheeseException異常截獲了: " + e.getMessage()); } public void afterThrowing(NoMorePepperException e) { System.out.println(" 系統:NoMorePepperException異常截獲了: " + e.getMessage()); } } 自定義的異常接口; public class KwikEMartException extends Exception { private static final long serialVersionUID = -3962577696326432053L; String retValue = "KwikEMartException 異常!"; public KwikEMartException(String name) { retValue = name; } public KwikEMartException() { } public String getMessage() { return retValue; } } 沒有更多的奶酪異常; public class NoMoreCheeseException extends KwikEMartException { private static final long serialVersionUID = -3961123496322432053L; String retValue = "NoMoreCheeseException 異常!"; public NoMoreCheeseException() { super(); } public NoMoreCheeseException(String name) { super(name); } public String getMessage() { return retValue; } } 沒有更多的胡椒粉異常; public class NoMorePepperException extends KwikEMartException { private static final long serialVersionUID = -3961234696322432053L; String retValue = "NoMorePepperException 異常!"; public NoMorePepperException() { super(); } public NoMorePepperException(String name) { super(name); } public String getMessage() { return retValue; } } 沒有更多的果醬異常; public class NoMoreSquishException extends KwikEMartException { private static final long serialVersionUID = -3121234696322432053L; String retValue = "NoMoreSquishException 異常!"; public NoMoreSquishException() { super(); } public NoMoreSquishException(String name) { super(name); } public String getMessage() { return retValue; } } 運作執行個體類; public class RunDemo { public static void kwikEMart() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/kwikemart.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) { // 異常已經被截獲了,不信你看控制台!~; } } public static void main(String[] args) { kwikEMart(); } } 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"> <!-- 把這裡注釋去掉的話,程式調用的時候測試異常通知; <property name="cheeseIsEmpty"> <value>true</value> </property> <property name="pepperIsEmpty"> <value>true</value> </property> <property name="squishIsEmpty"> <value>true</value> </property> --> </bean> <!-- 方法調用前通知 --> <bean id="welcomeAdvice" class="demo.advice.WelcomeAdvice" /> <!-- 方法調用後通知 --> <bean id="thankYouAdvice" class="demo.advice.ThankYouAdvice" /> <!-- 環繞調用通知 --> <bean id="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" /> <!-- 異常調用通知 --> <bean id="kwikEmartExceptionAdvice" class="demo.advice.KwikEmartExceptionAdvice" /> <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="demo.KwikEMart" /> <property name="interceptorNames"> <list> <value>welcomeAdvice</value> <value>thankYouAdvice</value> <value>onePerCustomerInterceptor</value> <value>kwikEmartExceptionAdvice</value> </list> </property> <property name="target"> <ref bean="kwikEMartTarget" /> </property> </bean> </beans> 這個例子東西很多,不過每個類的代碼都不大。如果你對 org.springframework.aop.framework.ProxyFactoryBean 不是很了解的話可以看我下篇尾處的介紹。 讀清楚之後,我們運作RunDemo 類,檢視控制台結果,如下: 店員::Hello 悠~遊! . How are you doing? 店員:悠~遊! ,Can I help you ? --我想買:果醬! 店員:OK! 悠~遊!.give you! 店員:Thank you 悠~遊! . Come again! 店員::Hello 悠~遊! . How are you doing? 店員:悠~遊! ,Can I help you ? --我想買:胡椒粉! 店員:OK! 悠~遊!.give you! 店員:Thank you 悠~遊! . Come again! 店員::Hello 悠~遊! . How are you doing? 店員:悠~遊! ,Can I help you ? --我想買:奶酪! 店員:OK! 悠~遊!.give you! 店員:Thank you 悠~遊! . Come again! 我們将 kwikEMartTarget 裡面的注釋去掉,測試異常實作,如下: 店員::Hello 悠~遊! . How are you doing? 店員:悠~遊! ,Can I help you ? 系統:NoMoreSquisheesException異常截獲了: NoMoreSquishException 異常! 好好了解一下,我就不廢話了,我們進行下一節。
四. Spring-Aop 之 Pointcut +advice+Advisor 實作我們修改我們的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="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" /> <!-- 使用NameMatchMethodPointcut --> <bean id="nameMatchfilterPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut"> <property name="mappedName"> <!-- buy.* 以buy開頭的方法; --> <value>buy*</value> </property> </bean> <!-- 使用Perl5RegexpMethodPointcut --> <bean id="regexpFilterPointcut" class="org.springframework.aop.support.Perl5RegexpMethodPointcut"> <property name="pattern"> <!-- .*buy.+ 以buy開頭的方法; .*buyS.+ 以buyS開頭的方法; --> <value>.*suy.+</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="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="demo.KwikEMart" /> <property name="interceptorNames"> <list> <value>runDemofilterPointcutAdvisor</value> </list> </property> <property name="target"> <ref bean="kwikEMartTarget" /> </property> </bean> </beans> 運作,結果如下: 店員:悠~遊! ,Can I help you ? --我想買:果醬! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我想買:胡椒粉! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我想買:奶酪! 店員:OK! 悠~遊!.give you! 在這裡簡單說明一下xml文檔: nameMatchfilterPointcut 和regexpFilterPointcut 是我們自己定義好規則的Pointcut。nameMatchfilterPointcut 根據mappedName來設定過濾規則, regexpFilterPointcut則是用pattern來設定過濾規則。runDemofilterPointcutAdvisor 則将我們的 Pointcut 和advice組合在一起。 讀者可以自己修改runDemofilterPointcutAdvisor的pointcut來切換不同的Pointcut。如果需要RegexpMethodPointcut 的子類的實作,必須要oro包支援。(注:RegexpMethodPointcut有倆個子類的實作,JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut)。 但是,如果我們想讓我們的
Advisor同時實作多個
Pointcut+advice怎麼辦呢?利用Spring In Action裡面的執行個體,我們來自己實作我們的
Pointcut。代碼: public class MyUnionPointcut implements Pointcut { private Pointcut delegate; public ClassFilter getClassFilter() { return getDelegate().getClassFilter(); } private Pointcut getDelegate() { if (delegate == null) { throw new AopConfigException("No pointcuts have been configured."); } return delegate; } public MethodMatcher getMethodMatcher() { return getDelegate().getMethodMatcher(); } public void setPointcuts(List pointcuts) { if (pointcuts == null || pointcuts.size() == 0) { throw new AopConfigException("Must have at least one Pointcut."); } delegate = (Pointcut) pointcuts.get(0); for (int i = 1; i < pointcuts.size(); i++) { Pointcut pointcut = (Pointcut) pointcuts.get(i); delegate = Pointcuts.union(delegate, pointcut); } } } 其實就是繼承Pointcut類,實作getMethodMatcher()方法即可,接下來看我們把那兩個Pointcut組合成一個,當其中一個Pointcut滿足的時候就傳回true,調用我們的Advice。 在xml中,加入下面代碼: <bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut"> <property name="pointcuts"> <list> <!-- 這裡說明一下:動态切入點和靜态切入點集合在一起的時候好像有問題? --> <ref bean="nameMatchfilterPointcut" /> <ref bean="regexpFilterPointcut" /> </list> </property> </bean> 修改runDemofilterPointcutAdvisor的pointcut來加入我們組合好的Pointcut。 <bean id="runDemofilterPointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="pointcut"> <ref bean= ”myUnionPointcut" /> </property> <property name="advice"> <ref bean="onePerCustomerInterceptor" /> </property> </bean> 同時在運作前,讀者可以自己修改兩個pointcut的比對方法,來比對更多的可選項。同時可以參考的一個類是org.springframework.aop.support.UnionPointcut,它實作兩個pointcut的聯合。如果讀者想實作更加靈活的比對,需要自己來定義自己的pointcut。(如第一個pointcut交叉第二個pointcut,聯合第三個pointcut),交叉的工具類為ComposablePointcut。 運作的結果請讀者自己試驗。這個時候您可能在想,這些pointcut都是Spring自己的實作,我們能否自己來定義我們自己規則的pointcut呢?當然可以!~ 代碼: public class MyPointcut extends StaticMethodMatcherPointcut implements Serializable { private static final long serialVersionUID = -101281038294508751L; private int money = 0; public boolean matches(Method method, Class targetClass) { if (method.getName().indexOf("buyCheese") == 0 && money >= 100) { money -= 100; return true; } else if (method.getName().indexOf("buyPepper") == 0 && money >= 5) { money -= 5; return true; } else if (method.getName().indexOf("buySquish") == 0 && money >= 10) { money -= 10; return true; } System.out.println(" 門衛:你要買的東西太貴,你的錢 "+money+" 太少!~ ,取消服務!"); return false; } public void setMoney(int money) { this.money = money; } } 這個就是我們自己定義的靜态Pointcut,主要實作自己的matches方法。看看如何加入到我們的XML文檔中: <!-- 使用自定義的切入點 --> <bean id="myPointcut" class="demo.pointcut.MyPointcut"> <property name="money"> <value>100</value> </property> </bean> 很簡單不是麼?我們定義一個數字,就是使用者的money,進入商店時候兜裡的錢^_^。同樣修改我們的myUnionPointcut裡面的pointcuts,加入我們的pointcut。 <bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut"> <property name="pointcuts"> <list> <!— 上面兩個要設定成不通過哦,或者索性就去掉先。 --> <ref bean="nameMatchfilterPointcut" /> <ref bean="regexpFilterPointcut" /> <ref bean="myPointcut" /> </list> </property> </bean> 當上面兩個Pointcut定義的規則不通過的時候,程式開始校驗我們的myPointcut。運作,結果如下: 店員:悠~遊! ,Can I help you ? --我想買:果醬! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我想買:胡椒粉! 店員:OK! 悠~遊!.give you! 門衛:你要買的東西太貴,你的錢 85 太少!~ , 取消服務! --我想買:奶酪!//服務員沒了... 好了,是不是我們想要的結果呢?呵呵。 同時,Spring 提供動态Pointcut。關于動态的說明我就不在熬述了,我們這裡隻關心具體Spring帶給我們的具體實作方法,具體應用請讀者自己斟酌使用。 <!-- 定制動态接入點,來判斷目前線程堆棧中是否有demo.RunDemo這個類; --> <bean id="runDemoPointcut" class="org.springframework.aop.support.ControlFlowPointcut"> <constructor-arg> <value>demo.RunDemo</value> </constructor-arg> </bean> 修改下面的引用我們的動态Pointcut; <bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut"> <property name="pointcuts"> <list> <ref bean=" runDemoPointcut " /> </list> </property> </bean> 運作,結果如下: 店員:悠~遊! ,Can I help you ? --我想買:果醬! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我想買:胡椒粉! 店員:OK! 悠~遊!.give you! 店員:悠~遊! ,Can I help you ? --我想買:奶酪! 店員:OK! 悠~遊!.give you! 動态切入點是根據目前堆棧資訊進行方法比對的一種規則,讀者可以自己修改demo.RunDemo,如java.lang.Integer,來看看結果。 --我想買:果醬! --我想買:胡椒粉! --我想買:奶酪! 到這裡能夠讀下來已經很不容易了,呵呵。還是站起來走動一下吧,接下來我們将搞定其他的一些東東。
五. 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 之 BeanNameAutoProxyCreatorBeanNameAutoProxyCreator 看其名,大概知道其意。根據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教程下載下傳:點選下載下傳