Spring AOP 剖析(5)

在動態代理 和 CGLIB 的支持下, Spring AOP 框架的實現經過了兩代。

 

從 Spring AOP 框架第一次發佈,到 Spring 2.0 發佈之前的 AOP 實現,是 Spring 第一代 AOP 實現。

 

Spring 2.0 發佈後的 AOP 實現是第二代。

 

但是,Spring AOP 的底層實現機制一直沒有變,唯一改變的,是各種 AOP 概念實現的表現形式以及 Spring AOP  的使用


方式。

 

 

Spring AOP 的底層實現機制

 

 

1. Spring AOP 中的 Joinpoint

 

Spring AOP 中,僅支持方法級別的 Joinpoint ,更確切的說,只支持方法執行 (Method Execution )類型的 Joinpoint

 

雖然 Spring AOP 僅提供方法攔截,但是實際的開發過程中,這已經可以滿足 80% 的開發需求了。Spring AOP 之所以

 

如此,主要有以下幾個原因。

 

a.  Spring AOP 要提供一個簡單而強大的 AOP 框架,並不想因大而全使得框架本身過於臃腫。能夠僅付出 20% 的

 

努力,就能夠得到 80% 的回報。否則,事倍功半,並不是想看到的結果。

 

b.  對於類中屬性 (Field )級別的 Joinpoint ,如果提供這個級別的攔截,那麼就破壞了面向對象的封裝,而且,完全

 

可以通過 setter 和 getter 方法的攔截達到同樣的目的。

 

c.   如果應用需求非常特殊,完全超出了 Spring AOP 提供的那 80% 的需求支持,可以求助於現有其他 AOP 實現產品,

 

如 AspectJ。 目前看來, AspectJ 是 Java 平臺對 AOP 支持最完善的產品,同時,Spring AOP 也提供了對 Aspect

 

的支持。

 

 

 

2.  Spring AOP 中的 Pointcut

 

Spring 中以接口 org.springframework.aop.Pointcut 作爲其 AOP 框架中的所有 Pointcut 的最頂層抽象。

 

package org.springframework.aop;

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	Pointcut TRUE = TruePointcut.INSTANCE;
}

 

ClassFilter  和  MethodMatcher 分別用於匹配被執行織入操作的對象以及相應的方法。

 

 

a.  ClassFilter

 

ClassFilter 接口的作用是對 Joinpoint 所處的對象進行 Class 級別的類型匹配。

 

package org.springframework.aop;

public interface ClassFilter {

	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

 

當織入的目標對象的 Class 類型與 Pointcut 所規定的類型相符時,matchers 方法將會返回 true,否則返回 false。

 

即意味着不會對這個類型的目標對象進行織入操作。比如,如果僅希望對系統中的 Foo 類型的對象執行織入,則可以

 

package prx.aop.proxy;

import org.springframework.aop.ClassFilter;

public class FooClassFilter implements ClassFilter{

	public boolean matches(Class<?> clazz) {
		return Foo.class.equals(clazz);
	}

}

 

如果類型對所捕捉的 Joinpoint 無所謂,那麼 Pointcut 中使用的 ClassFilter 可以直接使用

 

ClassFilter TRUE = TrueClassFilter.INSTANCE 。

 

當 Pointcut 中返回的 ClassFilter 類型爲該類型實例時,Pointcut 的匹配將會針對系統中所有類的實例。

 


b.  MethodMatcher

 

MethodMatcher 接口的作用是描述 Pointcut 中 Method Execution 的 Joinpoint 的集合。即對象中的方法是否匹配

 

該 Pointcut 的而需要攔截。也就是 Spring 主要支持的 方法級別的攔截 的依據。

 

package org.springframework.aop;

import java.lang.reflect.Method;


public interface MethodMatcher {

	boolean matches(Method method, Class<?> targetClass);

	boolean isRuntime();

