天天看點

深入了解Spring AOP 1.0

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

AOP語言

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

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

JAVA中AOP實作方式

  1. 動态代理
  • 在運作期間,為相應的接口動态生成對應的代理對象,将橫切關注點邏輯封裝到動态代理的InvocationHandler中,然後在系統運作期間,根據橫切關注點需要織入的子產品位置,将橫切邏輯織入到相應的代理類中。
  1. 動态位元組碼增強
  • 使用ASM或者CGLIB等Java工具庫,在程式運作期間,動态建構位元組碼的class檔案。
  • 在運作期間通過動态位元組碼增強技術織入橫切邏輯,為這些系統子產品類生成相應的子類,而将橫切邏輯加到這些子類中,讓應用程式的執行期間使用的是這些動态生成的子類,進而達到将橫切邏輯織入系統的目的。
  • 如果需要擴充的類以及類中的執行個體方法等聲明為final的話,則無法對其進行子類化的擴充。Spring AOP在無法使用動态代理機制進行AOP功能的擴充的時候,會使用CGLIB庫的動态位元組碼增強技術來實作AOP的擴充。
  1. java代碼生成
  • EJB容器根據部署描述符檔案提供了織入資訊,會為相應的功能子產品類根據描述符所提供的資訊生成對應的java代碼,然後通過部署工具或者部署接口編譯java代碼生成對應的java類。之後部署到EJB容器的功能子產品類就可以正常工作了。
  1. 自定義類加載器
  • 所有的java程式的class都要通過相應的類加載器(Classloader)加載到Java虛拟機之後才可以運作。預設的類加載器會讀取class位元組碼檔案,然後按照class位元組碼規範,解析并加載這些class

    檔案到虛拟機運作。如果我能夠在這個class加載到虛拟機運作期間,将橫切邏輯織入到class檔案的話,是不是就完成了AOP和OPP的融合呢?

  • 我們可以通過自定義類加載器的方式完成橫切邏輯到系統的織入,自定義類加載器通過讀取外部檔案規定的織入規則和必要資訊,在加載class檔案期間就可以将橫切邏輯添加到系統子產品類的現有邏輯中,然後将改動後的class交給java

    虛拟機運作。通過類加載器,我們基本可以對大部分類以及相應的執行個體進行織入,功能于之前的幾種方式相比當然強大很多。不過這種方式最大的問題就是類加載器本身的使用。某些應用伺服器會控制整個的類加載體系,是以,在這樣的場景下會造成一定的問題。

  • Jboss AOP 和已經并入AspectJ項目的AspectWerkz架構都是采用自定義類加載器的方式實作。
  1. AOL擴充
  • AOL擴充是最強大、也是最難掌握的一種方式,我們之前提到AspectJ就屬于這種方式。在這種方式中,AOP的各種概念在AOL中大都有一一對應的實體。我們可以使用擴充過的AOL,實作任何AOP概念實體甚至OPP

    概念實體,比如Aspect以及Class。所有的AOP概念在AOL中得到了最完美的表達。

  • 采用擴充的AOL,在AOP概念的表達上頗具執行個體,使得AOP涉及的所有橫切關注點邏輯在進行織入之前,可以自由自在地存活在自己的“國度中”。而像“編譯到靜态類可以提升系統運作性能”,“java

    虛拟機可以像加載平常類那種,加載已經織入相應邏輯的AO元件所在的檔案并運作”等特點。使用這種方式,需要學習一門擴充的AOL語言。

深入了解Spring AOP 1.0

Joinpoint

  • Joinpoint 切點
  • Pointcut 切點表達式
    • 直接指定Joinpoint所在的方法名稱
    • 正規表達式:Jboss、Spring AOP、AspectWerkz等均支援
    • 使用特定的Pointcut表達語言:Spring 2.0以後,借助于AspectJ的Pointcut表述語言解釋器,支援使用AspectJ的Pointcut表述語言來指定Pointcut。
深入了解Spring AOP 1.0

靜态代理

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();// 讓代理者去處理請求
	}
}
           

  • 動态代理機制主要由一個類和一個接口組成,即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》。

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();
	}
}
           

Spring AOP中的Pointcut

如果Pointcut類型為TruePointcut,預設會對系統中的所有對象,以及對象上所有被支援的Joinpoint進行比對。

Pointcut 源碼

package org.springframework.aop;

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	Pointcut TRUE = TruePointcut.INSTANCE;

}
           

TruePointcut 源碼

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;

}
           

MethodMatcher 源碼

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,該結果已經是最終結果,三個參數的方法肯定不會被執行了。

