SpringAOP中的Advisor詳解
前言
平時我們項(xiàng)目中涉及到 AOP,基本上就是聲明式配置一下就行了,無論是基于 XML 的配置還是基于 Java 代碼的配置,都是簡單配置即可使用。聲明式配置有一個好處就是對源代碼的侵入小甚至是零侵入。不過今天松哥要和小伙伴們聊一聊編程式的 AOP,為什么要聊這個話題呢?因?yàn)樵?Spring 源碼中,底層就是通過這種方式創(chuàng)建代理對象的,所以如果自己會通過編程式的方式進(jìn)行 AOP 開發(fā),那么在看 Spring 中相關(guān)源碼的時候,就會很好理解了。
1. 基本用法
1.1 基于 JDK 的 AOP
我們先來看基于 JDK 動態(tài)代理的 AOP。
假設(shè)我有如下一個計算器接口:
public interface ICalculator {
void add(int a, int b);
int minus(int a, int b);
}然后給這個接口提供一個實(shí)現(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)在假設(shè)我要生成一個代理對象,利用編程式的方式,代碼如下:
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í)行結(jié)束了。。。");
return proceed;
}
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3, 4);這里幾個方法應(yīng)該都好理解:
- setTarget 方法是設(shè)置真正的代理對象。這個在之前大家已經(jīng)接觸過了。
- addInterface,基于 JDK 的動態(tài)代理是需要有接口的,這個方法就是設(shè)置代理對象的接口。
- addAdvice 方法就是添加增強(qiáng)/通知。
- 最后通過 getProxy 方法獲取到一個代理對象然后去執(zhí)行。
最終打印結(jié)果如下:

1.2 基于 CGLIB 的 AOP
如果被代理的對象沒有接口,那么可以通過基于 CGLIB 的動態(tài)代理來生成代理對象。
假設(shè)我有如下類:
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í)行結(jié)束了。。。");
return proceed;
}
});
UserService us = (UserService) proxyFactory.getProxy();
us.hello();其實(shí)道理很簡單,沒有接口就不設(shè)置接口就行了。
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)化。因?yàn)閭鹘y(tǒng)上大家都認(rèn)為 CGLIB 動態(tài)代理性能高于 JDK 動態(tài)代理,不過這些年 JDK 版本更新也是非???,現(xiàn)在兩者性能差異已經(jīng)不大了。如果這個屬性設(shè)置為 true,那么系統(tǒng)就會去判斷是否有接口,有接口就 JDK 動態(tài)代理,否則就 CGLIB 動態(tài)代理。
如果需要設(shè)置該屬性,可以通過如下代碼設(shè)置:
proxyFactory.setOptimize(true);
config.isProxyTargetClass()
這個屬性作用也是類似,我們平時在使用 AOP 的時候,有時候也會設(shè)置這個屬性,這個屬性如果設(shè)置為 true,則會進(jìn)入到 if 分支中,但是 if 分支中的 if 則不宜滿足,所以一般情況下,如果這個屬性設(shè)置為 true,就意味著無論是否有接口,都使用 CGLIB 動態(tài)代理。如果這個屬性為 false,則有接口就使用 JDK 動態(tài)代理,沒有接口就使用 CGLIB 動態(tài)代理。
hasNoUserSuppliedProxyInterfaces(config)
這個方法主要做兩方面的判斷:
- 當(dāng)前代理對象如果沒有接口,則直接返回 true。
- 當(dāng)前代理對象有接口,但是接口是 SpringProxy,則返回 true。
返回 true 基本上就意味著要使用 CGLIB 動態(tài)代理了,返回 false 則意味著使用 JDK 動態(tài)代理。
如果是基于 JDK 的動態(tài)代理,那么最終調(diào)用的就是 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)代理,那么最終調(diào)用的就是 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);
}
}關(guān)于直接使用 JDK 創(chuàng)建動態(tài)代理對象和直接使用 CGLIB 創(chuàng)建動態(tài)代理對象的代碼我就不做過多介紹了,這些都是基本用法,松哥在之前錄制的免費(fèi)的 SSM 入門教程中都和小伙伴們講過了,這里就不啰嗦了。
2. Advisor
2.1 Advisor
Advisor = Pointcut+Advice。
前面的案例我們只是設(shè)置了 Advice,沒有設(shè)置 Pointcut,這樣最終攔截下來的是所有方法。
如果有需要,我們可以直接設(shè)置一個 Advisor,這樣就可以指定需要攔截哪些方法了。
我們先來看一下 Advisor 的定義:
public interface Advisor {
Advice EMPTY_ADVICE = new Advice() {};
Advice getAdvice();
boolean isPerInstance();
}可以看到,這里主要的就是 getAdvice 方法,這個方法用來獲取一個通知/增強(qiáng)。另外一個 isPerInstance 目前并沒有使用,默認(rèn)返回 true 即可。在具體實(shí)踐中,我們更關(guān)注它的一個子類:
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}這個子類多了一個 getPointcut 方法,PointcutAdvisor 這個接口很好的詮釋了 Advisor 的作用:Pointcut+Advice。
2.2 Pointcut
Pointcut 又有眾多的實(shí)現(xiàn)類:

挑兩個有意思的說一下,其他的其實(shí)也都差不多。
2.2.1 Pointcut
首先我們先來看下這個接口:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}接口里邊有兩個方法,看名字大概也能猜出來意思:
- getClassFilter:這個是類的過濾器,通過這個可以刷選出來要攔截的類。
- MethodMatcher:這個是方法過濾器,通過這個可以刷選出來需要攔截的方法。
至于 ClassFilter 本身其實(shí)就很好懂了:
@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 方法,當(dāng) isRuntime 方法返回 true 的時候,才會執(zhí)行第二個帶 args 參數(shù)的 matches 方法。
舉個簡單的使用案例,假設(shè)我現(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,比較常用的是通過表達(dá)式來定義切面,那么這里就可以使用 AspectJExpressionPointcut,這是一個類,所以可以不用繼承新類,直接使用創(chuàng)建使用即可,如下:
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* org.javaboy.bean.aop.ICalculator.add(..))");如上切點(diǎn)就表示攔截 ICalculator 類中的 add 方法。
2.3 Advice
這個好說,就是增強(qiáng)/通知,在本文第 1.1、1.2 小節(jié)中均已演示過,不再贅述。
2.4 Advisor 實(shí)踐
接下來松哥通過一個案例來和小伙伴們演示一下如何添加一個 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í)行結(jié)束了。。。");
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é)中不同的切點(diǎn),都是 OK 沒有問題的。getAdvice 就是前面定義的通知。
其實(shí)在本文的 1.1、1.2 小節(jié)中,我們直接添加了 Advice 而沒有配置 Advisor,我們自己添加的 Advice 在內(nèi)部也是被自動轉(zhuǎn)為了一個 Advisor,相關(guān)源碼如下:
@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 對象最終被轉(zhuǎn)為一個 DefaultPointcutAdvisor 對象,然后調(diào)用了 addAdvisor 方法進(jìn)行添加操作。
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}可以看到,在 DefaultPointcutAdvisor 初始化的時候,設(shè)置了 Pointcut.TRUE,也就是所有類的所有方法都會被攔截。
也就是 Advice 最終都會被轉(zhuǎn)為 Advisor。
到此這篇關(guān)于SpringAOP中的Advisor詳解的文章就介紹到這了,更多相關(guān)Spring中的Advisor內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)根據(jù)模板自動生成新的PPT
這篇文章主要介紹了如何利用Java代碼自動生成PPT,具體就是查詢數(shù)據(jù)庫數(shù)據(jù),然后根據(jù)模板文件(PPT),將數(shù)據(jù)庫數(shù)據(jù)與模板文件(PPT),進(jìn)行組合一下,生成新的PPT文件。感興趣的可以了解一下2022-02-02
java實(shí)現(xiàn)定制數(shù)據(jù)透視表的示例詳解
數(shù)據(jù)透視表(Pivot?Table)是一種數(shù)據(jù)分析工具,通常用于對大量數(shù)據(jù)進(jìn)行匯總、分析和展示,本文主要介紹了如何使用Java將計算項(xiàng)添加到數(shù)據(jù)透視表中,感興趣的可以了解下2023-12-12
Java中equals()方法重寫實(shí)現(xiàn)代碼
這篇文章主要介紹了Java中equals()方法重寫實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05
IDEA 單元測試創(chuàng)建方法詳解(2020.03版本親測)
這篇文章主要介紹了IDEA 單元測試創(chuàng)建方法詳解(2020.03版本親測),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
Spring?Boot如何在加載bean時優(yōu)先選擇我
這篇文章主要介紹了Spring?Boot如何在加載bean時優(yōu)先選擇我,在?Spring?Boot?應(yīng)用程序中,我們可以采取三種方式實(shí)現(xiàn)自己的?bean?優(yōu)先加載,本文通過實(shí)例代碼給大家詳細(xì)講解,需要的朋友可以參考下2023-03-03
SpringBoot整合OpenClaw技能系統(tǒng)的實(shí)戰(zhàn)指南
OpenClaw 雖然能直接通過聊天軟件指揮 AI 干活,但在企業(yè)場景里,我們總不能指望財務(wù)大姐在 Telegram 里敲命令來跑報表吧?本文教你用 SpringBoot 搭建一個企業(yè)級技能中臺,把 OpenClaw 的 5700+ 技能收編進(jìn) Java 體系,需要的朋友可以參考下2026-03-03

