SPRING AOP深刻理解

本文相關代碼(來自官方源碼spring-test模塊)請參見spring-demysify org.springframework.mylearntest包下。

統稱可以實現AOP的語言爲AOL,即(Aspect-Oriented Language),其餘Aspectj

  • AspectC
  • AspectC++
  • Aspect.Net
  • AspectL(Lisp)
  • AspectPHP
  • ......

JAVA中AOP實現方式java

  1. 動態代理git

    • 在運行期間,爲響應的接口動態生成對應的代理對象,將橫切關注點邏輯封裝到動態代理的InvocationHandler中,而後在系統運行期間,根據橫切關注點須要織入的模塊位置,將橫切邏輯織入到相應的代理類中。
  2. 動態字節碼加強github

    • 使用ASM或者CGLIB等Java工具庫,在程序運行期間,動態構建字節碼的class文件。
    • 在運行期間經過動態字節碼加強技術織入橫切邏輯,爲這些系統模塊類生成相應的子類,而將橫切邏輯加到這些子類中,讓應用程序的執行期間使用的是這些動態生成的子類,從而達到將橫切邏輯織入系統的目的。
    • 若是須要擴展的類以及類中的實例方法等聲明爲final的話,則沒法對其進行子類化的擴展。Spring AOP在沒法使用動態代理機制進行AOP功能的擴展的時候,會使用CGLIB庫的動態字節碼加強技術來實現AOP的擴展。
  3. java代碼生成正則表達式

    • EJB容器根據部署描述符文件提供了織入信息,會爲相應的功能模塊類根據描述符所提供的信息生成對應的java代碼,而後經過部署工具或者部署接口編譯java代碼生成對應的java類。以後部署到EJB容器的功能模塊類就能夠正常工做了。
  4. 自定義類加載器spring

    • 全部的java程序的class都要經過相應的類加載器(Classloader)架子啊到Java虛擬機以後才能夠運行。默認的類加載器會讀取class字節碼文件,而後按照class字節碼規範,解析並加載這些class文件到虛擬機運行。若是我夢可以在這個class加載到虛擬機運行期間,將橫切邏輯織入到class文件的話,是否是就完成了AOP和OPP的融合呢?
    • 咱們能夠經過自定義類加載器的方式完成橫切邏輯到系統的織入,自定義類加載器經過讀取外部文件規定的織入規則和必要信息,在加載class文件期間就能夠將橫切邏輯添加到系統模塊類的現有邏輯中,而後將改動後的class交給java虛擬機運行。經過類加載器,咱們基本能夠對大部分類以及相應的實例進行織入,功能於以前的幾種方式相比固然強大不少。不顧哦這種方式最大的問題就是類加載器自己的使用。某些應用服務器會控制整個的類加載體系,因此,在這樣的場景下會形成必定的問題。
    • Jboss AOP 和已經併入AspectJ項目的AspectWerkz框架都是採用自定義類加載器的方式實現。
  5. AOL擴展apache

    • AOL擴展是最強大、也是最難掌握的一種方式,咱們以前提到AspectJ就屬於這種方式。在這種方式中,AOP的各類概念在AOL中大都有一一對應的實體。咱們可使用擴展過的AOL,實現任何AOP概念實體甚至OPP概念實體,好比一Aspect以及Class。全部的AOP概念在AOL中獲得了最完美的表達。
    • 採用擴展的AOL,在AOP概念的表達上頗具實例,使得AOP涉及的全部橫切關注點邏輯在進行織入以前,能夠自由自在地存活在本身的「國度中」。而像「編譯到靜態類能夠提高系統運行性能」,「java虛擬機能夠像加載日常類那種,加載已經織入相應邏輯的AO組件所在的文件並運行」等特色。使用這種方式,須要學習一門擴展的AOL語言。


一些單詞的含義:設計模式

  • Joinpoint 切點緩存

  • Pointcut 切點表達式:服務器

    • 直接指定Joinpoint所在的方法名稱
    • 正則表達式:Jboss、Spring AOP、AspectWerkz等均支持
    • 使用特定的Pointcut表達語言:Spring 2.0之後,藉助於AspectJ的Pointcut表述語言解釋器,支持使用AspectJ的Pointcut表述語言來指定Pointcut。
  • Advice 切面微信

      1. Before Advice
      1. After Advice
      • After returning
      • After throwing
      • After Advice(finally)
      1. After Around
      1. Introduction
      • 在AspectJ中稱Inter-Type Declaration,在JBoss AOP 中稱Mix-in,都是指這同一種類型的Advice。與以前的幾種Advice類型不一樣,Introduction不是根據橫切邏輯在Joinpoint處的執行時機來區分的,而是根據它能夠完成的功能而區別於其餘Advice類型。
      • AspectJ採用靜態織入的形式,那麼對象在使用的時候,Itroduction邏輯已是編譯織入完成的。因此理論上來講,AspectJ提供的Introduction類型的Advice,在現有Java平臺上的AOP實現中是性能最好的;而像JBosss AOP或者Spring AOP等採用動態織入的AOP實現,Introduction的性能要稍遜一籌。

