1. 程式人生 > >精通Spring+4.x++企業開發與實踐之SpringAOP基礎

精通Spring+4.x++企業開發與實踐之SpringAOP基礎

AOP概述

AOP的簡稱"Aspect Oriented Programing"的簡稱———面向切面程式設計。

AOP術語

連線點

一個類或者一段程式程式碼用於一些具有邊界性質的特性的特定點。這些程式碼的特定點就被成為"連線點"。Spring只支援方法的連線點,即僅能在方法呼叫前,方法呼叫後,方法丟擲異常時及方法呼叫後這些程式執行點織入增強。連線點由兩個資訊確定: 1.用方法表示的程式執行點 2.用相對於位置表示方位。 例如: demo.foo()方法執行器的連線點,執行點未demo.foo(),方位未改方法執行前的位置。Spring使用切點對執行點進行定位,而方位裝載增強型別中定義。

切點(Pointcut)

每個程式類都用於多個連繫欸但,如果一個用於兩個方法的類,這兩個方法都是連線點,連線點是客觀存在的事務。AOP通過"切點"定位特定的連線點。切點和連線點是一對多的關係(就像資料庫查詢條件和記錄的關係)。在Spring中,切點使用org.springframework.aop.Pointcut介面進行描述,它使用類和方法作為連線點的查詢條件,Spring AOP的規則系欸效能引起負責系欸效能切點所設定的查詢條件,找到對應的連線點。連線點是方法執行前,執行後等包括方位資訊的具體程式執行點,而切點值定位到某個方法上,所以如果希望定位到具體的連線點上,還需要提供方位資訊.

增強(Advice)

增強是織入目標類連線點上的一段程式程式碼,結合執行段的方位資訊和切點資訊,就可以找到特定的連線。真正的增強及包括用於新增到目標連線點上的一段執行邏輯,由包含用於定位連線點的方位資訊,所以Spring所提供的增強介面的都是帶方位名的。所以Spring所提供的增強介面都是帶方位名的,例如BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等。只有結合了切點和增強,才能確定特定的連線點並實施增強邏輯。

目標物件(Target)

增強邏輯的織入目標類。如果沒有AOP,那麼目標業務需要自己實現所有的邏輯,在AOP的幫助下,那些非橫切的邏輯的程式邏輯,而效能監視和事務管理等這些橫切的邏輯則可以使用AOP的動態織入特定的連線點上。

引介(Introduction)

引介是一種特護的增強,它為類新增一些屬性和方法。這樣,幾時一個業務類原本沒有實現某個介面,通過AOP的引介功能,也可以動態地位改業務類新增介面地實現邏輯,讓業務類成為這個介面地實現類。

織入(Weaving)

織入是將增強新增到慕白哦類地具體連線點上。AOP就像一臺織布機,將目標類,曾倩或者引介天衣無縫地編織到一起。AOP有三種織入方式: 1.編譯期織入,這個要求使用特殊地Java編譯器。 2.類裝載期織入,這個要求使用特殊地類裝器。 動態代理,在執行期位目標類新增增強生產子類地方式。 Spring採用了動態代理地織入,而AspectJ採用編譯期織入和類裝載期織入。

代理(Proxy)

一個類被AOP織入增強之後,就產生了一個結果類,它是融合了袁磊和曾倩邏輯地代理類。根據不同地代理方式,代理可能是ihe袁磊具有相同介面地類,也可能是原類地子類。,所以可以採用於呼叫原類相同地方式呼叫代理類。

切面(Aspect)

切面有切點和曾倩(引介)組成,它既包括橫切邏輯地定義,也包括了連線點地定義。Spring AOP就是負責實施切面地框架,它將切面所定義地橫切邏輯織入切面所指定的連線點中。

SpringAOP涉及的java基礎知識

JDK動態代理

JDK的動態代理主題要涉及java.lang.reflect包下面的java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。其中,java.lang.reflect.InvocationHandler是一個介面,可以i通過實現介面定義很且邏輯,並通過反射機制呼叫目標類程式碼,動態地將橫切邏輯和業務邏輯編制在一起。Proxy利用了InvocationHandler動態建立一個符合某一介面地例項. 例子: 實現InvocationHandler

public class JdkProxy implements InvocationHandler {
	private Object target;

	public JdkProxy(Object target) {
		this.target = target;
	}

