1. 程式人生 > 實用技巧 >函式基礎2

函式基礎2

SpringAop

統稱能夠實現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

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

Order的作用
  • 大多數時候,會有多個關注橫切點,那麼,系統實現中就會有多個Advisor存在。當其中的某些Advisor的Pointcut匹配了同一個Joinpoint的時候,就會在這同一個Joinpoint處執行多個Advice的橫切邏輯。一旦這幾個需要在同一個Joinpoint處執行的Advice邏輯存在優先順序依賴的話,就需要我們來干預了。
  • Spring在處理同一Joinpoint處的多個Advisor的時候,會按照指定的順序有優先順序來執行他們。順序號越小,優先順序越高,優先順序越高的,越先被執行。(預設情況下,Spring會按照它們的宣告順序來應用它們,最先宣告的順序號最小但優先順序最大,其次次之)
  • 各個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);

	}
}

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