深入了解Spring AOP 1.0
深入了解Spring AOP 1.0

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" });
           
  • 此方法無法對重載的方法名進行比對,因為它僅對方法名進行比對,不會考慮參數相關資訊,而且也沒有提供可以指定參數比對資訊的途徑。

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. 指定正規表達式比對模式應該覆寫比對整個方法簽名,而不是隻指定到方法名稱部分。

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);
           

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);

	}
}
           

ControlFlowPointcut

ControlFlowPointcut比對程式的調用流程,不是對某個方法執行所在Joinpoint處的單一特征進行比對,而是要被特定的類執行時,才會進行方法攔截。

因為ControlFlowPointcut類型的Pointcut 需要在運作期間檢查程式的調用棧,而且每次方法調用都需要檢查,是以性能比較差。

Spring AOP中的Advice

  1. Before Advice
  2. After Advice
  • After returning
  • After throwing
  • After Advice(finally)
  1. After Around
深入了解Spring AOP 1.0
  1. Introduction
  • 在AspectJ中稱Inter-Type Declaration,在JBoss AOP 中稱Mix-in,都是指這同一種類型的Advice。與之前的幾種Advice類型不同,Introduction

    不是根據橫切邏輯在Joinpoint處的執行時機來區分的,而是根據它可以完成的功能而差別于其他Advice類型。

  • AspectJ采用靜态織入的形式,那麼對象在使用的時候,Introduction邏輯已經是編譯織入完成的。是以理論上來說,AspectJ提供的Introduction類型的Advice,在現有Java平台上的AOP

    實作中是性能最好的;而像JBosss AOP或者Spring AOP等采用動态織入的AOP實作,Introduction的性能要稍遜一籌。

深入了解Spring AOP 1.0

在Spring中,Advice按照其自身執行個體能否在目标對象類的所有執行個體中共享這一标準,可以劃分為兩大類,即per-calss類型的Advice 和 per-instance類型的Advice。

per-class

per-class的Advice是指,該類型的Advice的執行個體可以在目标對象類的所有執行個體之間共享。這種類型的Advice通常隻是提供方法的攔截功能,不會對目标對象類儲存任何狀态或者添加新的特性。

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());
		}
	}
}
           

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) {
		// 處理應用程式生成的異常
	}
}
           

AfterReturningAdvice

此Advice可以通路到目前Joinpoint的方法傳回值、方法、方法參數以及所在的目标對象,但是不能更改傳回值,可以使用Around Advice來更改傳回值。

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()方法。目前被攔截的方法實際上是整個調用鍊中要最終執行的唯一方法。

深入了解Spring AOP 1.0
  • Introduction型的Advice兩條分支,即以DynamicIntroductionAdvice為首的動态分支(不共享)和以IntroductionInfo為首的靜态(共享)。
  • DynamicIntroductionAdvice不用預先設定目标接口類型;而IntroductionInfo則完全相反,實作類必須傳回預定目标接口的類型。
public interface IntroductionInfo {
    Class[] getInterfaces();
}
           

如果需要對目标對象攔截并添加Introduction邏輯,可以使用現有兩個實作類即可

DelegatingIntroductionInterceptor

DelegatePerTargetObjectIntroductionInterceptor

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 ...");
	}
}
           
  • 為新的狀态和行為定義接口。我們要為實作類添加增強的功能,首先需要将需求的職能以接口定義的形式聲明。
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public interface ITester {
	boolean isBusyAsTester();
	void testSoftware();
}
           
  • 給出新的接口的實作類。接口實作類給出将要添加到目标對象的具體邏輯。當目标對象要行使新的職能的時候,會通過該實作類尋求幫忙。
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 ...");
	}
}
           
  • 通過DelegatingIntroductionInterceptor進行Introduction攔截。有了新增加的職能的接口以及相應的實作類,使用DelegatingIntroductionInterceptor

    ,我們可以把具體的Introduction攔截委托給具體的實作類來完成。

ITester delegator = new Tester();
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(delegator);
		
// 進行織入
ITester tester = (ITester)weaver.weave(developer).with(interceptor).getProxy();
tester.testSoftware();
           
  • 雖然,DelegatingIntroductionInterceptor是Introduction型Advice的一個實作,但它的實作根本就有兌現Introduction作為per-instance

    型的承諾。實際上DelegatingIntroductionInterceptor會使用它所持有的同一個"delegate" 接口執行個體,供同一目标類的所有執行個體共享使用。如果要想嚴格達到Introduction型Advice的效果,我們應該使用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