	[@Override](https://my.oschina.net/u/1162528)
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		PerformanceMonitor.begin(target.getClass().getName()+"."+method.getName());
		Object invoke = method.invoke(target, args);
		PerformanceMonitor.end();
		return invoke;
	}
}

測試程式碼: System.out.println("-------------------使用jdk動態代理------------------"); JdkProxy jdkProxy = new JdkProxy(forumService); ForumService instance = (ForumService) Proxy.newProxyInstance(Demo.class.getClassLoader(), new Class[]{ForumService.class}, jdkProxy); instance.removeForum(1); 執行結果:

CGLib動態代理

使用JDK建立地代理有一個限制,它只能位介面建立一個代理例項,這一點可以從Proxy地介面方法public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)中看出來。第二個引數就是介面需要代理例項實現的介面列表。CGLib採用了底層的位元組碼技術,可以位一個類建立一個子類,在子類中採用方法攔擊的技術攔截所以父類方法呼叫並順勢織入很且邏輯。下面採用CGLib技術編寫一個可以位任何類建立織入效能鍵事橫切邏輯代理物件的代理建立器。

例項:

public class CglibProxy implements MethodInterceptor {
	Enhancer enhancer = new Enhancer();

	public Object getProxy(Class clazz) {
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		return enhancer.create();
	}

	[@Override](https://my.oschina.net/u/1162528)
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
	  PerformanceMonitor.begin(o.getClass().getName()+"."+method.getName());
		Object invoke = methodProxy.invokeSuper(o, objects);
		PerformanceMonitor.end();
		return invoke;
	}
}

使用者通過getProxy(Class clazz)為一個類建立代理物件,該物件擴充套件clazz實現代理,在這個代理物件中,織入效能監視得橫切邏輯。intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)是CGLib定義的intercept介面方法,它攔截了所以目標類方法的呼叫。o:目標類例項,method:目標了方法的反射物件,args:方法的動態入參,proxy:代理例項。

測試程式碼:

    System.out.println("-------------------使用CGLib動態代理------------------");
    CglibProxy cglibProxy = new CglibProxy();
    ForumServiceImpl forumService1 = (ForumServiceImpl)cglibProxy.getProxy(ForumServiceImpl.class);
    forumService1.removeForum(1);

執行結果:

AOP聯盟

AOP聯盟是眾多開源開源AOP專案的聯合組織,該組織的目的是為了制定一套規範的AOP標準,定義標準的AOP介面。以便遵守標準的具體實現可以互相呼叫。

SpringAOP的增強型別

建立增強

Spring只支援方法的增強,增強既包含橫切邏輯,又包括部分連結點的資訊。

增強型別

AOP聯盟為增強定義了Advice介面,Sring支援5種類型的增強,增強的介面整合關係圖如下:

1.前置增強:org.springframework.aop.BeforeAdvice是為擴充套件而用,org.springframework.aop.MethodBeforeAdvice是目前可用的前置增強。

2.後置增強:org.springframework.aop.AfterReturningAdvice代表後置增強,表示在目標方法執行後實施增強。

3.環繞增強:org.aopalliance.intercept.MethodInterceptor代表環繞增強,表示在目標方法的執行前後實施增強。

4.異常丟擲增強:org.springframework.aop.ThrowsAdvice代表丟擲異常增強,表示在目標類種新增一些新的方法和屬性。

5.引介增強:org.springframework.aop.IntroductionInterceptor代表引介增強,表示在目標類種新增一些新的方法和屬性。

前置增強

MethodBeforeAdvice是BeforeAdvice前置的增強介面子類,BeforeAdvice存在的意義是為了後續的擴充套件。 void before(Method method, Object[] args, Object target) throws Throwable是MethodBeforeAdvice介面的唯一方法,method是目標放啊,args目標方法的引數,target為目標類例項。該方法發生異常的時候將阻止目標類方法執行。

例項: Waiter.java普通服務員

public interface Waiter {
void greetTo(String name);
void serveTo(String name);
}

PoliteWaiter.java禮貌地服務員