	boolean matches(Method method, Class<?> targetClass, Object[] args);

	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}
 

MethodMatcher 通過重載,定義了兩個 matches 方法,而這兩個方法的分界線就是 isRuntime 方法。

 

兩個 mathcers 方法的區別在於:

 

在進行方法攔截的時候,可以選擇忽略方法執行時的傳入參數,也可以每次都檢查方法執行時的傳入參數。

 

 

比如:現在要對 登錄方法 login(String username, String passwod) 進行攔截

 

1. 只想在 login 方法之前插入計數功能,那麼 login 方法的參數對於 Joinpoint 捕捉就是可以忽略的。

 

2. 在用戶登錄的時候對某個用戶做單獨處理(拒絕登錄 或 給予特殊權限),那麼方法的參數在匹配 Joinpoint 時必須要考慮到

 

(1).  StaticMethodMatcher

 

前一種情況下, isRuntime 返回 false , 表示不會考慮具體 Joinpoint 的方法參數, 這種類型的 MethodMatcher

 

稱之爲 StaticMethodMatcher。因爲不用每次都檢查參數,那麼對於同樣的類型的方法匹配結果,就可以在框架內部

 

緩存以提高性能。isRuntime 方法返回 false 表明當前的 MethodMatcher 爲 StaticMethodMatcher 的時候, 只有

 

boolean matches(Method method, Class<?> targetClass);

 方法將被執行, 它的匹配結果將會成爲其所屬的 Pointcut 主要依據。

 

package org.springframework.aop.support;

import java.lang.reflect.Method;

import org.springframework.aop.MethodMatcher;

public abstract class StaticMethodMatcher implements MethodMatcher {

	public final boolean isRuntime() {
		return false;
	}

	public final boolean matches(Method method, Class<?> targetClass, Object[] args) {
		// should never be invoked because isRuntime() returns false
		throw new UnsupportedOperationException("Illegal MethodMatcher usage");
	}

}
 

(2). DynamicMethodMatcher

 

當 isRuntime 方法返回 true 時, 表明 MethodMatcher 將會每次都對方法調用的參數進行匹配檢查,這種類型的

 

MethodMatcher 稱之爲 DynamicMethodMatcher。 因爲每次都要對方法參數進行檢查,無法對匹配結果進行緩存,

 

所以,匹配效率相對 StatisMethodMatcher 來說要差。

 

大部分情況下, StaticMethodMatcher 已經夠用了,最好避免使用 DynamicMethodMatcher 類型。

 

如果一個 MethodMatcher 爲 DynamicMethodMatcher , 那麼只有 isRuntime 返回 true, 而且

 

matchers(Method method, Class targetClass) 也返回 true 的時候, 三個參數的 matchers 方法將被執行,進行

 

進一步檢查匹配條件。否則不會執行 三個參數的 matchers 方法,直接返回 false 了。

 

package org.springframework.aop.support;

import java.lang.reflect.Method;

import org.springframework.aop.MethodMatcher;

public abstract class DynamicMethodMatcher implements MethodMatcher {

	public final boolean isRuntime() {
		return true;
	}

