深入理解Spring AOP 1.0

WhaleFall541發表於2020-07-27

本文相關程式碼(來自官方原始碼spring-test模組)請參見spring-demysify org.springframework.mylearntest包下。

統稱能夠實現AOP的語言為AOL,即(Aspect-Oriented Language),其他Aspectj

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

JAVA中AOP實現方式

  1. 動態代理

    • 在執行期間,為相應的介面動態生成對應的代理物件,將橫切關注點邏輯封裝到動態代理的InvocationHandler中,然後在系統執行期間,根據橫切關注點需要織入的模組位置,將橫切邏輯織入到相應的代理類中。
  2. 動態位元組碼增強

    • 使用ASM或者CGLIB等Java工具庫,在程式執行期間,動態構建位元組碼的class檔案。
    • 在執行期間通過動態位元組碼增強技術織入橫切邏輯,為這些系統模組類生成相應的子類,而將橫切邏輯加到這些子類中,讓應用程式的執行期間使用的是這些動態生成的子類,從而達到將橫切邏輯織入系統的目的。
    • 如果需要擴充套件的類以及類中的例項方法等宣告為final的話,則無法對其進行子類化的擴充套件。Spring AOP在無法使用動態代理機制進行AOP功能的擴充套件的時候,會使用CGLIB庫的動態位元組碼增強技術來實現AOP的擴充套件。
  3. java程式碼生成

    • EJB容器根據部署描述符檔案提供了織入資訊,會為相應的功能模組類根據描述符所提供的資訊生成對應的java程式碼,然後通過部署工具或者部署介面編譯java程式碼生成對應的java類。之後部署到EJB容器的功能模組類就可以正常工作了。
  4. 自定義類載入器

    • 所有的java程式的class都要通過相應的類載入器(Classloader)載入到Java虛擬機器之後才可以執行。預設的類載入器會讀取class位元組碼檔案,然後按照class位元組碼規範,解析並載入這些class檔案到虛擬機器執行。如果我能夠在這個class載入到虛擬機器執行期間,將橫切邏輯織入到class檔案的話,是不是就完成了AOP和OPP的融合呢?
    • 我們可以通過自定義類載入器的方式完成橫切邏輯到系統的織入,自定義類載入器通過讀取外部檔案規定的織入規則和必要資訊,在載入class檔案期間就可以將橫切邏輯新增到系統模組類的現有邏輯中,然後將改動後的class交給java虛擬機器執行。通過類載入器,我們基本可以對大部分類以及相應的例項進行織入,功能於之前的幾種方式相比當然強大很多。不過這種方式最大的問題就是類載入器本身的使用。某些應用伺服器會控制整個的類載入體系,所以,在這樣的場景下會造成一定的問題。
    • Jboss AOP 和已經併入AspectJ專案的AspectWerkz框架都是採用自定義類載入器的方式實現。
  5. AOL擴充套件

    • 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。

ProxyFactoryBean

  1. ProxyFactoryBean的本質
  • ProxyFactoryBean本質上是一個用來產生Proxy的FactoryBean,FactoryBean的作用 -- 如果某個物件持有某個FactoryBean的引用,它取得的不是FactoryBean本身,而是FactoryBean的getObject()方法返回的物件。所以,如果容器中某個物件依賴於ProxyFactoryBean,那麼它將會使用到ProxyFactoryBean的getObject()方法返回的代理物件。
  • 要讓ProxyFactoryBean的getObject()方法返回相應目標物件的代理物件其實很簡單。因為ProxyFactoryBean繼承了ProxyFactory共有的父類ProxyCreatorSupport,而ProxyCreatorSupport基本上已經把要做的事情(設定目標物件、配置其他部件、生成對應的AopProxy等)全部完成了。我們只用在ProxyFactoryBean的getObject()方法中通過父類的createAopProxy()拿到代理物件,然後return AopProxy.getObject()即可。