public class PoliteWaiter implements Waiter {
	[@Override](https://my.oschina.net/u/1162528)
	public void greetTo(String name) {
		System.out.println("greet to "+name+"....");
	}

	[@Override](https://my.oschina.net/u/1162528)
	public void serveTo(String name) {
		System.out.println("serve to "+name+"....");

	}
}

GreetBeforeAdvice.java 前置增強

public class GreetBeforeAdvice implements MethodBeforeAdvice {
	[@Override](https://my.oschina.net/u/1162528)
	public void before(Method method, Object[] objects, Object o) throws Throwable {
		String clientName = (String)objects[0];
		System.out.println("How are you! Mr."+clientName);
	}
}

測試程式碼:

    Waiter target = new PoliteWaiter();
    BeforeAdvice advice = new GreetBeforeAdvice();
    //Spring提供的代理工廠
    ProxyFactory proxyFactory = new ProxyFactory();
    //設定代理目標
    proxyFactory.setTarget(target);
    proxyFactory.addAdvice(advice);
    Waiter proxy = (Waiter) proxyFactory.getProxy();
    proxy.greetTo("John");
    proxy.serveTo("jack");

執行結果:

ProxyFactory 在上面的例子中我們使用了ProxyFactory,ProxyFactory實際上使用的是JDK和CGLib動態代理技術將增強應用到目標類中。 ![] CglibAopProxy使用的是CGLib動態代理技術,JdkDynamicAopProxy使用的是JDK動態代理技術建立代理。如果通過ProxyFactory的setInterfaces(Class[] interfaces)方法指定目標介面進行代理,就是要JdkDynamicAopProxy,如果針對的是類的代理,就使用CglibAopProxy,可以使用ProxyFactory的setOptimize(true)方法讓ProxyFactory啟動u歐化代理的方式,此時針對介面的代理也是使用CglibAopProxy。

    //使用CGLib代理的方式
    proxyFactory.setOptimize(true);

    //使用的jdk代理的方式  proxyFactory.setInterfaces(target.getClass().getInterfaces());

使用配置檔案的方式:

	<bean id="greetingAdvice" class="com.flexible.beforeadvice.GreetBeforeAdvice"></bean>
	<bean id="target" class="com.flexible.beforeadvice.PoliteWaiter"></bean>

	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
	p:proxyInterfaces="com.flexible.beforeadvice.Waiter"
	p:interceptorNames="greetingAdvice"
	p:target-ref="target">
	</bean>

測試程式碼:

	@Test
	public void testMethod1(){
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
		Waiter waiter = (Waiter) context.getBean("waiter");
		waiter.greetTo("zhangsgan ");
		waiter.serveTo("李四");
	}

ProxyFactoryBean是FactoryBean介面的實現類,它負責例項化一個Bean。ProxyFactoryBean負責為其他的Bean建立代理例項,它在內部使用ProxyFactory來完成這項工作。ProxyFactoryBean的幾個常用的可配置屬性。

1.target:代理的目標物件。

2.proxyInterfaces:代理所需要實現的介面,可以是多個介面。該屬性還有一個別名屬性interfaces

3.interceptorNames:需要織入目標物件的Bean列表,採用Bean的名曾指定,這些Bean必須實現了org.springframework.cglib.proxy.MethodInterceptor介面或者org.springframework.aop.Advisor的Bean,配置中的順序的應呼叫的順序。

4.sigleton:返回的代理是否是單例項,預設為單例項。

5.optimize:預設是false,使用的是jdk動態代理建立代理,如果設定true,強制使用CGLib動態代理,對於singleton代理,推薦使用CGLib。

6.proxyTargetClass:是否對類進行代理(而不是對介面進行代理)。設定為true時,使用的CGLib動態代理。

後置增強

後置增強在目標類方法呼叫後執行。

GreetAfterAdvice.java

public class GreetAfterAdvice implements AfterReturningAdvice {

	@Override
	public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
		System.out.println("please enjoy yourself....");
	}
}

beans.xml

	<bean id="target2" class="com.flexible.afteradvice.PoliteWaiter"></bean>
	<bean id="greetingAdvice2" class="com.flexible.afteradvice.GreetBeforeAdvice"></bean>
	<bean id="greetingAfterAdvice" class="com.flexible.afteradvice.GreetAfterAdvice"></bean>

	<bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean"
		  p:proxyInterfaces="com.flexible.afteradvice.Waiter"
		  p:interceptorNames="greetingAdvice2,greetingAfterAdvice"
		  p:optimize="true"
		  p:target-ref="target2">
	</bean>

測試程式碼:

	@Test
	public void testMethod1(){
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
		Waiter waiter = (Waiter) context.getBean("waiter2");
		waiter.greetTo("zhangsgan ");
		waiter.serveTo("李四");
	}

執行結果:

How are you! Mr.zhangsgan greet to zhangsgan .... please enjoy yourself.... How are you! Mr.李四 serve to 李四.... please enjoy yourself....

環繞增強

環繞增強允許在目標類方法呼叫前後織入橫切邏輯,綜合了前置和後置增強功能。

例子: GreetingInterceptor.java

public class GreetingInterceptor implements MethodInterceptor {
	@Override
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		Object[] args =methodInvocation.getArguments();
		String clientNme = (String) args[0];
		System.out.println("How are you Mr."+clientNme+"....");
		Object proceed = methodInvocation.proceed();
		System.out.println("Please enjoy yourself....");
		return proceed;
	}
}

bean.xml