Aspect

Aspect是對系統中的橫切關注點邏輯進行模塊化封裝的AOP的概念實體。一般狀況下,Aspect能夠包含多個Pointcut以及相關Advice定義。

設計模式之代理模式

  1. 靜態代理
package org.springframework.mylearntest.aop.staticproxy;

public interface IRequestable {
	void request();
}
package org.springframework.mylearntest.aop.staticproxy;

public class RequestableImpl implements IRequestable{
	@Override
	public void request() {
		System.out.println(" request process in RequestableImpl");
	}
}
package org.springframework.mylearntest.aop.staticproxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ServiceControlRequestableProxy implements IRequestable{
	private static final Logger logger = LoggerFactory.getLogger(ServiceControlRequestableProxy.class);
	private IRequestable requestable;

	public ServiceControlRequestableProxy(IRequestable target) {
		this.requestable = target;
	}

	@Override
	public void request() {
		System.out.println("request process in ServiceControlRequestableProxy");
		requestable.request();
	}

	public static void main(String[] args) {
		IRequestable target = new RequestableImpl();// 須要被代理的對象
		IRequestable proxy = new ServiceControlRequestableProxy(target); // 以構造方法形式將被代理對象傳入代理者中
		proxy.request();// 讓代理者去處理請求
	}
}
  1. 動態代理
  • 動態代理機制主要由一個類和一個接口組成,即java.lang.reflect.Proxy類和java.lang.reflect.InvocationHadler接口。
package org.springframework.mylearntest.aop.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class RequestCtrlInvocationHandler implements InvocationHandler {
	private Object target;

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

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("reflect invoke before target method");
		if ("request".equals(method.getName())) {
			System.out.println("dynamic proxy target method");
			return method.invoke(target, args);
		}
		return null;
	}
}
package org.springframework.mylearntest.aop.dynamicproxy;

import org.springframework.mylearntest.aop.staticproxy.IRequestable;
import org.springframework.mylearntest.aop.staticproxy.RequestableImpl;

import java.lang.reflect.Proxy;

@SuppressWarnings("rawtypes")
public class Test4DynamicProxy {
	public static void main(String[] args) {
		// arg1:類加載器 arg2:接口信息 arg3:實現InvocationHandler的類 並傳入須要代理的對象
		IRequestable requestable = (IRequestable) Proxy.newProxyInstance(
				Test4DynamicProxy.class.getClassLoader(),
				new Class[]{IRequestable.class},
				new RequestCtrlInvocationHandler(new RequestableImpl()));
		requestable.request();
	}
}

若是想深刻了解動態代理,請閱讀《java reflect in action》。

  1. CGLIB字節碼生成
  • 須要使用CGLIB擴展子類,首先須要實現一個net.sf.cglib.proxy.Callback,不過更多的時候,咱們會直接使用net.sf.cglib.proxy.MethodInterceptor接口(MethodInterceptor擴展了Callback接口)。
package org.springframework.mylearntest.aop.CGLIBClassGenerate;

public class Requestable {
	public void request(){
		System.out.println("req in requestable without implement any interface");
	}
}
package org.springframework.mylearntest.aop.CGLIBClassGenerate;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class RequestCtrlCallback implements MethodInterceptor {
	@Override
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		if (method.getName().equals("request")) {
			System.out.println("proxy generated by cglib intercept method request");
			return methodProxy.invokeSuper(o, objects);
		}
		return null;
	}
}
package org.springframework.mylearntest.aop.CGLIBClassGenerate;

import org.springframework.cglib.proxy.Enhancer;

public class Test4CGLIB {
	public static void main(String[] args) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(Requestable.class);
		enhancer.setCallback(new RequestCtrlCallback());

		Requestable proxy = (Requestable) enhancer.create();
		proxy.request();
	}
}

AOP中的Pointcut

若是Pointcut類型爲TruePointcut,默認會對系統中的全部對象,以及對象上全部被支持的Joinpoint進行匹配。

package org.springframework.aop;

springframework.aop.support.MethodMatchers

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	Pointcut TRUE = TruePointcut.INSTANCE;

}
package org.springframework.aop;