public Object getObject() throws BeansException {
		initializeAdvisorChain();
		if (isSingleton()) {
			return getSingletonInstance();
		}
		else {
			if (this.targetName == null) {
				logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
						"Enable prototype proxies by setting the 'targetName' property.");
			}
			return newPrototypeInstance();
		}
	}
  • ProxyBean定義中要求表明返回的物件是以singleton的scope返回,還是prototype的scope返回。針對這兩種情況返回不同的代理物件,以滿足FactoryBean的isSingleton()方法的語義。
  • 如果將ProxyFactoryBean的singleton屬性設定為true,則ProxyFactoryBean在第一次生成代理物件之後,會通過內部例項變數singletonInstance(Object型別)快取生成的代理物件。之後所有的請求都返回這一快取例項,從而滿足singleton的語義。反之,如果將ProxyFactoryBean的singleton屬性設定為false,那麼,ProxyFactoryBean每次都會重新檢測各項設定,併為當前呼叫準備一套新的環境,然後再根據最新的環境資料,返回一個新的代理物件。因此,如果singleton屬性為false,在生成代理物件的效能上存在損失。
  1. ProxyFactoryBean的使用
  • 與ProxyFactory一樣,通過ProxyFactoryBean,我們可以在生成目標物件的代理物件的時候,指定使用基於介面的代理還是基於類的代理方式,而且,因為它們全部繼承自同一個父類,大部分設定專案都相同。ProxyFactoryBean在繼承了ProxyCreatorSupport的所有配置屬性之外還新增了自己獨有的:
    1. proxyInterfaces:如果我們要採用基於介面的代理方式,那莪需要通過該屬性配置相應的介面型別,通過Collection物件傳入配置元素的介面資訊。ProxyFactoryBean有一個autodetectInterfaces屬性,該屬性預設為true,如果沒有明確指定要代理的介面型別,ProxyFactoryBean會自動檢測目標物件實現的介面型別並進行代理。
    2. interceptorNames:通過該屬性,我們可以指定多個將要織入到目標物件的Advice、攔截器以及Advisor,而再也不通過ProxyFactory那樣的addAdvice或者addAdvisor方法新增,通常我們會使用配置元素新增需要的攔截器名稱
      • 如果沒有設定目標物件,那麼可以在interceptorNames的最後一個元素的位置,放置物件的Bean定義名稱。建議直接定義目標物件,不採用前面的方法。
      • 通過指定的interceptorNames某個元素名稱之後新增*萬用字元,可以讓ProxyFactoryBean在容器中搜尋符合條件的所有Advisro並應用到目標物件。
  • 使用萬用字元的範例
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="..."/>
	<property name="interceptorNames">
		<list>
			<value>global*</value>
		</list>
	</property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor">
  1. singleton:ProxyFactoryBean本質上是一個FactoryBean,所以我們可以通過singleton屬性,指定getObject呼叫是返回同一個代理物件還是新的。
使用ProxyFactoryBean生成代理物件案例
  • 配置檔案
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<!--	目標物件的Bean定義-->
	<bean id="task"
		  class="org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask" scope="prototype"/>

	<!--	ProxyFactoryBean定義-->
	<bean id="introducedTask" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype">
		<property name="targetName">
			<value>task</value>
		</property>
		<property name="proxyInterfaces">
			<list>
				<value>org.springframework.mylearntest.aop.weaver.baseoninterface.ITask</value>
				<value>org.springframework.mylearntest.aop.weaver.proxyfactorybean.ICounter</value>
			</list>
		</property>
		<property name="interceptorNames">
			<list>
				<value>introductionInterceptor</value>
			</list>
		</property>
	</bean>

	<!--	introductionInterceptor定義-->
	<bean id="introductionInterceptor"
		  class="org.springframework.aop.support.DelegatingIntroductionInterceptor" scope="prototype">
		<constructor-arg>
			<bean class="org.springframework.mylearntest.aop.weaver.proxyfactorybean.CounterImpl"/>
		</constructor-arg>
	</bean>

</beans>
  • java類
package org.springframework.mylearntest.aop.weaver.proxyfactorybean;

/**
 * @Author: whalefall
 * @Date: 2020/7/22 23:34
 */
public interface ICounter {
	void resetCounter();
	int getCounter();
}
package org.springframework.mylearntest.aop.weaver.proxyfactorybean;

/**
 * @Author: whalefall
 * @Date: 2020/7/22 23:35
 */
public class CounterImpl implements ICounter{
	private int counter;

	@Override
	public void resetCounter() {
		counter = 0;
	}

	@Override
	public int getCounter() {
		counter ++;
		return counter;
	}
}
package org.springframework.mylearntest.aop.weaver.proxyfactorybean;

import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: whalefall
 * @Date: 2020/7/22 23:51
 * @see DelegatingIntroductionInterceptor
 */