	<bean id="target3" class="com.flexible.aroundadvice.PoliteWaiter"></bean>
	<bean id="greetingAdvice3" class="com.flexible.aroundadvice.GreetingInterceptor"></bean>

	<bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean"
		  p:proxyInterfaces="com.flexible.aroundadvice.Waiter"
		  p:interceptorNames="greetingAdvice3"
		  p:target-ref="target3">
	</bean>

測試程式碼:

	@Test
	public void testMethod1(){
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
		Waiter waiter = (Waiter) context.getBean("waiter3");
		waiter.greetTo("zhangsgan ");
		waiter.serveTo("李四");
	}

測試結果

How are you Mr.zhangsgan ....
greet to zhangsgan ....
Please enjoy yourself....
How are you Mr.李四....
serve to 李四....
Please enjoy yourself....

異常丟擲增強

異常丟擲增強最適合的應用場景時事務管理,當參與事務的某個DAO發生異常時,事務管理器就必須回滾事務。要定義異常增強需要實現業務ThrowsAdvice增強,該介面只是一個標籤介面,在執行期間,Spring使用反射機制自行判斷,必須使用下面的形式定義方法

public void afterThrowing(Method method,Object[] args,Object target,Exception ex){

PayService.java 業務類 /** * 模擬業務的類,支付的時候丟擲異常 */ public class PayService {

	public void payMony(String from, double money, String to) {
	//TODO -- TODO some thing
		throw new RuntimeException("測試丟擲執行時異常....");
	}
}

beans.xml 配置

<bean id="transactionManager" class="com.flexible.throwadvice.TransactionManager"></bean>
<bean id="payTarget" class="com.flexible.throwadvice.PayService"></bean>

<bean id="ins" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:interceptorNames="transactionManager"
      p:target-ref="payTarget"
      p:proxyTargetClass="true">
</bean>

測試程式碼:

@Test
public void testMethod1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
    PayService payService = (PayService) context.getBean("ins");
    payService.payMony("1",100.00,"2");
}

測試結果:


method:payMony 丟擲異常:測試丟擲執行時異常.... 成功回滾事務

引介增強

引介增強時一種比較特殊的增強型別,它不是在目標方法增強,而是為目標類建立新的方法和屬性,所以介增強的連線點是類級別的。而非方法級別的。通過引介增強,可以為目標類新增一個介面的實現,即原來的目標類的為實現某個介面,通過引介增強可以為目標了常見實現某個介面的代理。Spring 定義了引介增強介面IntroductionInterceptor,該介面沒有定義任何方法,Spring為該介面提供了DelegatingIntroductionInterceptor實現類。一般情況下,通過擴充套件該實現類定義自己引介增強類。

建立切面

如果我們需要有選擇地織入目標類地某些特定地方法中,就需要使用切點進行目標連線點的定位。描述連繫欸但是進行AOP程式設計最主要的工作。增強的提供了連繫欸但方法為資訊,切點進一步的描述了織入哪些類的哪些方法上。 Spirng通過org.springframework.aop.Pointcut介面秒速和切點,其中org.springframework.aop.Pointcut是由ClassFilter和MethodMatcher構成,通過ClassFilter定位到某些特定類上,通過MethodMatcher定位到某些特定方法上。這樣org.springframework.aop.Pointcut就能夠定位某些類的某些方法上的能力。org.springframework.aop.Pointcut的關係結構圖如下:

ClassFilter只定義了一個方法matcher(Class clazz),其引數代表一個被檢測類,改方法判別被u檢測的類是否匹配過濾條件。 Spring支援兩種匹配器:

1.靜態匹配器(僅需要匹配一次,對方法簽名進行匹配)

2.動態匹配器(因為可能每次入參都不一樣,需要每次呼叫都檢測,影響效能,一般不常使用),方法匹配器的型別由isRuntime()方法返回值決定,false表示靜態匹配器,true表示動態匹配器。

切點的型別

1.靜態方法切點:org.springframework.aop.support.StaticMethodMatcherPointcut是靜態方法切點的抽象類,預設情況下它匹配所以的類。org.springframework.aop.support.StaticMethodMatcherPointcut的子類有:org.springframework.aop.support.NameMatchMethodPointcut和org.springframework.aop.support.AbstractRegexpMethodPointcut,NameMatchMethodPointcut提供簡單字串匹配簽名,AbstractRegexpMethodPointcut使用正則表示式匹配放啊簽名。

2.動態方法切點:org.springframework.aop.support.DynamicMethodMatcherPointcut是動態方法切點的抽象基類,預設情況下匹配所有類。

3.註解切點:org.springframework.aop.support.annotation.AnnotationMatchingPointcut實現類表示註解其欸但。使用AnnotationMatchingPointcut支援在BEAN中直接通過Java5.0註解標籤定義的切點。

4.表示式切點:org.springframework.aop.support.ExpressionPointcut介面主要為了支援AspectJ切點表示式語法而定義的介面。

5.流程切點:org.springframework.aop.support.ControlFlowPointcut實現類表示控制流程切點。ControlFlowPointcut是一種特殊的的切點,它根據程式執行堆疊資訊的檢視目標方法是否由某一個方法直接或者見解的發起呼叫,以此判斷是否為匹配的連線點。

6.複合切點:org.springframework.aop.support.ComposablePointcut實現類是為了建立多個切點而提供的方便操作類。它所有的方法都返回ComposablePointcut類,這樣就可以使用連結表示式對切點進行操作:Pointcut pc = new ComposablePointcut().union(classFilter).intersetion(methodMatcher).intersetion(pointcut).

切面的型別

由於增強既包含橫切程式碼,又包含部分連線點的部分資訊(方法前,方法後主方為資訊),可以進通過增強類生成一個切面。但是切點僅代表目標類連繫欸但那的部分資訊(類和方法的定位),所有僅有切點無法制作出一個切面,必須結合增強才能製作出切面。Spring使用org.springframework.aop.Advisor介面比埃是切面的概念,一個切面同時包含橫切程式碼和連線點資訊。切面可以分為三類:

1.Advisor:代表一般切面,僅包含一個Advice。因為Advice包含了橫切程式碼和連線點資訊,所有Advice本身就是一個簡單的切面,它代表的橫切的連線點是所有目標類的所有方法,因為這個橫切面很寬泛,一般情況下不使用。

2.PointcutAdvisor:代表具有切點的切面,包含Advice和Pointcut兩個類,這樣就可以通過類,方法名及方法方位等資訊靈活的定義切面的連結點,提供更具使用行的切面。

PointcutAdvisor主要有6個具體的實現類。 1.DefaultPointcutdvisor:最常用的切面型別,它可以通過任意Pointcut和Advice定義一個切面,唯一不支援的是引介的切面型別,一般可以通過擴充套件改類實現自定義的的切面。

