最近由于工作需要,要求掌握關于 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
下載地址:
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 就是將符合的規則的剩下的兩個連接點和具體的日志記錄代碼組合在一起。
建立一個接口;
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 異常!
好好理解一下,我就不廢話了,我們進行下一節。
當上面兩個Pointcut定義的規則不通過的時候,程序開始校驗我們的myPointcut。運行,結果如下:
店員:悠~游! ,Can I help you ?
--我想買:果醬!
店員:OK! 悠~游!.give you!
店員:悠~游! ,Can I help you ?
--我想買:胡椒粉!
店員:OK! 悠~游!.give you!
門衛:你要買的東西太貴,你的錢 85 太少!~ , 取消服務!
--我想買:奶酪!//服務員沒了...
好了,是不是我們想要的結果呢?呵呵。
同時,Spring 提供動態Pointcut。關于動態的說明我就不在熬述了,我們這里只關心具體Spring帶給我們的具體實現方法,具體應用請讀者自己斟酌使用。
運行,結果如下:
店員:悠~游! ,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,來看看結果。
--我想買:果醬!
--我想買:胡椒粉!
--我想買:奶酪!
到這里能夠讀下來已經很不容易了,呵呵。還是站起來走動一下吧,接下來我們將搞定其他的一些東東。