SpringAOP中的Advisor詳解
前言
平時我們項目中涉及到 AOP,基本上就是聲明式配置一下就行了,無論是基于 XML 的配置還是基于 Java 代碼的配置,都是簡單配置即可使用。聲明式配置有一個好處就是對源代碼的侵入小甚至是零侵入。不過今天松哥要和小伙伴們聊一聊編程式的 AOP,為什么要聊這個話題呢?因為在 Spring 源碼中,底層就是通過這種方式創(chuàng)建代理對象的,所以如果自己會通過編程式的方式進行 AOP 開發(fā),那么在看 Spring 中相關源碼的時候,就會很好理解了。
1. 基本用法
1.1 基于 JDK 的 AOP
我們先來看基于 JDK 動態(tài)代理的 AOP。
假設我有如下一個計算器接口:
public interface ICalculator { void add(int a, int b); int minus(int a, int b); }
然后給這個接口提供一個實現(xiàn)類:
public class CalculatorImpl implements ICalculator { @Override public void add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); } @Override public int minus(int a, int b) { return a - b; } }
現(xiàn)在假設我要生成一個代理對象,利用編程式的方式,代碼如下:
ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(new CalculatorImpl()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name+" 方法開始執(zhí)行了。。。"); Object proceed = invocation.proceed(); System.out.println(name+" 方法執(zhí)行結束了。。。"); return proceed; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add(3, 4);
這里幾個方法應該都好理解:
- setTarget 方法是設置真正的代理對象。這個在之前大家已經(jīng)接觸過了。
- addInterface,基于 JDK 的動態(tài)代理是需要有接口的,這個方法就是設置代理對象的接口。
- addAdvice 方法就是添加增強/通知。
- 最后通過 getProxy 方法獲取到一個代理對象然后去執(zhí)行。
最終打印結果如下:
1.2 基于 CGLIB 的 AOP
如果被代理的對象沒有接口,那么可以通過基于 CGLIB 的動態(tài)代理來生成代理對象。
假設我有如下類:
public class UserService { public void hello() { System.out.println("hello javaboy"); } }
要給這個類生成代理對象,如下:
ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(new UserService()); proxyFactory.addAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { String name = invocation.getMethod().getName(); System.out.println(name+" 方法開始執(zhí)行了。。。"); Object proceed = invocation.proceed(); System.out.println(name+" 方法執(zhí)行結束了。。。"); return proceed; } }); UserService us = (UserService) proxyFactory.getProxy(); us.hello();
其實道理很簡單,沒有接口就不設置接口就行了。
1.3 源碼分析
在上面生成代理對象的 getProxy 方法中,最終會執(zhí)行到 createAopProxy 方法,在該方法中會根據(jù)是否有接口來決定是使用 JDK 動態(tài)代理還是 CGLIB 動態(tài)代理:
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
從這段源碼中可以看到,有接口就是 JDK 動態(tài)代理,沒有接口則是 CGLIB 動態(tài)代理。不過在最上面有一個 if 判斷,這個判斷中有三個條件,分別來和小伙伴們說一下:
config.isOptimize()
這個方法是判斷是否需要優(yōu)化。因為傳統(tǒng)上大家都認為 CGLIB 動態(tài)代理性能高于 JDK 動態(tài)代理,不過這些年 JDK 版本更新也是非??欤F(xiàn)在兩者性能差異已經(jīng)不大了。如果這個屬性設置為 true,那么系統(tǒng)就會去判斷是否有接口,有接口就 JDK 動態(tài)代理,否則就 CGLIB 動態(tài)代理。
如果需要設置該屬性,可以通過如下代碼設置:
proxyFactory.setOptimize(true);
config.isProxyTargetClass()
這個屬性作用也是類似,我們平時在使用 AOP 的時候,有時候也會設置這個屬性,這個屬性如果設置為 true,則會進入到 if 分支中,但是 if 分支中的 if 則不宜滿足,所以一般情況下,如果這個屬性設置為 true,就意味著無論是否有接口,都使用 CGLIB 動態(tài)代理。如果這個屬性為 false,則有接口就使用 JDK 動態(tài)代理,沒有接口就使用 CGLIB 動態(tài)代理。
hasNoUserSuppliedProxyInterfaces(config)
這個方法主要做兩方面的判斷:
- 當前代理對象如果沒有接口,則直接返回 true。
- 當前代理對象有接口,但是接口是 SpringProxy,則返回 true。
返回 true 基本上就意味著要使用 CGLIB 動態(tài)代理了,返回 false 則意味著使用 JDK 動態(tài)代理。
如果是基于 JDK 的動態(tài)代理,那么最終調用的就是 JdkDynamicAopProxy#getProxy() 方法,如下:
@Override public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); } @Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this); }
Proxy.newProxyInstance
這就是 JDK 里邊的動態(tài)代理了,這很好懂。
如果是基于 CGLIB 的動態(tài)代理,那么最終調用的就是 CglibAopProxy#getProxy() 方法,如下:
@Override public Object getProxy() { return buildProxy(null, false); } private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) { try { Class<?> rootClass = this.advised.getTargetClass(); Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); Class<?> proxySuperClass = rootClass; if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { proxySuperClass = rootClass.getSuperclass(); Class<?>[] additionalInterfaces = rootClass.getInterfaces(); for (Class<?> additionalInterface : additionalInterfaces) { this.advised.addInterface(additionalInterface); } } // Validate the class, writing log messages as necessary. validateClassIfNecessary(proxySuperClass, classLoader); // Configure CGLIB Enhancer... Enhancer enhancer = createEnhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); if (classLoader instanceof SmartClassLoader smartClassLoader && smartClassLoader.isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } } enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setAttemptLoad(true); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader)); Callback[] callbacks = getCallbacks(rootClass); Class<?>[] types = new Class<?>[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } // fixedInterceptorMap only populated at this point, after getCallbacks call above enhancer.setCallbackFilter(new ProxyCallbackFilter( this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); enhancer.setCallbackTypes(types); // Generate the proxy class and create a proxy instance. return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks)); } catch (CodeGenerationException | IllegalArgumentException ex) { throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (Throwable ex) { // TargetSource.getTarget() failed throw new AopConfigException("Unexpected AOP exception", ex); } }
關于直接使用 JDK 創(chuàng)建動態(tài)代理對象和直接使用 CGLIB 創(chuàng)建動態(tài)代理對象的代碼我就不做過多介紹了,這些都是基本用法,松哥在之前錄制的免費的 SSM 入門教程中都和小伙伴們講過了,這里就不啰嗦了。
2. Advisor
2.1 Advisor
Advisor = Pointcut+Advice。
前面的案例我們只是設置了 Advice,沒有設置 Pointcut,這樣最終攔截下來的是所有方法。
如果有需要,我們可以直接設置一個 Advisor,這樣就可以指定需要攔截哪些方法了。
我們先來看一下 Advisor 的定義:
public interface Advisor { Advice EMPTY_ADVICE = new Advice() {}; Advice getAdvice(); boolean isPerInstance(); }
可以看到,這里主要的就是 getAdvice 方法,這個方法用來獲取一個通知/增強。另外一個 isPerInstance 目前并沒有使用,默認返回 true 即可。在具體實踐中,我們更關注它的一個子類:
public interface PointcutAdvisor extends Advisor { Pointcut getPointcut(); }
這個子類多了一個 getPointcut 方法,PointcutAdvisor 這個接口很好的詮釋了 Advisor 的作用:Pointcut+Advice。
2.2 Pointcut
Pointcut 又有眾多的實現(xiàn)類:
挑兩個有意思的說一下,其他的其實也都差不多。
2.2.1 Pointcut
首先我們先來看下這個接口:
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); Pointcut TRUE = TruePointcut.INSTANCE; }
接口里邊有兩個方法,看名字大概也能猜出來意思:
- getClassFilter:這個是類的過濾器,通過這個可以刷選出來要攔截的類。
- MethodMatcher:這個是方法過濾器,通過這個可以刷選出來需要攔截的方法。
至于 ClassFilter 本身其實就很好懂了:
@FunctionalInterface public interface ClassFilter { boolean matches(Class<?> clazz); ClassFilter TRUE = TrueClassFilter.INSTANCE; }
就一個 matches 方法,傳入一個 Class 對象,然后執(zhí)行比較即可,返回 true 就表示要攔截,返回 false 則表示不攔截。
MethodMatcher 也類似,如下:
public interface MethodMatcher { boolean matches(Method method, Class<?> targetClass); boolean isRuntime(); boolean matches(Method method, Class<?> targetClass, Object... args); MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; }
這里三個方法,兩個是做匹配的 matches 方法,當 isRuntime 方法返回 true 的時候,才會執(zhí)行第二個帶 args 參數(shù)的 matches 方法。
舉個簡單的使用案例,假設我現(xiàn)在要攔截所有方法,那么我可以按照如下方式定義:
public class AllClassAndMethodPointcut implements Pointcut { @Override public ClassFilter getClassFilter() { return ClassFilter.TRUE; } @Override public MethodMatcher getMethodMatcher() { return MethodMatcher.TRUE; } }
這是自帶的兩個常量,表示攔截所有類和所有方法。
再假如,我要攔截 CalculatorImpl 類的 add 方法,那么我可以按照如下方式來定義:
public class ICalculatorAddPointcut implements Pointcut { @Override public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { return clazz.getName().equals("org.javaboy.bean.aop.CalculatorImpl"); } }; } @Override public MethodMatcher getMethodMatcher() { NameMatchMethodPointcut matcher = new NameMatchMethodPointcut(); matcher.addMethodName("add"); return matcher; } }
2.2.2 AspectJExpressionPointcut
我們平時寫 AOP,比較常用的是通過表達式來定義切面,那么這里就可以使用 AspectJExpressionPointcut,這是一個類,所以可以不用繼承新類,直接使用創(chuàng)建使用即可,如下:
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression("execution(* org.javaboy.bean.aop.ICalculator.add(..))");
如上切點就表示攔截 ICalculator 類中的 add 方法。
2.3 Advice
這個好說,就是增強/通知,在本文第 1.1、1.2 小節(jié)中均已演示過,不再贅述。
2.4 Advisor 實踐
接下來松哥通過一個案例來和小伙伴們演示一下如何添加一個 Advisor:
ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(new CalculatorImpl()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor(new PointcutAdvisor() { @Override public Pointcut getPointcut() { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression("execution(* org.javaboy.bean.aop.ICalculator.add(..))"); return pointcut; } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name + " 方法開始執(zhí)行了。。。"); Object proceed = invocation.proceed(); System.out.println(name + " 方法執(zhí)行結束了。。。"); return proceed; } }; } @Override public boolean isPerInstance() { return true; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add(3, 4); calculator.minus(3, 4);
在 getPointcut 方法中,可以返回 3.2 小節(jié)中不同的切點,都是 OK 沒有問題的。getAdvice 就是前面定義的通知。
其實在本文的 1.1、1.2 小節(jié)中,我們直接添加了 Advice 而沒有配置 Advisor,我們自己添加的 Advice 在內部也是被自動轉為了一個 Advisor,相關源碼如下:
@Override public void addAdvice(Advice advice) throws AopConfigException { int pos = this.advisors.size(); addAdvice(pos, advice); } /** * Cannot add introductions this way unless the advice implements IntroductionInfo. */ @Override public void addAdvice(int pos, Advice advice) throws AopConfigException { if (advice instanceof IntroductionInfo introductionInfo) { addAdvisor(pos, new DefaultIntroductionAdvisor(advice, introductionInfo)); } else if (advice instanceof DynamicIntroductionAdvice) { // We need an IntroductionAdvisor for this kind of introduction. throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor"); } else { addAdvisor(pos, new DefaultPointcutAdvisor(advice)); } }
小伙伴們看到,我們傳入的 Advice 對象最終被轉為一個 DefaultPointcutAdvisor 對象,然后調用了 addAdvisor 方法進行添加操作。
public DefaultPointcutAdvisor(Advice advice) { this(Pointcut.TRUE, advice); }
可以看到,在 DefaultPointcutAdvisor 初始化的時候,設置了 Pointcut.TRUE
,也就是所有類的所有方法都會被攔截。
也就是 Advice 最終都會被轉為 Advisor。
到此這篇關于SpringAOP中的Advisor詳解的文章就介紹到這了,更多相關Spring中的Advisor內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用restTemplate.postForEntity()的問題
這篇文章主要介紹了使用restTemplate.postForEntity()的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09Java數(shù)據(jù)長度獲取方式對比之length屬性、length()和size()方法詳解
在Java編程語言中l(wèi)ength、length()和size()是三個常見的用來獲取不同數(shù)據(jù)類型對象長度或大小的方法,但它們各自適用于不同的上下文,這篇文章主要給大家介紹了關于Java數(shù)據(jù)長度獲取方式對比之length屬性、length()和size()方法詳解2024-07-07Java中Queue的poll()和remove()區(qū)別詳解
這篇文章主要介紹了Java中Queue的poll()和remove()區(qū)別詳解,Queue接口提供了許多方法,其中poll()和remove()是兩個常用的方法,它們的區(qū)別在于,當隊列為空時,poll()方法返回null,而remove()方法會拋出,需要的朋友可以參考下2023-07-07老生常談spring boot 1.5.4 日志管理(必看篇)
下面小編就為大家?guī)硪黄仙U剆pring boot 1.5.4 日志管理(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06spring boot使用自定義的線程池執(zhí)行Async任務
這篇文章主要介紹了spring boot使用自定義的線程池執(zhí)行Async任務的相關資料,需要的朋友可以參考下2018-02-02