Aspect是對系統中的橫切關注點邏輯進行子產品化封裝的AOP的概念實體。通常情況下,Aspect可以包含多個Pointcut以及相關Advice定義。

深入了解Spring AOP 1.0

Advisor代表Spring中的Aspect,但是與正常的Aspect不同,Advisor通常隻持有一個Pointcut和一個Advice。而理論上,Aspect定義中可以有多個Pointcut和多個Advice,是以Advisor是一種特殊的Aspect。

PointcutAdvisor

深入了解Spring AOP 1.0
深入了解Spring AOP 1.0

實際上,org.springframework.aop.PointcutAdvisor才是真正定義的有一個Pointcut和一個Advice的Advisor,大部分的Advisor實作全部是在PointcutAdvisor下的。

DefaultPointcutAdvisor

<beans>
	<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>
</beans>
           

NameMatchMethodPointcutAdvisor

  • 此類内部持有一個NameMatchMethodPointcut類型的Pointcut執行個體。當通過NameMatchMethodPointcutAdvisor公開的setMappedName和setMappedNames方法設定将要被攔截的方法名的時候,實際上是在操作NameMatchMethodPointcutAdvisor内部的NameMatchMethodPointcut執行個體。
Advice advice = new DiscountMethodInterceptor();
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
advisor.setMappedName("invoke");
           
RegexpMethodPointcutAdvisor

隻能通過正規表達式為其設定相應的Pointcut,内部持有一個AbstractRegexpMethodPointcut的執行個體。AbstractRegexpMethodPointcut有兩個實作類,Perl5RegexpMethodPointcutAdvisor和JdkRegexpMethodPointcut。預設使用JdkRegexpMethodPointcut,如果強制使用Perl5RegexpMethodPointcutAdvisor,那麼可以通過RegexpMethodPointcutAdvisor的setPerl5(boolean)實作。

DefaultBeanFactoryPointcutAdvisor

DefaultBeanFactoryPointcutAdvisor自身綁定到了BeanFactory,要使用DefaultBeanFactoryPointcutAdvisor,要綁定到Spring IoC容器。通過容器中的Advice注冊的beanName來關聯對應的Advice。隻有當對應的Pointcut比對成功之後,采取執行個體化對應的Advice,減少了容器啟動初期Advisor和Advice之間的耦合性。

IntroductionAdvisor

IntroductionAdvisor隻能應用于類級别的攔截,隻能使用Introduction型的Advice,而不能像PointcutAdvisor那樣,可以使用任意類型的Pointcut,以及差不多任何類型的Advice。

深入了解Spring AOP 1.0

Order

大多數時候,會有多個關注橫切點,那麼,系統實作中就會有多個Advisor存在。當其中的某些Advisor的Pointcut比對了同一個Joinpoint的時候,就會在這同一個Joinpoint處執行多個Advice的橫切邏輯。一旦這幾個需要在同一個Joinpoint處執行的Advice邏輯存在優先順序依賴的話,就需要我們來幹預了。

深入了解Spring AOP 1.0

Spring在處理同一Joinpoint處的多個Advisor的時候,會按照指定的順序有優先級來執行他們。順序号越小,優先級越高,優先級越高的,越先被執行。(預設情況下,Spring會按照它們的聲明順序來應用它們,最先聲明的順序号最小但優先級最大,其次次之)

深入了解Spring AOP 1.0

各個Advisor實作類,其實已經實作了Order接口。在使用的時候我們可以直接指定即可

<beans>
    <bean id="permissionAuthAdvisor" class="...PermissionAuthAdvisor">
    	<property name="order" value="1"/>
        ...
    </bean>
    
    <bean id="exceptionBarrierAdvisor" class="...ExceptionBarrierAdvisor">
    	<property name="order" value="0"/>
        ...
    </bean>
</beans>
           

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);

  1. 擷取代理對象

    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 1.0

Spring AOP架構内使用AopProxy對使用的不用的代理實作機制進行了适度的抽象,主要有針對JDK動态代理和CGLIB兩種機制的AopProxy兩種實作,分别是Cglib2AopProxy和JdkDynamicAopProxy兩種實作。動态代理需要通過InvocationHandler提供調用攔截,是以JdkDynamicAopProxy同時實作了InvocationHandler接口。采用抽象工廠模式,通過org.springframework.aop.framework.AopProxyFactory進行。

public 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])));
	}

}
           
深入了解Spring AOP 1.0