public class Test4ProxyFactoryBean {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("proxyfactorybean\\proxyfactorybean.xml");
		Object proxy1 = context.getBean("introducedTask");
		Object proxy2 = context.getBean("introducedTask");

		System.out.println(((ICounter)proxy1).getCounter());//1
		System.out.println(((ICounter)proxy1).getCounter());//2
		System.out.println(((ICounter)proxy2).getCounter());//1
	}
}
自動代理
  • Spring AOP自動代理的實現建立在IoC容器的BeanPostProcessor概念之上,使用一個BeanPostProcessor,然後在BeanPostProcessor內部實現這樣的邏輯,即當物件例項化的時候,為其生成代理物件並返回,而不是例項化後的目標物件本身,從而達到自動代理的目的。
	for(bean in IoC container){
		// 檢查當前bean定義是否滿足攔截條件,是則攔截
		if(isAssistentStatement){
			Object proxy = createProxyFor(bean);
			return proxy;
		} else {
			Object instance = createInstance(bean);
			return instance;
		}
	}
  • 攔截條件:
    • 通過外部配置檔案傳入這些攔截條件資訊,比如我們在容器的配置檔案中註冊的有關Pointcut以及Advisor等就包括這些資訊;
    • 還可以在具體類的定義檔案中,通過後設資料來知名具體的攔截條件是什麼,比如可以通過Jakarta Commons Atrributes或者Java5的註解,直接在程式碼類中標註Pointcut等攔截資訊。
Spring中可用的自動代理類

Spring AOP在org.springframework.aop.framework.autoproxy包下提供了兩個常用的AutoProxyCreator,即BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。

  1. BeanNameAutoProxyCreator
  • 使用BeanNameAutoProxyCreator可以通過指定一組容器內的目標物件對應的BeanName,將指定的一組攔截器應用到這些目標物件之上。
  • 配置案例
<bean id="target1" class="..."/>
<bean id="target2" class="..."/>

<bean id="mockTask" class="..."/>
<bean id="fakeTask" class="..."/>

<bean id="taskThrowsAdvice" class="...TaskThrowsAdvice"/>
<bean id="performanceInterceptor" class="...PerformanceInterceptor">

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
	<!--指定哪些bean自動生成代理物件-->
	<property name="beanNames">
		<list>
			<value>target1</value>
			<value>target2</value>
		</list>
	</property>
	
	<!--指定將要應用到目標物件的攔截器、Advice或者Advisor等-->
	<property name="interceptorNames">
		<list>
			<value>taskThrowsAdvice</value>
		</list>
	</property>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
	<property name="beanNames">
		<!--使用*號進行通配-->
		<list>
			<value>mockTask*</value>
			<value>fakeTask*</value>
		</list>
	</property>
	<property name="interceptorNames">
		<list>
			<value>performanceInterceptor</value>
		</list>
	</property>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
	<property name="beanNames">
		<!--對於*萬用字元的情況下,也可以使用逗號隔開-->
		<list>
			<value>target*,*Task,*service</value>
		</list>
	</property>
	<property name="interceptorNames">
		<list>
			<value>performanceInterceptor</value>
		</list>
	</property>
</bean>
  1. DefaultAdvisorAutoProxyCreator
  • 只需要在ApplicationContext中註冊Bean即可,剩下的任務會由DefaultAdvisorAutoProxyCreator完成。將其注入容器之後,將會自動搜尋容器內的所有Advisor,然後根據各個Advisor所提供的攔截資訊,為符合條件的容器中的目標物件生成相應的代理物件。DefaultAdvisorAutoProxyCreator只對Advisor有效,因為只有Advisor才既有Pointcut資訊捕捉符合條件的目標物件,又有相應的Advice。
	<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
		<!--設定物件使用基於類的代理-->
		<property name="proxyTargetClass">
			<value>true</value>
		</property>
	</bean>

	<bean id="target1" class="..."/>
	<bean id="target2" class="..."/>

	<bean id="mockTask" class="..."/>
	<bean id="fakeTask" class="..."/>

	<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="pointcut">
			...
		</property>
		<property name="advice">
			<bean id="performanceInterceptor"
				  class="org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor"></bean>
		</property>
	</bean>

	<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="pointcut">
			...
		</property>
		<property name="advice">
			<bean id="taskThrowsAdvice" class="...TaskThrowsAdvice"></bean>

		</property>
	</bean>
  1. 擴充套件AutoProxyCreator
  • 可以在Spring AOP提供的AbstractAutoProxyCreator或者AbstractAdvisorAutoProxyCreator基礎之上,實現相應的子類。
  • Sprig AOP框架中有關自動代理的實現架構
    • 所有的AutoProxyCreator都是InstantiationAwareBeanPostProcessor,這種型別的BeanPostProcessor與普通的BeanPostProcessor有所不同。當Spring IoC容器檢測到有InstantiationAwareBeanPostProcessor型別的BeanPostProcessor的時候,會直接通過InstantiationAwareBeanPostProcessor中的邏輯構造物件例項返回,而不會走正常的物件例項化流程。也就是“短路”。這樣AutoProxyCreator會直接構造目標物件的代理物件返回,而不是原來的目標物件。

  • AspectJAwareAdvisorAutoProxyCreator是Spring 2.0之後的AutoProxyCreator實現,也算是一個AutoProxyCreator的自定義實現。它還有一個子類AnnotationAwareAspectJAutoProxyCreator,可以根據Java5的註解捕獲資訊以完成自動代理。
  • Spring AOP還支援基於Jakarta Commons Atrributes的後設資料的自動代理機制,來提供攔截資訊。