	/**
	 * Can override to add preconditions for dynamic matching. This implementation
	 * always returns true.
	 */
	public boolean matches(Method method, Class<?> targetClass) {
		return true;
	}

}
 

在 MethodMatcher 類型的基礎上, Pointcut 可以分爲兩類, 即 StaticMethodMatcherPointcut 和 DynamicMethodMatcherPointcut。

 

因爲 StaticMethodMatcherPointcut 具有明顯的性能優勢, 所以, Spring 爲其提供了更多的支持。

 

Spring 中 Pointcut  局部類結構圖

 


 

部分資料說明  AbstractRegexpMethodPointcut 還有個 子類  Perl5RegexpMethodPointcut,但是我在

 

Spring 3.0.5 包中沒有看到此類。

 

從上圖中看出 Spring 提供了集中常見的 Pointcut 實現 (淺紅色的類圖 )。


1.  NameMatchMethodPointcut

 

最簡單的 Pointcut 實現,根據自身指定的一組方法名稱與 Joinpoint 處的方法的名稱進行匹配,支持“*”通配符實現簡單

 

的模糊匹配。

 

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();

pointcut.setMappedName("methodName");

pointcut.setMappedNames(new String[]{"methodName1", "methodName2"});

pointcut.setMappedNames(new String[]{"method*", "*Name", "method*Num"});

 

但是, NameMatchMethodPointcut 無法對重載的方法名進行匹配, 因爲它僅對方法名匹配,不考慮參數信息。

 


2.  JdkRegexpMethodPointcut

 

StaticMethodMatcherPointcut 中正則表達式的分支實現,基於 JDK1.4 之後引入的 JDK 標準正則表達式。

 

JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();

pointcut.setPattern(".*method.*");

pointcut.setPatterns(new String[]{".*method.*", ".*name.*"});

 

注意:使用正則表達式來匹配對應的 Joinpoint 所處的方法時, 正則表達式的匹配模式必須以匹配整個方法簽名的形式


指定,而不能像 NameMatchMethodPointcut 那樣僅給出匹配的方法名稱。

 

 

package prx.aop.proxy;

public class Foo {
	public void doSomething() {
		
	}
}

 

如果使用正則表達式  .*doS.* 則會匹配 Foo 的 doSomething  方法, 即完整簽名:

 

prx.aop.proxy.Foo.doSomething  。 但是如果 Pointcut 使用 doS.* 作爲匹配的正則表達式模式,就無法捕捉到

 

Foo 的 doSomething 方法的執行。

 


3.  AnnotationMatchingPointcut

 

根據目標對象中是否存在指定類型的註解來匹配 Joinpoint , 只能使用在 JDK5 或更高版本中, 因爲註解是 JDK5

 

發佈後纔有的。

 

AnnotationMatchingPointcut 根據目標對象中是否存在指定類型的註解來匹配 Joinpoint ,要使用該類型的 Pointcut

 

首先需要聲明相應的註解。

 

例如:

 

package prx.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassLevelAnnotation {

}

 

package prx.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation {

}

 

註解定義中的 @Target 指定註解可以標註的類型。 ClassLevelAnnotation用於類層次,MethodLevelAnnotation

 

用於方法層次。

 

不同的 AnnotationMatchingPointcut 的定義方式會產生不同的匹配行爲。

 

//僅指定類級別的註解, 標註了 ClassLevelAnnotation 註解的類中的所有方法執行的時候,將全部匹配。
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);

//還可以使用靜態方法創建 pointcut 實例
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);


//僅指定方法級別的註解,標註了 MethodLeavelAnnotaion 註解的方法(忽略類匹配)都將匹配
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);


//同時限定類級別和方法級別的註解,只有標註了 ClassLevelAnnotation 的類中 同時標註了 MethodLevelAnnotation 的方法纔會匹配
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);

 

 講了這麼多,比較空泛了, 來個簡單的實際例子看下: (註解的定義延用上面的代碼)

 

package prx.aop.annotation;

@ClassLevelAnnotation
public class TargetObject {

	@MethodLevelAnnotation
	public void method1() {
		System.out.println("target : method1");
	}
	
	public void method2() {
		System.out.println("target : method2");
	}
}
package prx.aop.annotation;

import java.lang.reflect.Method;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;

public class Client {

	public static void main(String[] args) {	
		//pointcut 定義, 匹配方式可以按上面的說明修改,  這裏是註解類的所有方法都匹配
		AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
		
		// advice 定義, 根據前面的介紹知道 這個是 橫切邏輯的定義, 這裏是 方法執行前插入橫切邏輯
		BeforeAdvice advice = new MethodBeforeAdvice() {
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");
			}	
		};
		
		// Spring 中的 Aspect , pointcut 和 advice 的封裝類
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setPointcut(pointcut);
		advisor.setAdvice(advice);
		
		// Spring 基本織入器 weaving 和 weaver
		ProxyFactory weaver = new ProxyFactory();
		weaver.setTarget(new TargetObject());	//指定代理目標對象
		weaver.addAdvisor(advisor);				//指定 Aspect
		
		Object proxyObject = weaver.getProxy();	//生成代理對象 (這裏沒接口, Spring 使用 CGLIB 創建子類)
		
		((TargetObject) proxyObject).method1();
		((TargetObject) proxyObject).method2();
	}
}
 

 


4.   ComposablePointcut

 