import java.io.Serializable;

@SuppressWarnings("serial")
final class TruePointcut implements Pointcut, Serializable {

	public static final TruePointcut INSTANCE = new TruePointcut();

	private TruePointcut() {
	}

	@Override
	public ClassFilter getClassFilter() {
		return ClassFilter.TRUE;
	}

	@Override
	public MethodMatcher getMethodMatcher() {
		return MethodMatcher.TRUE;
	}

	private Object readResolve() {
		return INSTANCE;
	}

	@Override
	public String toString() {
		return "Pointcut.TRUE";
	}

}

ClassFilter和MethodMatcher分別用於匹配將被執行織入操做的對象以及相應的方法。之因此將類型匹配和方法匹配分開定義,是由於能夠重用不一樣級別的匹配定義,而且能夠在不一樣級別或者相同級別上進行組合操做,或者強制讓某個子類只覆蓋(Override)相應方法定義等。

package org.springframework.aop;

@FunctionalInterface
public interface ClassFilter {

	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}
```java
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;

}
  • 當isRuntime返回false時,表示不會考慮具體Joinpoint的方法參數,這種類型的MethodMatcher稱之爲staticMethodMatcher。由於不用每次都檢查參數,那麼對於一樣類型的方法匹配結果,就能夠在框架內部緩存以提升性能。
  • 當isRuntime返回true時,代表MethodMatcher將會每次都對方法調用的參數進行匹配檢查,這種類型的MethodMatcher稱之爲DynamicMethodMatcher。由於每次都要對方法參數進行檢查,沒法對匹配的結果進行緩存,因此,匹配效率相對於StaticMethodMatcher來講要差。並且大部門狀況下,staticMethodMatcher已經能夠知足須要。最好避免使用DynamicMethodMatcher類型。
  • 若是boolean matches(Method method, Class targetClass);返回true時,三個參數的matches將會被執行,以進一步檢查匹配條件;若是boolean matches(Method method, Class targetClass);返回false,那麼無論這個MethodMatcher是staticMethodMatcher仍是DynamicMethodMatcher,該結果已是最終結果,三個參數的方法確定不會被執行了。

    常見pointcut

分述各類Pointcut

  1. NameMatchMethodPointcut
  • 最簡單的Pointcut實現,屬於StaticMethodMatcherPointcut的子類,能夠根據自身指定一組方法名稱與Joinpoint處的方法的方法名稱進行匹配。
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("matches");
// 或者傳入多個方法名
pointcut.setMappedNames(new String[]{"matches", "isRuntime"});
// 簡單模糊匹配
pointcut.setMappedNames(new String[]{"match*", "matches", "mat*es" });
  • 此方法沒法對重載的方法名進行匹配,由於它僅對方法名進行匹配,不會考慮參數相關信息,並且也沒有提供能夠指定參數匹配信息的途徑。
  1. JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut
  • StaticMethodMatcherPointcut的子類有一個專門提供基於正則表達式的實現分支,以抽象類AbstractRegexpMethodPointcut爲統帥,聲明瞭pattern 和 patterns屬性,能夠指定一個或則和多個正則表達式的匹配模式。其下設JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut兩種具體實現。JdkRegexpMethodPointcut是在JDK 1.4以後引入JDK標準正則表達式。
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPatterns(new String[]{".*match.", ".*matches"});
  • 注意正則表達式匹配模式必須匹配整個方法簽名(Method signature)的形式指定,而不能像NameMatchMethodPointcut那樣僅給出匹配的方法名稱。

  • Perl5RegexpMethodPointcut實現使用jakarta ORO提供正則表達式支持,

    1. 能夠經過pattern或者patterns對象屬性指定一個或者多個正則表達式
    2. 指定正則表達式匹配模式應該覆蓋匹配整個方法簽名,而不是隻指定到方法名稱部分。
  1. AnnotationMatchingPointcut
package org.springframework.mylearntest.aop.annotationmatchingpointcut;

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 org.springframework.mylearntest.aop.annotationmatchingpointcut;

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 {
}
package org.springframework.mylearntest.aop.annotationmatchingpointcut;

@ClassLevelAnnotation
public class GenericTargetObject {

	@MethodLevelAnnotation
	public void getMethod1() {
		System.out.println("getMethod1");
	}

	public void getMethod2() {
		System.out.println("getMethod2");
	}
}
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);
	// 也能夠經過靜態方法
	AnnotationMatchingPointcut pointcut1 = AnnotationMatchingPointcut.forClassAnnotation(MethodLevelAnnotation.class);
	// 同時限定
	AnnotationMatchingPointcut pointcut2 = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
  1. ComposablePointcut
    Spring AOP提供Pointcut邏輯運算的Pointcut實現。它能夠進行Pointcut之間的「並」以及「交」運算。
package org.springframework.mylearntest.aop.pointcut.composablePointcut;

import org.junit.Assert;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.Pointcuts;

public class Test4ComposablePointcut {

	public static void main(String[] args) {
		ComposablePointcut pointcut1 = new ComposablePointcut(new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return false;
			}
		}, MethodMatcher.TRUE);

		ComposablePointcut pointcut2 = new ComposablePointcut(new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return false;
			}
		}, MethodMatcher.TRUE);

		// union intersection
		ComposablePointcut union = pointcut1.union(pointcut2);
		ComposablePointcut intersection = pointcut1.intersection(union);

		Assert.assertEquals(pointcut1,intersection);

		// combine classFilter with methodMatcher
		pointcut2.union(new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return false;
			}
		}).intersection(MethodMatcher.TRUE);

		// just compute between pointcut, use org.springframework.aop.support.Pointcuts
		Pointcut pointcut3 = new Pointcut() {
			@Override
			public ClassFilter getClassFilter() {
				return null;
			}

			@Override
			public MethodMatcher getMethodMatcher() {
				return null;
			}
		};

		Pointcut pointcut4 = new Pointcut() {
			@Override
			public ClassFilter getClassFilter() {
				return null;
			}

			@Override
			public MethodMatcher getMethodMatcher() {
				return null;
			}
		};

		Pointcut union1 = Pointcuts.union(pointcut3, pointcut4);
		Pointcut intersection1 = Pointcuts.intersection(pointcut3, pointcut4);

	}
}
  1. ControlFlowPointcut
    ControlFlowPointcut匹配程序的調用流程,不是對某個方法執行所在Joinpoint處的單一特徵進行匹配,而是要被特定的類執行時,纔會進行方法攔截。
    由於ControlFlowPointcut類型的Pointcut 須要在運行期間檢查程序的調用棧,並且每次方法調用都須要檢查,因此性能比較差。

Spring Aop中的Advice

Spring 中各類Advice 和 Aop Alliance標準接口之間的關係。

  • 在Spring中,Advice按照其自身實例可否在目標對象類的全部實例中共享這一標準,能夠劃分爲兩大類,即per-calss類型的Advice 和 per-instance類型的Advice。

per-class

per-class的Advice是指,該類型的Advice的實例能夠在目標對象類的全部實例之間共享。這種類型的Advice一般只是提供方法的攔截功能,不會對目標對象類保存任何狀態或者添加新的特性。

  1. BeforeAdvice
package org.springframework.mylearntest.aop.advice;

import org.apache.commons.io.FileUtils;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.core.io.Resource;

import java.lang.reflect.Method;

public class ResourceSetupBeforeAdvice implements MethodBeforeAdvice {
	private Resource resource;

	public ResourceSetupBeforeAdvice(Resource resource) {
		this.resource = resource;
	}

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		if (!resource.exists()) {
			FileUtils.forceMkdir(resource.getFile());
		}
	}
}
  1. ThrowsAdvice
package org.springframework.mylearntest.aop.advice;

import org.omg.CORBA.portable.ApplicationException;
import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
	public void afterThrowing(Throwable t) {
		// 普通異常處理
	}

	public void afterThrowing(RuntimeException t) {
		// 運行時異常處理
	}

	public void afterThrowing(Method m, Object[] args, Object target, ApplicationException e) {
		// 處理應用程序生成的異常
	}
}
  1. AfterReturningAdvice
    此Advice能夠訪問到當前Joinpoint的方法返回值、方法、方法參數以及所在的目標對象,可是不能更改返回值,可使用Around Advice來更改返回值。

  2. Around Advice
    Spring中沒有定義Around Advice ,而是直接使用AOP Alliance的標準接口,實現 MethodInterceptor便可。

per-instance

per-instance類型的Advice不會在目標類全部對象實例之間共享,而是會爲不一樣的實例對象保存它們各自的狀態以及相關邏輯。在Spring中Introduction就是惟一的一種per-instance型Advice。

  • Introduction 能夠在不改動目標類定義的狀況下,爲目標類添加新的屬性以及行爲。
  • 在Spring中,爲目標對象添加新的屬性和行爲必須聲明相應的接口以及相應的實現。這樣,再經過特定的攔截器將新的接口定義以及實現類中的邏輯附加到目標對象之上。以後,目標對象就擁有了新的狀態和行爲。這個特定的攔截器是org.springframework.aop.IntroductionInterceptor。
  • Introduction繼承了MethodInterceptor以及DynamicIntroductionAdvice,經過DynamicIntroductionAdvice,咱們能夠界定當前的IntroductionInterceptor爲哪些接口類提供相應的攔截功能。經過MethodInterceptor,IntroductionInterceptor就能夠處理新添加的接口上的方法調用了。一般狀況下,對於IntroductionInterceptor來講,若是是新增長的接口上的方法調用,沒必要去調用MethodInterceptor的proceed()方法。當前被攔截的方法其實是整個調用鏈中要最終執行的惟一方法。
    Introduction相關類圖
DelegatingIntroductionInterceptor

DelegatingIntroductionInterceptor不會本身實現將要添加到目標對象上的新邏輯行爲,而是委派給其餘的實現類。

  • 使用DelegatingIntroductionInterceptor加強Developer。接口省略。
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public class Developer implements IDeveloper{
	@Override
	public void developSoftware() {
		System.out.println(" do some developing ...");
	}
}
  1. 爲新的狀態和行爲定義接口。咱們要爲實現類添加加強的功能,首先須要將需求的職能以接口定義的形式聲明。
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public interface ITester {
	boolean isBusyAsTester();
	void testSoftware();
}
  1. 給出新的接口的實現類。接口實現類給出將要添加到目標對象的具體邏輯。當目標對象要行使新的職能的時候,會經過該實現類尋求幫忙。
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public class Tester implements ITester{
	private  boolean busyAsTester;

	public void setBusyAsTester(boolean busyAsTester) {
		this.busyAsTester = busyAsTester;
	}

	@Override
	public boolean isBusyAsTester() {
		return busyAsTester;
	}

	@Override
	public void testSoftware() {
		System.out.println("do some developing and test ...");
	}
}
  1. 經過DelegatingIntroductionInterceptor進行Introduction攔截。有了新增長的職能的接口以及相應的實現類,使用DelegatingIntroductionInterceptor,咱們能夠把具體的Introduction攔截委託給具體的實現類來完成。
ITester delegator = new Tester();
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(delegator);
		
// 進行織入
ITester tester = (ITester)weaver.weave(developer).with(interceptor).getProxy();
tester.testSoftware();
  1. 雖然,DelegatingIntroductionInterceptor是Introduction型Advice的一個實現,但它的實現根本就有兌現Introduction做爲per-instance型的承諾。實際上DelegatingIntroductionInterceptor會使用它所持有的同一個"delegate" 接口實例,供同一目標類的全部實例共享使用。若是要想嚴格達到Introduction型Advice的效果,咱們應該使用DelegatePerTargetObjectIntroductionInterceptor。
DelegatePerTargetObjectIntroductionInterceptor

與DelegatingIntroductionInterceptor不一樣,DelegatePerTargetObjectIntroductionInterceptor會在內部持有一個目標對象與相應Introduction邏輯實現類之間的映射關係。當每一個對象上的新定義的接口方法被調用的時候,DelegatePerTargetObjectIntroductionInterceptor會攔截這些調用,而後以目標對象實例做爲鍵,到它持有的那個映射關係中取得對應當前目標對象實例的Introduction實現實例。

DelegatePerTargetObjectIntroductionInterceptor interceptor1 =
				new DelegatePerTargetObjectIntroductionInterceptor(Tester.class,ITester.class);
  • 擴展DelegatingIntroductionInterceptor
package org.springframework.mylearntest.aop.advice.perinstance;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

public class TesterFeatureIntroductionInterceptor extends DelegatingIntroductionInterceptor implements ITester {

	public static final long serialVersionUID = -3387097489523045796L;
	private boolean busyAsTester;

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		if (isBusyAsTester() && StringUtils.contains(mi.getMethod().getName(), "developSoftware")) {
			throw new RuntimeException("I'am so tired");
		}
		return super.invoke(mi);
	}

	@Override
	public boolean isBusyAsTester() {
		return busyAsTester;
	}

	public void setBusyAsTester(boolean busyAsTester) {
		this.busyAsTester = busyAsTester;
	}

	@Override
	public void testSoftware() {
		System.out.println("I will ensure the quality");
	}
}

Spring AOP 中的Aspect

  • Advisor表明Spring中的Aspect,可是與正常的Aspect不一樣,Advisor一般只持有一個Pointcut和一個Advice。而理論上,Aspect定義中能夠有多個Pointcut和多個Advice,因此Advisor是一種特殊的Aspect。
PointcutAdvisor


  • 實際上,org.springframework.aop.PointcutAdvisor纔是真正定義的有一個Pointcut和一個Advice的Advisor,大部分的Advisor實現所有是在PointcutAdvisor下的。
  1. DefaultPointcutAdvisor
<bean id="pointcut"
	  class="org.springframework.mylearntest.aop.pointcut.selfdefinepointcut.QueryMethodPointcut"/>
	<bean id="advice" class="org.springframework.mylearntest.aop.advice.perclass.DiscountMethodInterceptor"/>

	<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="pointcut" ref="pointcut"/>
		<property name="advice" ref="advice"/>
	</bean>
  1. NameMatchMethodPointcutAdvisor
  • 此類內部持有一個NameMatchMethodPointcut類型的Pointcut實例。當經過NameMatchMethodPointcutAdvisor公開的setMappedName和setMappedNames方法設置將要被攔截的方法名的時候,實際上實在操做NameMatchMethodPointcutAdvisor內部的NameMatchMethodPointcut實例。
Advice advice = new DiscountMethodInterceptor();
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
advisor.setMappedName("invoke");
  1. RegexpMethodPointcutAdvisor
    只能經過正則表達式爲其設置相應的Pointcut,內部持有一個AbstractRegexpMethodPointcut的實例。AbstractRegexpMethodPointcut有兩個實現類,Perl5RegexpMethodPointcutAdvisor和JdkRegexpMethodPointcut。默認使用JdkRegexpMethodPointcut,若是強制使用Perl5RegexpMethodPointcutAdvisor,那麼能夠經過RegexpMethodPointcutAdvisor的setPerl5(boolean)實現。

  2. DefaultBeanFactoryPointcutAdvisor
    DefaultBeanFactoryPointcutAdvisor自身綁定到了BeanFactory,要使用DefaultBeanFactoryPointcutAdvisor,要綁定到Spring IoC容器。經過容器中的Advice註冊的beanName來關聯對應的Advice。只有當對應的Pointcut匹配成功以後,採起實例化對應的Advice,減小了容器啓動初期Advisor和Advice之間的耦合性。

IntroductionAdvisor

IntroductionAdvisor只能應用於類級別的攔截,只能使用Introduction型的Advice,而不能像PointcutAdvisor那樣,可使用任意類型的Pointcut,以及差很少任何類型的Advice。
IntroductionAdvisor類結構圖

Order的做用
  • 大多數時候,會有多個關注橫切點,那麼,系統實現中就會有多個Advisor存在。當其中的某些Advisor的Pointcut匹配了同一個Joinpoint的時候,就會在這同一個Joinpoint處執行多個Advice的橫切邏輯。一旦這幾個須要在同一個Joinpoint處執行的Advice邏輯存在優先順序依賴的話,就須要咱們來干預了。
  • Spring在處理同一Joinpoint處的多個Advisor的時候,會按照指定的順序有優先級來執行他們。順序號越小,優先級越高,優先級越高的,越先被執行。(默認狀況下,Spring會按照它們的聲明順序來應用它們,最早聲明的順序號最小但優先級最大,其次次之)
    order圖
  • 各個Advisor實現類,其實已經實現了Order接口。在使用的時候咱們能夠直接指定便可
<bean id="permissionAuthAdvisor" class="...PermissionAuthAdvisor">
	<property name="order" value="1">
	...
<bean>

<bean id="exceptionBarrierAdvisor" class="...ExceptionBarrierAdvisor">
	<property name="order" value="0">
	...
<bean>

Spring AOP的織入

AspectJ採用ajc編譯器做爲它的織入器;JBoss AOP使用自定義的ClassLoader做爲它的織入器;而在Spring AOP中,使用類org.springframework.aop.framework.ProxyFactory做爲織入器。

  • 使用方法
    1. 傳入須要織入的對象
      ProxyFactory weaver = new ProxyFactory(target);
    2. 將要應用到目標對象的Advisor綁定到織入器上
      • 若是不是Introduction的Advice類型,Proxy內部就會爲這些Advice構造相應的Advisor,只不過在爲它們構造Advisor中使用的Pointcut爲Pointcut.TRUE。
      • 若是是Introduction類型,則會根據該Introduction具體類型進行區分;若是是Introduction的子類實現,框架內部會爲其構造一個DefaultIntroductionAdvisor;若是是DynamicIntroductionAdvice的子實現類,框架內部將拋出AOPConfigException異常(由於沒法從DynamicIntroductionAdvice取得必要的目標對象信息)
        weaver.addAdvisor(advisor);
    3. 獲取代理對象
      Object proxyObject = weaver.getProxy();
基於接口的代理
package org.springframework.mylearntest.aop.weaver;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;

import java.util.Date;

/**
 * @Author: whalefall
 * @Date: 2020/7/15 22:53
 */

@SuppressWarnings({"rawtypes", "Deprecated"})
public class Test4ProxyFactory {
	public static void main(String[] args) {
		/*// 1. 傳入須要織入的對象
		ProxyFactory weaver = new ProxyFactory(new Tester());
		// weaver.setTarget(new Tester());

		// 2. 將要應用到目標對象的Advisor綁定到織入器上
		ApplicationContext context = new ClassPathXmlApplicationContext("advisor/defaultadvisor/defaultadvisor.xml");
		Advisor advisor = (Advisor) context.getBean("advisor");
		weaver.addAdvisor(advisor);

		Object proxyObject =  weaver.getProxy();
		System.out.println(proxyObject.getClass());
		// out: class org.springframework.mylearntest.aop.advice.perinstance.Tester$$EnhancerBySpringCGLIB$$8e739b5b
		*/

		// 目標類有實現接口的用法
		// 只要不將ProxyFactory的optimize和proxyTargetClass設置爲true
		// 那麼ProxyFactory都會按照面向接口進行代理
		MockTask task = new MockTask();
		ProxyFactory weaver = new ProxyFactory(task);
		// weaver.setInterfaces(new Class[]{ITask.class});
		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
		advisor.setMappedNames("execute");
		advisor.setAdvice(new PerformanceMethodInterceptor());
		weaver.addAdvisor(advisor);
		ITask proxyObj = (ITask)weaver.getProxy();
		// com.sun.proxy.$Proxy0
		// System.out.println(proxyObj.getClass());
		// 只能強制轉化爲接口類型,不能轉化爲實現類類型 不然會拋出ClassCastException
		// ITask proxyObj = (MockTask)weaver.getProxy();
		proxyObj.execute(new Date());

		// 目標類沒有實現接口的用法


	}
}
基於類代理
package org.springframework.mylearntest.aop.weaver.baseonclass;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor;

/**
 * @Author: whalefall
 * @Date: 2020/7/17 23:31
 */
public class Test4CGLib {
	public static void main(String[] args) {
		ProxyFactory weaver = new ProxyFactory(new Executable());
		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();

		advisor.addMethodName("execute");
		advisor.setAdvice(new PerformanceMethodInterceptor());
		weaver.addAdvisor(advisor);

		Executable proxyObject = (Executable)weaver.getProxy();
		proxyObject.execute();
		// org.springframework.mylearntest.aop.weaver.baseonclass.Executable$$EnhancerBySpringCGLIB$$37e40619
		System.out.println("proxyObject class: " + proxyObject.getClass());
	}
}
  • 若是目標類沒有實現任何接口,無論proxyTargetClass的屬性是什麼,ProxyFactoy會採用基於類的代理
  • 若是ProxyFactoy的proxyTargetClass屬性值被設置爲true,ProxyFactoy會採用基於類的代理
  • 若是ProxyFactoy的optimize屬性被設置爲true,ProxyFactory會採用基於類的代理。
Introduction的織入
  • Introduction能夠爲已經存在的對象類型添加新的行爲,只能應用於對象級別的攔截,而不是一般Advice的方法級別的攔截,因此在Introduction的織入過程當中,不須要指定Pointcut,而只須要指定目標接口類型。
  • Spring的Introduction支持只能經過接口定義爲當前對象添加新的行爲。因此,咱們須要在織入的時機,指定新織入的接口類型。
package org.springframework.mylearntest.aop.weaver.introduction;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.mylearntest.aop.advice.perinstance.Developer;
import org.springframework.mylearntest.aop.advice.perinstance.IDeveloper;
import org.springframework.mylearntest.aop.advice.perinstance.ITester;
import org.springframework.mylearntest.aop.advice.perinstance.TesterFeatureIntroductionInterceptor;

/**
 * @Author: whalefall
 * @Date: 2020/7/19 0:02
 */

@SuppressWarnings("rawtypes")
public class Test4Introduction {
	public static void main(String[] args) {
		ProxyFactory weaver = new ProxyFactory(new Developer());
		weaver.setInterfaces(new Class[]{IDeveloper.class, ITester.class});
		TesterFeatureIntroductionInterceptor advice = new TesterFeatureIntroductionInterceptor();
		weaver.addAdvice(advice);
		// DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice,advice);
		// weaver.addAdvisor(advisor);

		Object proxy = weaver.getProxy();
		((ITester)proxy).testSoftware();
		((IDeveloper)proxy).developSoftware();
		System.out.println("proxy = " + proxy);

	}
}

ProxyFactory本質

  • Spring AOP框架內使用AopProxy對使用的不用的代理實現機制進行了適度的抽象,主要有針對JDK動態代理和CGLIB兩種機制的AopProxy兩種實現,分別是Cglib2AopProxy和JdkDynamicAopProxy兩種實現。動態代理須要經過InvocationHandler提供調用攔截,因此JdkDynamicAopProxy同時實現了InvocationHandler接口。採用抽象工廠模式,經過org.springframework.aop.framework.AopProxyFactory進行。
pulic interface AopProxyFactory {
	AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
  • AopProxyFactory根據傳入的AdvisedSupport實例提供的相關信息,來決定生成什麼類型的AopProxy,具體的工做由AopProxyFactory具體的實現類來完成。即org.springframework.aop.framework.DefaultAopProxyFactory。
package org.springframework.aop.framework;

import java.io.Serializable;
import java.lang.reflect.Proxy;

import org.springframework.aop.SpringProxy;

@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		// 若是傳入的AdvisedSupport實例的isOptimize或者isProxyTargetClass方法返回true,
		// 或者目標對象沒有實現任何接口,則採用CGLIB生成代理對象,不然使用動態代理。
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

	private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
		Class<?>[] ifcs = config.getProxiedInterfaces();
		return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
	}

}

  • AdvisedSupport是一個生成代理對象所須要的信息的載體。一類爲org.springframework.aop.framework.ProxyConfig爲首的,記載生成代理對象的控制信息;一類以org.springframework.aop.framework.Advised爲首,承載生成代理對象的所須要的必要信息,好比相關目標類、Advice、Advisor等。

  • ProxyConfig就是普通的JavaBean,定義了五個boolean型的屬性,分別控制在生成代理代理對象的時候,應該採起哪些措施。

    1. ProxyTargetClass:若是這個屬性設置如true,則ProxyFactory將會使用CGLIB對目標對象進行代理。默認值爲false。
    2. optimize:該屬性主要用於告知代理對象是否須要採起進一步的優化措施。若是代理對象生成以後,即便爲其添加或者移除了相應的人Advice,代理對象也能夠忽略這種變更。若是這個屬性設置如true,則ProxyFactory將會使用CGLIB對目標對象進行代理。默認值爲false。
    3. opaque:該屬性用於控制生成的代理對象是否能夠強制轉化爲Advised,默認值爲false,表示任何生成的代理對象均可以強制轉型爲Advised,咱們能夠經過Advised查詢代理對象的一些狀態。
    4. exposeProxy:設置exposeProxy,可讓Spring AOP框架在生成代理對象時,將當前代理對象綁定到ThreadLocal。若是目標對象須要訪問當前代理對象,能夠經過AopContext.currentProxy()拿到代理對象。出於性能方面考慮,該屬性默認爲false。
    5. frozen:若是將frozen設置爲true,那麼一旦針對dialing對象生成的各項信息配置完成,則不允許更改。好比ProxyFactory的設置完畢,而且frozen爲true,則不能對Advice進行任何變更,這樣能夠優化代理對象的性能,默認狀況下爲false。
  • 要生成代理對象,只有ProxyConfig提供的信息還不夠,咱們還須要生成代理對象的一些具體信息,好比,要針對哪些目標類生成代理對象,要爲代理對象加入何種橫切邏輯等,這些信息能夠經過org.springframework.aop.framework.Advised設置或者拆線呢。默認狀況下Spring AOP框架返回的代理對象均可以強制轉型爲Advised,已查詢代理對象的相關信息。

  • 咱們可使用Advised接口訪問相應代理對象全部持有的Advisor,進行添加Advisor、一處Advisor等相關動做。即便代理對象已經生成完畢,也可對其進行操做,直接操做Advised,更多時候用於測試場景,能夠幫助咱們檢查生成的代理對象是否如所指望的那樣。

AopProxy、AdvisedSupport、ProxyFactory之間的關係

  • ProxyFactory集AopProxy和AdvisedSupport於一身,能夠經過AdvisedSupport設置生成代理對象所須要的相關信息,能夠經過AopProxy生成代理對象。爲了重用相關邏輯,Spring AOP框架在實現的時候,將一些公用的邏輯抽取到了org.springframework.aop.frameworkx.ProxyCreatorSuppport中,自身繼承了AdvisedSupport,因此就能具備設置生成代理對象所須要的相關信息。
  • 爲了簡化生成不一樣類型AopProxy的工做,ProxyCreatorSuppport內部持有一個AopProxyFactory實例,默認採用的是DefaultAopProxyFactory。

歡迎關注微信公衆號哦~ ~

相關文章
相關標籤/搜索