Spring Aop Step-By-Step 學習筆記(上)
最近由于工作需要,要求掌握關于 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 異常!