TargetSource

  • TargetSource的作用:TargetSource它是目標物件的容器,當每個針對目標物件的方法呼叫經過層層攔截而到達呼叫鏈的終點的時候,就該呼叫目標物件上定義的方法了,這時候不是直接呼叫這個目標物件上的方法,而是通過某個TargetSource與實際目標物件之間互動,然後再呼叫從TargetSource中取得的目標物件上的相應的方法。

  • TargetSource的特性:

    • 每次方法呼叫都會觸發TargetSource的getTarget()方法,getTarget()方法將從相應的TargetSource實現類中取得具體的目標物件,這樣,我們就可以控制每次方法呼叫作用到的具體物件例項。
      • 提供一個目標物件池,每次從TargetSource取得的目標物件都從這個目標物件池中取得。
      • 讓一個TargetSource實現類持有多個目標物件的例項,然後按照某種規則,在每次方法呼叫時,返回相應的目標物件例項。
    • 還可以讓TargetSource只持有一個目標物件,通常ProxyFactory或者ProyxFactoryBean處理目標物件的方式也是如此,它們內部會構造一個org.springframework.aop.target.SingletonTargetSource例項,而SingletonTargetSource則會針對每次方法呼叫返回同一個目標物件的例項引用。
TargetSource實現類
  1. SingletonTargetSource
  • org.springframework.aop.target.SingletonTargetSource是使用最多的TargetSource實現類,雖然我們可能並不知道。因為通過ProxyFactory的setTarget()設定完目標物件之後,ProxyFactory內部會自行使用一個SingletonTargetSource對設定的目標物件進行封裝。
  1. PrototypeTargetSource
<bean id="target" class="org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask"
		  scope="prototype"/>

	<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
		<property name="targetBeanName">
			<value>target</value>
		</property>
	</bean>

	<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="targetSource">
			<ref bean="prototypeTargetSource"/>
		</property>
		<property name="interceptorNames">
			<list>
				<value>anyInterceptor</value>
			</list>
		</property>
	</bean>
  • 目標物件的bean定義宣告必須為prototype。
  • 通過targetBeanName屬性指定目標物件的bean定義名稱,而不是引用。
  1. HotSwappableTargetSource
  • 使用HotSwappableTargetSource封存目標物件,可以讓我們在應用程式執行的時候,根據某種特定條件,動態地替換目標物件類的具體實現,比如,IService有多個實現類,如果程式啟動之後,預設的IService實現類出現了問題,我們可以馬上切換到Iservice的另一個實現上,而所有這些對於呼叫者來說都是透明的。
  • 使用方法:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="task" class="org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask">

	</bean>

	<bean id="hotSwapTargetSource" class="org.springframework.aop.target.HotSwappableTargetSource">
		<constructor-arg>
			<ref bean="task"/>
		</constructor-arg>
	</bean>

	<bean id="taskProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="targetSource" ref="hotSwapTargetSource"/>
		<property name="interceptorNames">
			<list>
				<value>performanceMethodInterceptor</value>
			</list>
		</property>
	</bean>

	<bean id="performanceMethodInterceptor"
		  class="org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor"/>

</beans>
package org.springframework.mylearntest.aop.weaver.hotswaptargetsource;