ComposablePointcut 是 Spring AOP 提供的可以進行 Pointcut 邏輯運算的 Pointcut 實現, 它可以進行 Pointcut

 

之間的 “並” 以及 “交” 運算。

 

ComposablePointcut pointcut1 = new ComposablePointcut(classFilter1, methodMatcher1);
ComposablePointcut pointcut2 = new ComposablePointcut(classFilter2, methodMatcher2);

//求並集
ComposablePointcut unitedPointcut 		= pointcut1.union(pointcut2);
//求交集
ComposablePointcut intersectionPointcut	= pointcut1.intersection(unitedPointcut);

assertEquals(pointcut1, intersectionPointcut);

 

同時, Spring AOP 還提供了 工具類: org.springframework.aop.support.Pointcuts

 

Pointcut pointcut1 = ...;
Pointcut pointcut2 = ...;

//求並集
Pointcut unitedPointcut = Pointcuts.union(pointcut1, pointcut2);
//求交集
Pointcut intersectionPointcut = Pointcuts.intersection(pointcut1, unitedPointcut);

assertEquals(pointcut1, intersectionPointcut);
 


5.   ControlFlowPointcut

 

非常有個性的 Pointcut 類型, 不是很常用。 指定只有當 Joinpoint 指定的某個方法 在 某個特定的 類中被調用時,才

 

對其進行攔截。而一般情況是,Joinpoint  指定的方法,無論被誰調用,都會被攔截。

 

package prx.aop.controlflow;

public class TargetObject {

	public void method1() {
		System.out.println("TargetObject : method1");
	}
}

 

package prx.aop.controlflow;

public class TargetCaller {
	
	private TargetObject target;
	
	public void callMethod() {
		target.method1();
	}
	
	public void setTarget(TargetObject target) {
		this.target = target;
	}
}

 

package prx.aop.controlflow;

import java.lang.reflect.Method;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class Client {

	public static void main(String[] args) {	
		//該 Pointcut 表示 Joinpoint 指定的方法 只有在 TargetCaller 類中被調用,才能攔截織入橫切邏輯
		ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
		
		// advice 定義, 根據前面的介紹知道 這個是 橫切邏輯的定義, 這裏是 方法執行前插入橫切邏輯
		BeforeAdvice advice = new MethodBeforeAdvice() {
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");
			}	
		};
		
		// Spring 中的 Aspect , pointcut 和 advice 的封裝類
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setPointcut(pointcut);
		advisor.setAdvice(advice);
		
		// Spring 基本織入器 weaving 和 weaver
		ProxyFactory weaver = new ProxyFactory();
		weaver.setTarget(new TargetObject());	//指定代理目標對象
		weaver.addAdvisor(advisor);				//指定 Aspect
		
		Object proxyObject = weaver.getProxy();	//生成代理對象 (這裏沒接口, Spring 使用 CGLIB 創建子類)
	
		//直接調用 method1 不會觸發橫切邏輯執行
		((TargetObject) proxyObject).method1();

		System.out.println("-----------------");
		
		//advice的邏輯在這裏才能被觸發執行
		//因爲 TargetCaller 的 callMethod() 將調用 method1
		TargetCaller caller = new TargetCaller();
		caller.setTarget((TargetObject)proxyObject);
		caller.callMethod();

	}
}

 

如果在 ControlFlowPointcut 的構造方法中單獨指定 Class 類型的參數,如上面的例子,那麼 ControlFlowPointcut

 

將嘗試匹配指定的 Class 中聲明的所有方法,跟目標對象的 Joinpoint 處的方法流程組合。 所以,如果是想要做到

 

“只有 TargetCaller 類的 callMethod 方法調用 TargetObject.method1() 才攔截,而 TargetCaller 的其他方法

 

全都忽略” 的話,可以在構造時,傳入第二個參數

 

ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class, "callMethod");

 

因爲 ControlFlowPointcut 類型的 Pointcut 需要在運行期間檢查程序的調用棧,而且每次方法調用都需要檢查,所以


性能比較差,應該儘量避免使用。

 

 

太長了。。。下回待續。O(∩_∩)O~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章