     2.NameMatchMethodPointcutAdvisor:通過類可以定義按方法名定義切點的切面。

     3.RegexpMethodPointcutAdvisor:對於按正則表示式匹配方法名進行切點定義的切面,可以通過擴充套件改實現類進行操作。其內部是通過jdkRegexpMethodPoint構造出正則表示式方法名稱切點。

     4.StaticMethodMatcherPointcutAdvisor:靜態方法匹配切點定義的切面,預設情況下匹配所有的目標類。

    5.AspectJExpressionPointcutAdvisor:用於AspectJ切點表示式定義切點的切面。

    6.AspectJPointcutAdvisor:用於AspectJ語法定義切點的切切面。

3.IntroductionAdvisor:代表引介切面。引介切面是對應引介增強的特殊的切面,它應用於類層面上,所有切點使用ClassFilter進行定義。

靜態普通方法名匹配切面

StaticMethodMatcherPointAdvisor代表一個靜態方法匹配切面,它通過StaticMethodMatcherPointcut來定義切點,並通過類過濾和方法名來匹配所定義的切點。 例子: Waiter.java

public class Waiter {
void greetTo(String name){
	System.out.println("Waiter greet to "+name+"...");
}
void serveTo(String name){
	System.out.println("waiter sering "+name+"...");
}
}

Seller.java

public class Seller {
public void greetTo(String name){
	System.out.println("Waiter greet to "+name+"...");
}
}

GreetBeforeAdvice.java(前置增強)

public class GreetBeforeAdvice implements MethodBeforeAdvice {
	@Override
	public void before(Method method, Object[] objects, Object o) throws Throwable {
		String clientName = (String)objects[0];
		System.out.println("How are you! Mr."+clientName);
	}
}

GreetingAdvisor.java(切面匹配,這裡限制只能匹配Waiter的greetTo())

public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		return "greetTo".equals(method.getName());
	}
	public ClassFilter getClassFilter() {
		return new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return Waiter.class.isAssignableFrom(clazz);
			}
		};
	}
}