AdvisedSupport是一個生成代理對象所需要的資訊的載體。

  • 一類以org.springframework.aop.framework.ProxyConfig為首的,記載生成代理對象的控制資訊;
  • 一類以org.springframework.aop.framework.Advised為首,承載生成代理對象的所需要的必要資訊,比如相關目标類、Advice、Advisor等。

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

  • ProxyTargetClass:如果這個屬性設定如true,則ProxyFactory将會使用CGLIB對目标對象進行代理。預設值為false。
  • optimize:該屬性主要用于告知代理對象是否需要采取進一步的優化措施。如果代理對象生成之後,即使為其添加或者移除了相應的人Advice,代理對象也可以忽略這種變動。如果這個屬性設定如true,則ProxyFactory

    将會使用CGLIB對目标對象進行代理。預設值為false。

  • opaque:該屬性用于控制生成的代理對象是否可以強制轉化為Advised,預設值為false,表示任何生成的代理對象都可以強制轉型為Advised,我們可以通過Advised查詢代理對象的一些狀态。
  • exposeProxy:設定exposeProxy,可以讓Spring AOP架構在生成代理對象時,将目前代理對象綁定到ThreadLocal。如果目标對象需要通路目前代理對象,可以通過AopContext

    .currentProxy()拿到代理對象。出于性能方面考慮,該屬性預設為false。

  • frozen:如果将frozen設定為true,那麼一旦針對dialing對象生成的各項資訊配置完成,則不容許更改。比如ProxyFactory的設定完畢,并且frozen為true,則不能對Advice

    進行任何變動,這樣可以優化代理對象的性能,預設情況下為false。

要生成代理對象,隻有ProxyConfig提供的資訊還不夠,我們還需要生成代理對象的一些具體資訊,比如,要針對哪些目标類生成代理對象,要為代理對象加入何種橫切邏輯等,這些資訊可以通過org.springframework.aop.framework.Advised設定或者拆線呢。預設情況下Spring AOP架構傳回的代理對象都可以強制轉型為Advised,已查詢代理對象的相關資訊。

我們可以使用Advised接口通路相應代理對象所有持有的Advisor,進行添加Advisor、一處Advisor等相關動作。即使代理對象已經生成完畢,也可對其進行操作,直接操作Advised,更多時候用于測試場景,可以幫助我們檢查生成的代理對象是否如所期望的那樣。

深入了解Spring AOP 1.0

ProxyFactory集AopProxy和AdvisedSupport于一身,可以通過AdvisedSupport設定生成代理對象所需要的相關資訊,可以通過AopProxy生成代理對象。為了重用相關邏輯,Spring AOP架構在實作的時候,将一些公用的邏輯抽取到了org.springframework.aop.frameworkx.ProxyCreatorSupport中,自身繼承了AdvisedSupport,是以就能具有設定生成代理對象所需要的相關資訊。

為了簡化生成不同類型AopProxy的工作,ProxyCreatorSupport内部持有一個AopProxyFactory執行個體,預設采用的是DefaultAopProxyFactory。

深入了解Spring AOP 1.0

ProxyFactoryBean

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,在生成代理對象的性能上存在損失。

ProxyFactoryBean的使用

與ProxyFactory一樣,通過ProxyFactoryBean,我們可以在生成目标對象的代理對象的時候,指定使用基于接口的代理還是基于類的代理方式,而且,因為它們全部繼承自同一個父類,大部分設定項目都相同。ProxyFactoryBean在繼承了ProxyCreatorSupport的所有配置屬性之外還添加了自己獨有的:

proxyInterfaces:如果我們要采用基于接口的代理方式,那莪需要通過該屬性配置相應的接口類型,通過Collection對象傳入配置元素的接口資訊。ProxyFactoryBean

有一個autodetectInterfaces屬性,該屬性預設為true,如果沒有明确指定要代理的接口類型,ProxyFactoryBean會自動檢測目标對象實作的接口類型并進行代理。

interceptorNames:通過該屬性,我們可以指定多個将要織入到目标對象的Advice、攔截器以及Advisor,而再也不通過ProxyFactory那樣的addAdvice或者addAdvisor

方法添加,通常我們會使用配置元素添加需要的攔截器名稱。

  • 如果沒有設定目标對象,那麼可以在interceptorNames的最後一個元素的位置,放置對象的Bean定義名稱。建議直接定義目标對象,不采用前面的方法。
  • 通過指定的interceptorNames某個元素名稱之後添加*通配符,可以讓ProxyFactoryBean在容器中搜尋符合條件的所有Advisro并應用到目标對象。

使用通配符的範例

<beans>
    <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">
</beans>
           

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,将指定的一組攔截器應用到這些目标對象之上。

  • 配置案例
