深入理解Spring AOP 1.0
- 2020 年 7 月 27 日
- 筆記
- Spring Framework, 讀書筆記
本文相關代碼(來自官方源碼spring-test模塊)請參見spring-demysify org.springframework.mylearntest包下。
統稱能夠實現AOP的語言為AOL,即(Aspect-Oriented Language),其他Aspectj
- AspectC
- AspectC++
- Aspect.Net
- AspectL(Lisp)
- AspectPHP
- ……
JAVA中AOP實現方式
-
動態代理
- 在運行期間,為相應的接口動態生成對應的代理對象,將橫切關注點邏輯封裝到動態代理的InvocationHandler中,然後在系統運行期間,根據橫切關注點需要織入的模塊位置,將橫切邏輯織入到相應的代理類中。
-
動態位元組碼增強
- 使用ASM或者CGLIB等Java工具庫,在程序運行期間,動態構建位元組碼的class文件。
- 在運行期間通過動態位元組碼增強技術織入橫切邏輯,為這些系統模塊類生成相應的子類,而將橫切邏輯加到這些子類中,讓應用程序的執行期間使用的是這些動態生成的子類,從而達到將橫切邏輯織入系統的目的。
- 如果需要擴展的類以及類中的實例方法等聲明為final的話,則無法對其進行子類化的擴展。Spring AOP在無法使用動態代理機制進行AOP功能的擴展的時候,會使用CGLIB庫的動態位元組碼增強技術來實現AOP的擴展。
-
java代碼生成
- EJB容器根據部署描述符文件提供了織入信息,會為相應的功能模塊類根據描述符所提供的信息生成對應的java代碼,然後通過部署工具或者部署接口編譯java代碼生成對應的java類。之後部署到EJB容器的功能模塊類就可以正常工作了。
-
自定義類加載器
- 所有的java程序的class都要通過相應的類加載器(Classloader)加載到Java虛擬機之後才可以運行。默認的類加載器會讀取class位元組碼文件,然後按照class位元組碼規範,解析並加載這些class文件到虛擬機運行。如果我能夠在這個class加載到虛擬機運行期間,將橫切邏輯織入到class文件的話,是不是就完成了AOP和OPP的融合呢?
- 我們可以通過自定義類加載器的方式完成橫切邏輯到系統的織入,自定義類加載器通過讀取外部文件規定的織入規則和必要信息,在加載class文件期間就可以將橫切邏輯添加到系統模塊類的現有邏輯中,然後將改動後的class交給java虛擬機運行。通過類加載器,我們基本可以對大部分類以及相應的實例進行織入,功能於之前的幾種方式相比當然強大很多。不過這種方式最大的問題就是類加載器本身的使用。某些應用服務器會控制整個的類加載體系,所以,在這樣的場景下會造成一定的問題。
- Jboss AOP 和已經併入AspectJ項目的AspectWerkz框架都是採用自定義類加載器的方式實現。
-
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 切面
-
- Before Advice
-
- After Advice
- After returning
- After throwing
- After Advice(finally)
-
- After Around
- After Around
-
- 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定義。
設計模式之代理模式
- 靜態代理
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();
}
}
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
- 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提供正則表達式支持,
- 可以通過pattern或者patterns對象屬性指定一個或者多個正則表達式
- 指定正則表達式匹配模式應該覆蓋匹配整個方法簽名,而不是只指定到方法名稱部分。
- 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
Spring 中各種Advice 和 Aop Alliance標準接口之間的關係。
- 在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()方法。當前被攔截的方法實際上是整個調用鏈中要最終執行的唯一方法。
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 ...");
}
}
- 為新的狀態和行為定義接口。我們要為實現類添加增強的功能,首先需要將需求的職能以接口定義的形式聲明。
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。
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下的。
- 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>
- 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。
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作為織入器。
- 使用方法
- 傳入需要織入的對象
ProxyFactory weaver = new ProxyFactory(target);
- 將要應用到目標對象的Advisor綁定到織入器上
- 如果不是Introduction的Advice類型,Proxy內部就會為這些Advice構造相應的Advisor,只不過在為它們構造Advisor中使用的Pointcut為Pointcut.TRUE。
- 如果是Introduction類型,則會根據該Introduction具體類型進行區分;如果是Introduction的子類實現,框架內部會為其構造一個DefaultIntroductionAdvisor;如果是DynamicIntroductionAdvice的子實現類,框架內部將拋出AOPConfigException異常(因為無法從DynamicIntroductionAdvice取得必要的目標對象信息)
weaver.addAdvisor(advisor);
- 獲取代理對象
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型的屬性,分別控制在生成代理代理對象的時候,應該採取哪些措施。
- 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,更多時候用於測試場景,可以幫助我們檢查生成的代理對象是否如所期望的那樣。
AopProxy、AdvisedSupport、ProxyFactory之間的關係
- ProxyFactory集AopProxy和AdvisedSupport於一身,可以通過AdvisedSupport設置生成代理對象所需要的相關信息,可以通過AopProxy生成代理對象。為了重用相關邏輯,Spring AOP框架在實現的時候,將一些公用的邏輯抽取到了org.springframework.aop.frameworkx.ProxyCreatorSuppport中,自身繼承了AdvisedSupport,所以就能具有設置生成代理對象所需要的相關信息。
- 為了簡化生成不同類型AopProxy的工作,ProxyCreatorSuppport內部持有一個AopProxyFactory實例,默認採用的是DefaultAopProxyFactory。
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並應用到目標對象。
- 使用通配符的範例
<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">
- singleton:ProxyFactoryBean本質上是一個FactoryBean,所以我們可以通過singleton屬性,指定getObject調用是返回同一個代理對象還是新的。
使用ProxyFactoryBean生成代理對象案例
- 配置文件
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:context="//www.springframework.org/schema/context"
xsi:schemaLocation="//www.springframework.org/schema/beans
//www.springframework.org/schema/beans/spring-beans-2.5.xsd
//www.springframework.org/schema/context
//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。
- 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>
- 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>
- 擴展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的getTarget()方法,getTarget()方法將從相應的TargetSource實現類中取得具體的目標對象,這樣,我們就可以控制每次方法調用作用到的具體對象實例。
TargetSource實現類
- SingletonTargetSource
- org.springframework.aop.target.SingletonTargetSource是使用最多的TargetSource實現類,雖然我們可能並不知道。因為通過ProxyFactory的setTarget()設置完目標對象之後,ProxyFactory內部會自行使用一個SingletonTargetSource對設置的目標對象進行封裝。
- 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定義名稱,而不是引用。
- HotSwappableTargetSource
- 使用HotSwappableTargetSource封存目標對象,可以讓我們在應用程序運行的時候,根據某種特定條件,動態地替換目標對象類的具體實現,比如,IService有多個實現類,如果程序啟動之後,默認的IService實現類出現了問題,我們可以馬上切換到Iservice的另一個實現上,而所有這些對於調用者來說都是透明的。
- 使用方法:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//www.springframework.org/schema/beans //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());
}
}
}
歡迎關注微信公眾號哦~ ~