beans.xml

<!--普通靜態方法切面匹配-->

<bean id="waiterTarget" class="com.flexible.advisormatch.staticmethodadvisormatch.Waiter"></bean>
<bean id="sellerTarget" class="com.flexible.advisormatch.staticmethodadvisormatch.Seller"></bean>

<bean id="advisormatchGreetingAdvice"
      class="com.flexible.advisormatch.staticmethodadvisormatch.GreetBeforeAdvice"></bean>

<bean id="greetAdvisor" class="com.flexible.advisormatch.staticmethodadvisormatch.GreetingAdvisor"
      p:advice-ref="advisormatchGreetingAdvice"></bean>  <!--這裡注入一個前置切面-->
<!--通過一個父類定義一個公共的配置資訊-->
<bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:interceptorNames="greetAdvisor"
      p:proxyTargetClass="true"
></bean>
<bean id="advisormatchWaiter" parent="parent" p:target-ref="waiterTarget"></bean>
<bean id="advisormatchSeller" parent="parent" p:target-ref="sellerTarget"></bean>

測試程式碼:

    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
    Waiter waiter = (Waiter) context.getBean("advisormatchWaiter");
    Seller seller = (Seller) context.getBean("advisormatchSeller");
    waiter.greetTo("zhangsan");
    waiter.serveTo("zhangsan");
    seller.greetTo("李四");

執行結果:

靜態正則表示式方法匹配切面

RegexpMethodPointcutAdvisor是正則表示式方法匹配的切面實現類,改類已經是功能齊全的實現類,可以直接拿來使用。

例子: 與上一個例子差別主要再配置檔案,beans.xml配置檔案的內容如下:

<bean id="waiterTarget2" class="com.flexible.advisormatch.regexpadvisormatch.Waiter"></bean>
<bean id="sellerTarget2" class="com.flexible.advisormatch.regexpadvisormatch.Seller"></bean>

<bean id="advisormatchGreetingAdvice2"
      class="com.flexible.advisormatch.regexpadvisormatch.GreetBeforeAdvice"></bean>

<bean id="greetAdvisor2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
      p:advice-ref="advisormatchGreetingAdvice2">
    <property name="patterns">
        <list>
            <value>.*greet.*</value>
        </list>
    </property>
</bean>
<!--通過一個父類定義一個公共的配置資訊-->
<bean id="parent2" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:interceptorNames="greetAdvisor2"
      p:proxyTargetClass="true"
></bean>
<bean id="advisormatchWaiter2" parent="parent2" p:target-ref="waiterTarget2"></bean>
<bean id="advisormatchSeller2" parent="parent2" p:target-ref="sellerTarget2"></bean>

測試執行結果:

動態切面

DynamicMethodMatcherPointcut抽象類,是Spring用於建立動態切面你的抽象類,改出響雷預設匹配所有的類和方法,因此預設需要擴充套件該類編寫符合要求的動態的切點。 例子: GreetingDynamicPointcut.java

public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {
	private static List<String> specialClientList = new ArrayList<>();
	static {
		specialClientList.add("John");
		specialClientList.add("Tom");
	}
	//    對類進行靜態切點檢查
	@Override
	public ClassFilter getClassFilter() {
		return new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				System.out.println("呼叫getClassFilter()對" + clazz.getName() + "做靜態檢查.");
				return Waiter.class.isAssignableFrom(clazz);
			}
		};
	}
	//    對方法進行靜態切點檢測
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		return super.matches(method, targetClass);
	}

	// 對方法進行動態切點檢測
	@Override
	public boolean matches(Method method, Class<?> targetClass, Object... args) {
		System.out.println("呼叫matches(Method method, Class<?> targetClass, Object... args)"
				+ targetClass.getName() + "." + method.getName()+"做動態檢測");
		String clientName = (String) args[0];
		return specialClientList.contains(clientName);
	}
}