<beans>
    <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>
</beans>
           
  1. DefaultAdvisorAutoProxyCreator

隻需要在ApplicationContext中注冊Bean即可,剩下的任務會由DefaultAdvisorAutoProxyCreator完成。将其注入容器之後,将會自動搜尋容器内的所有Advisor,然後根據各個Advisor所提供的攔截資訊,為符合條件的容器中的目标對象生成相應的代理對象。DefaultAdvisorAutoProxyCreator隻對Advisor有效,因為隻有Advisor才既有Pointcut資訊捕捉符合條件的目标對象,又有相應的Advice。

<beans>
	<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>
</beans>
           
  1. 擴充AutoProxyCreator

可以在Spring AOP提供的AbstractAutoProxyCreator或者AbstractAdvisorAutoProxyCreator基礎之上,實作相應的子類。

Sprig AOP架構中有關自動代理的實作架構

  • 所有的AutoProxyCreator都是InstantiationAwareBeanPostProcessor,這種類型的BeanPostProcessor與普通的BeanPostProcessor有所不同。當Spring IoC

    容器檢測到有InstantiationAwareBeanPostProcessor類型的BeanPostProcessor的時候,會直接通過InstantiationAwareBeanPostProcessor中的邏輯構造對象執行個體傳回,而不會走正常的對象執行個體化流程。也就是“短路”。這樣AutoProxyCreator會直接構造目标對象的代理對象傳回,而不是原來的目标對象。

深入了解Spring AOP 1.0

AspectJAwareAdvisorAutoProxyCreator是Spring 2.0之後的AutoProxyCreator實作,也算是一個AutoProxyCreator的自定義實作。它還有一個子類AnnotationAwareAspectJAutoProxyCreator,可以根據Java5的注解捕獲資訊以完成自動代理。

Spring AOP還支援基于Jakarta Commons Atrributes的中繼資料的自動代理機制,來提供攔截資訊。

TargetSource

深入了解Spring AOP 1.0

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

TargetSource的特性

每次方法調用都會觸發TargetSource的getTarget()方法,getTarget()方法将從相應的TargetSource實作類中取得具體的目标對象,這樣,我們就可以控制每次方法調用作用到的具體對象執行個體。

  • 提供一個目标對象池,每次從TargetSource取得的目标對象都從這個目标對象池中取得。
  • 讓一個TargetSource實作類持有多個目标對象的執行個體,然後按照某種規則,在每次方法調用時,傳回相應的目标對象執行個體。

還可以讓TargetSource隻持有一個目标對象,通常ProxyFactory或者ProyxFactoryBean處理目标對象的方式也是如此,它們内部會構造一個org.springframework.aop.target

.SingletonTargetSource執行個體,而SingletonTargetSource則會針對每次方法調用傳回同一個目标對象的執行個體引用。

TargetSource實作類

SingletonTargetSource

org.springframework.aop.target.SingletonTargetSource是使用最多的TargetSource實作類,雖然我們可能并不知道。因為通過ProxyFactory的setTarget()設定完目标對象之後,ProxyFactory内部會自行使用一個SingletonTargetSource對設定的目标對象進行封裝。

深入了解Spring AOP 1.0

PrototypeTargetSource

<beans>
    <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>
</beans>
           

目标對象的bean定義聲明必須為prototype。

通過targetBeanName屬性指定目标對象的bean定義名稱,而不是引用。

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);
	}
}
           

CommonsPoolTargetSource

某些時候,我們可能想傳回有限數目的目标對象執行個體,這些目标對象執行個體的地位是平等的,就好像資料庫連接配接池中的那些Connection一樣,我們可以提供一個目标對象的對象池,然後讓某個TargetSource實作每次都從這個對象池中取得目标對象。

如果不能使用Jakarta Commons Pool,那麼也可以通過擴充org.springframework.aop.target.AbstractPoolingTargetSource類,實作相應的提供對象池化的功能的TargetSource。

ThreadLocalTargetSource

如果想為不同的線程調用提供不同的目标對象,那麼可以使用org.springframework.aop.target.ThreadLocalTargetSource。它可以保證各自線程上目标對象的調用,可以被配置設定到目前線程對應的那個目标對象的執行個體上。其實,ThreadLocalTargetSource無非就是對JDK标準的ThreadLocal進行了簡單的封裝而已。

自定義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());
		}
	}
}
           
參考資料
  1. 書籍名稱:Spring揭秘 作者:王福強
  2. https://github.com/spring-projects/spring-framework.git
轉載請注明 原文位址
上一篇: CSS學習
下一篇: CSS學習