import org.junit.Assert;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mylearntest.aop.weaver.baseoninterface.ITask;

import java.util.Date;

/**
 * @Author: whalefall
 * @Date: 2020/7/26 19:47
 */
public class Test4HotSwappableTargetSource {
	public static void main(String[] args) throws Exception {
		ApplicationContext context = new ClassPathXmlApplicationContext("hotswappabletargetsource\\hotSwappableTargetSource.xml");
		Object proxy = context.getBean("taskProxy");
		Object initTarget = ((Advised)proxy).getTargetSource().getTarget();

		HotSwappableTargetSource hotSwappableTargetSource = (HotSwappableTargetSource)context.getBean(
				"hotSwapTargetSource");
		Object oldTarget = hotSwappableTargetSource.swap(new ITask() {
			@Override
			public void execute(Date date) {
				System.out.println("old target generated by hotSwapTargetSource");
			}
		});

		Object newTarget = ((Advised)proxy).getTargetSource().getTarget();

		// initTarget = org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask@72967906
		// oldTarget = org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask@72967906
		// newTarget = org.springframework.mylearntest.aop.weaver.hotswaptargetsource
		// .Test4HotSwappableTargetSource$1@5b8dfcc1

		Assert.assertSame(initTarget,oldTarget);
		Assert.assertNotSame(initTarget,newTarget);
	}
}
  1. CommonsPoolTargetSource
  • 某些時候,我們可能想返回有限數目的目標物件例項,這些目標物件例項的地位是平等的,就好像資料庫連線池中的那些Connection一樣,我們可以提供一個目標物件的物件池,然後讓某個TargetSource實現每次都從這個物件池中取得目標物件。
  • 如果不能使用Jakarta Commons Pool,那麼也可以通過擴充套件org.springframework.aop.target.AbstractPoolingTargetSource類,實現相應的提供物件池化的功能的TargetSource。
  1. ThreadLocalTargetSource
    如果想為不同的執行緒呼叫提供不同的目標物件,那麼可以使用org.springframework.aop.target.ThreadLocalTargetSource。它可以保證各自執行緒上目標物件的呼叫,可以被分配到當前執行緒對應的那個目標物件的例項上。其實,ThreadLocalTargetSource無非就是對JDK標準的ThreadLocal進行了簡單的封裝而已。

  2. 自定義TargetSource

package org.springframework.mylearntest.aop.weaver.selfdefinetargetsource;

import org.springframework.aop.TargetSource;
import org.springframework.mylearntest.aop.weaver.baseoninterface.ITask;

/**
 * @Author: whalefall
 * @Date: 2020/7/27 22:27
 */
@SuppressWarnings("rawtypes")
public class AlternativeTargetSource implements TargetSource {
	private ITask alternativeTask1;
	private ITask alternativeTask2;

	private int counter;

	public AlternativeTargetSource(ITask task1, ITask task2) {
		this.alternativeTask1 = task1;
		this.alternativeTask2 = task2;
	}

	@Override
	public Object getTarget() throws Exception {
		try {
			if (counter % 2 == 0)
				return alternativeTask2;
			else
				return alternativeTask1;
		} finally {
			counter ++;
		}
	}

	@Override
	public  Class getTargetClass() {
		return ITask.class;
	}

	@Override
	public boolean isStatic() {
		return false;
	}

	@Override
	public void releaseTarget(Object arg0) throws Exception {

	}
}
package org.springframework.mylearntest.aop.weaver.selfdefinetargetsource;

import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.mylearntest.aop.weaver.baseoninterface.ITask;

import java.util.Date;

/**
 * @Author: whalefall
 * @Date: 2020/7/27 22:33
 */
public class Test4AlternativeTargetSource {
	public static void main(String[] args) {
		ITask task1 = new ITask() {
			@Override
			public void execute(Date date) {
				System.out.println("execute in Task1");
			}
		};

		ITask task2 = new ITask() {
			@Override
			public void execute(Date date) {
				System.out.println("execute in Task2");
			}
		};

		ProxyFactory pf = new ProxyFactory();
		TargetSource targetSource = new AlternativeTargetSource(task1,task2);
		pf.setTargetSource(targetSource);
		Object proxy = pf.getProxy();
		for (int i = 0; i < 100; i++) {
			((ITask)proxy).execute(new Date());
		}
	}
}

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

相關文章