beans.xml

	<bean id="waiterTarget3" class="com.flexible.advisormatch.dynamicmatch.Waiter"></bean>
	<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="pointcut">
			<bean class="com.flexible.advisormatch.dynamicmatch.GreetingDynamicPointcut"></bean>
		</property>
		<property name="advice">
			<bean class="com.flexible.advisormatch.dynamicmatch.GreetBeforeAdvice"></bean>
		</property>
	</bean>
	<bean id="waiter4" class="org.springframework.aop.framework.ProxyFactoryBean"
		  p:interceptorNames="dynamicAdvisor"
		  p:target-ref="waiterTarget3"
		  p:proxyTargetClass="true"></bean>

測試程式碼:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
		Waiter waiter = (Waiter) context.getBean("waiter4");
		waiter.greetTo("John");
		waiter.greetTo("John");
		waiter.serveTo("zhangsan");
		waiter.serveTo("zhangsan");

執行結果:

從執行的結果可以看出,Spring會建立代理織入切面時,對目標類中的所有方法進行了靜態切點檢查;生成了的植入切面的代理物件後,第一次呼叫代理類的每一個方法都會進行一次靜態的檢查,如果本次檢查就能從候選者列表中將該方法排除,則以後對該方法的呼叫就不再執行靜態檢查;對於靜態匹配的方法,後續的呼叫該方法都會進行動態的檢查。

流程切面

Spring 的流程切面由DefaultPointcutAdvisor和ControlPointcut是實現。流程切點代表由某個方法直接或者簡潔的發起呼叫的其他方法。

例子: WaiterDelegate.java

public class WaiterDelegate {

	private Waiter waiter;

	/**
	 * 該方法發起呼叫的其他方法都織入GreetBeforeAdvice增強,
	 * 要完成該功能就需要使用到流程切面。
	 *
	 * @param clientName
	 */
	public void service(String clientName) {
		waiter.greetTo("service" + clientName);
		waiter.serveTo("service" + clientName);
	}

	public void setWaiter(Waiter waiter) {
		this.waiter = waiter;
	}
}

beans.xml

	<!--流程切面-->
	<bean id="controlFlowGreetingAdvice" class="com.flexible.advisormatch.controflowmatch.GreetBeforeAdvice"></bean>

	<bean id="controlFlowWaiterTarget" class="com.flexible.advisormatch.controflowmatch.Waiter"></bean>

	<bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
		<constructor-arg type="java.lang.Class"
						 value="com.flexible.advisormatch.controflowmatch.WaiterDelegate"></constructor-arg>
		<constructor-arg type="java.lang.String" value="service"></constructor-arg>
	</bean>

	<bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
		  p:pointcut-ref="controlFlowPointcut"
		  p:advice-ref="controlFlowGreetingAdvice">
	</bean>

	<bean id="controlFlowWaiter1" class="org.springframework.aop.framework.ProxyFactoryBean"
		  p:interceptorNames="controlFlowAdvisor"
		  p:target-ref="controlFlowWaiterTarget"
		  p:proxyTargetClass="true">
	</bean>

測試程式碼:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
		Waiter waiter = (Waiter) context.getBean("controlFlowWaiter1");
		WaiterDelegate delegate = new WaiterDelegate();
		delegate.setWaiter(waiter);
		waiter.greetTo("zhangsan");
		waiter.serveTo("zhangsan");
		delegate.service("zhangsan");

執行結果:

複合切點切面

如果一個切點難以描述目標連線點的資訊,比如再前面流程切面的例子中,在上面的例子中,如果呼叫service(String clientName)就會發現該方法呼叫的兩個方法都會織入增強,如果只希望增強其中一個,那麼這個切點就是符合切點。

例子:

GreetingComposablePointcut.java

public class GreetingComposablePointcut {
	public Pointcut getIntersectionPointcut(){
		ComposablePointcut cp = new ComposablePointcut();
		Pointcut pt1 = new ControlFlowPointcut(WaiterDelegate.class,"service");
		NameMatchMethodPointcut pt2 = new NameMatchMethodPointcut();
		pt2.addMethodName("greetTo");
		return cp.intersection(pt1).intersection((Pointcut)pt2);
	}
}

beans.xml

<!--複合切點-->
	<bean id="composableGreetingAdvice" class="com.flexible.advisormatch.composablepointcutmatch.GreetBeforeAdvice"></bean>
	<bean id="gcp" class="com.flexible.advisormatch.composablepointcutmatch.GreetingComposablePointcut"></bean>
	<bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
	p:pointcut="#{gcp.intersectionPointcut}"
	p:advice-ref="composableGreetingAdvice"
	></bean>
	<bean id="composableWaiterTarget" class="com.flexible.advisormatch.composablepointcutmatch.Waiter"></bean>
	<bean id="composableProxyWaiter" class="org.springframework.aop.framework.ProxyFactoryBean"
	p:interceptorNames="composableAdvisor"
	p:target-ref="composableWaiterTarget"
	p:proxyTargetClass="true"></bean>

測試程式碼:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
		Waiter waiter = (Waiter) context.getBean("composableProxyWaiter");
		WaiterDelegate delegate = new WaiterDelegate();
		delegate.setWaiter(waiter);
		waiter.serveTo("zhansagn");
		waiter.greetTo("zhangsan");
		delegate.service("zhangsgan");

執行結果:

自動建立代理

之前的例子都是通過ProxyFactoryBean建立織入切面的代理,每個需要被代理的Bean都需要配置,這樣就會很麻煩,因此Spring提供了自動代理的機制,讓容器自動生成代理,把開發從繁瑣的配置工作中解放出來。而這些都是Spring內部使用BeanPostProcessor自動完成這項工作。

BaeanPostProcessor實現類的介紹

基於BeanPostProcessor自動代理建立器的實現類有三類: 1.BeanNameAutoProxyCreator:基於Bean配置名規則的自動代理建立器,允許為一組特定的配置名的Bean自動建立代理例項的代理建立器。

例子:

beans.xml

<!--BeanNameAutoProxyCreator自動建立代理類-->
<bean id="autoWaiter" class="com.flexible.autoprxycreator.Waiter"></bean>
<bean id="autoSeller" class="com.flexible.autoprxycreator.Seller"></bean>
<bean id="autoGreetAdvice" class="com.flexible.autoprxycreator.GreetingBeforeAdvice"></bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
//這裡可以配置多個需要代理的baean
p:beanNames="autoWaiter,autoSeller"
p:interceptorNames="autoGreetAdvice"
//p:optimize="true"需要配置,實現使用CGLib生成代理
p:optimize="true"></bean>

測試程式碼:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:proxyfactory/beans");
		Waiter waiter = (Waiter) context.getBean("autoWaiter");
		Seller seller = (Seller) context.getBean("autoSeller");
		waiter.greetTo("John");
		seller.greetTo("Tom");

執行結果:

com.flexible.autoprxycreator.Waiter.greetTo How are you!Mr.John. waiter greet to John... com.flexible.autoprxycreator.Seller.greetTo How are you!Mr.Tom. seller greet to Tom...

2.DefaultAdvisorAutoProxyCreator:基於Advisor匹配機制的自動代理建立器,他會對容器中的所有的Advisor進行掃描,自動將這些切面應用到匹配的Bean中(為目標Bean建立代理例項)

beans.xml配置

	<bean id="autoWaiter2" class="com.flexible.autoprxycreator.defaultautoproxycreator.Waiter"></bean>
	<bean id="autoSeller2" class="com.flexible.autoprxycreator.defaultautoproxycreator.Seller"></bean>
	<bean id="autoGreetAdvice2" class="com.flexible.autoprxycreator.defaultautoproxycreator.GreetingBeforeAdvice"></bean>
	<bean id="regexpMethodPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
		  p:advice-ref="autoGreetAdvice2">
		<property name="patterns">
			<list>
				<value>.*greet.*</value>
			</list>
		</property>
	</bean>
	<!--會開啟掃描所有的切面 這個地需要配置使用CGLib動態代理-->
	<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  p:proxyTargetClass="true"></bean>

測試程式碼:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:autoproxycreator/beans");
		Waiter waiter = (Waiter) context.getBean("autoWaiter2");
		Seller seller = (Seller) context.getBean("autoSeller2");
		waiter.greetTo("John");
		seller.greetTo("Tom");

執行結果:

com.flexible.autoprxycreator.defaultautoproxycreator.Waiter.greetTo How are you!Mr.John. waiter greet to John... com.flexible.autoprxycreator.defaultautoproxycreator.Seller.greetTo How are you!Mr.Tom. seller greet to Tom...

3.AnnotationAwareAspectJAutoProxyCreator:基於Bean中AspectJ註解標籤的自動代理建立器,為包含AspectJ註解的Bean自動建立代理例項。

Spring AOP的切面型別

通過自動代理技術建立切面