欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringAOP中的Advisor詳解

 更新時(shí)間:2023年08月01日 10:00:06   作者:Young丶  
這篇文章主要介紹了SpringAOP中的Advisor詳解,平時(shí)我們項(xiàng)目中涉及到?AOP,基本上就是聲明式配置一下就行了,無論是基于?XML?的配置還是基于?Java?代碼的配置,都是簡單配置即可使用,今天就來看一下聲明式配置的使用,需要的朋友可以參考下

前言

平時(shí)我們項(xiàng)目中涉及到 AOP,基本上就是聲明式配置一下就行了,無論是基于 XML 的配置還是基于 Java 代碼的配置,都是簡單配置即可使用。聲明式配置有一個(gè)好處就是對源代碼的侵入小甚至是零侵入。不過今天松哥要和小伙伴們聊一聊編程式的 AOP,為什么要聊這個(gè)話題呢?因?yàn)樵?Spring 源碼中,底層就是通過這種方式創(chuàng)建代理對象的,所以如果自己會通過編程式的方式進(jìn)行 AOP 開發(fā),那么在看 Spring 中相關(guān)源碼的時(shí)候,就會很好理解了。

1. 基本用法

1.1 基于 JDK 的 AOP

我們先來看基于 JDK 動(dòng)態(tài)代理的 AOP。

假設(shè)我有如下一個(gè)計(jì)算器接口:

public interface ICalculator {
    void add(int a, int b);
    int minus(int a, int b);
}

然后給這個(gè)接口提供一個(gè)實(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è)我要生成一個(gè)代理對象,利用編程式的方式,代碼如下:

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

這里幾個(gè)方法應(yīng)該都好理解:

  1. setTarget 方法是設(shè)置真正的代理對象。這個(gè)在之前大家已經(jīng)接觸過了。
  2. addInterface,基于 JDK 的動(dòng)態(tài)代理是需要有接口的,這個(gè)方法就是設(shè)置代理對象的接口。
  3. addAdvice 方法就是添加增強(qiáng)/通知。
  4. 最后通過 getProxy 方法獲取到一個(gè)代理對象然后去執(zhí)行。

最終打印結(jié)果如下:

圖片

1.2 基于 CGLIB 的 AOP

如果被代理的對象沒有接口,那么可以通過基于 CGLIB 的動(dòng)態(tài)代理來生成代理對象。

假設(shè)我有如下類:

public class UserService {
    public void hello() {
        System.out.println("hello javaboy");
    }
}

要給這個(gè)類生成代理對象,如下:

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 動(dòng)態(tài)代理還是 CGLIB 動(dòng)態(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 動(dòng)態(tài)代理,沒有接口則是 CGLIB 動(dòng)態(tài)代理。不過在最上面有一個(gè) if 判斷,這個(gè)判斷中有三個(gè)條件,分別來和小伙伴們說一下:

config.isOptimize()

這個(gè)方法是判斷是否需要優(yōu)化。因?yàn)閭鹘y(tǒng)上大家都認(rèn)為 CGLIB 動(dòng)態(tài)代理性能高于 JDK 動(dòng)態(tài)代理,不過這些年 JDK 版本更新也是非常快,現(xiàn)在兩者性能差異已經(jīng)不大了。如果這個(gè)屬性設(shè)置為 true,那么系統(tǒng)就會去判斷是否有接口,有接口就 JDK 動(dòng)態(tài)代理,否則就 CGLIB 動(dòng)態(tài)代理。

如果需要設(shè)置該屬性,可以通過如下代碼設(shè)置:

proxyFactory.setOptimize(true);

config.isProxyTargetClass()

這個(gè)屬性作用也是類似,我們平時(shí)在使用 AOP 的時(shí)候,有時(shí)候也會設(shè)置這個(gè)屬性,這個(gè)屬性如果設(shè)置為 true,則會進(jìn)入到 if 分支中,但是 if 分支中的 if 則不宜滿足,所以一般情況下,如果這個(gè)屬性設(shè)置為 true,就意味著無論是否有接口,都使用 CGLIB 動(dòng)態(tài)代理。如果這個(gè)屬性為 false,則有接口就使用 JDK 動(dòng)態(tài)代理,沒有接口就使用 CGLIB 動(dòng)態(tài)代理。

hasNoUserSuppliedProxyInterfaces(config)

這個(gè)方法主要做兩方面的判斷:

  1. 當(dāng)前代理對象如果沒有接口,則直接返回 true。
  2. 當(dāng)前代理對象有接口,但是接口是 SpringProxy,則返回 true。

返回 true 基本上就意味著要使用 CGLIB 動(dòng)態(tài)代理了,返回 false 則意味著使用 JDK 動(dòng)態(tài)代理。

如果是基于 JDK 的動(dòng)態(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 里邊的動(dòng)態(tài)代理了,這很好懂。

如果是基于 CGLIB 的動(dòng)態(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)建動(dòng)態(tài)代理對象和直接使用 CGLIB 創(chuàng)建動(dòng)態(tài)代理對象的代碼我就不做過多介紹了,這些都是基本用法,松哥在之前錄制的免費(fèi)的 SSM 入門教程中都和小伙伴們講過了,這里就不啰嗦了。

2. Advisor

2.1 Advisor

Advisor = Pointcut+Advice。

前面的案例我們只是設(shè)置了 Advice,沒有設(shè)置 Pointcut,這樣最終攔截下來的是所有方法。

如果有需要,我們可以直接設(shè)置一個(gè) Advisor,這樣就可以指定需要攔截哪些方法了。

我們先來看一下 Advisor 的定義:

public interface Advisor {
 Advice EMPTY_ADVICE = new Advice() {};
 Advice getAdvice();
 boolean isPerInstance();
}

可以看到,這里主要的就是 getAdvice 方法,這個(gè)方法用來獲取一個(gè)通知/增強(qiáng)。另外一個(gè) isPerInstance 目前并沒有使用,默認(rèn)返回 true 即可。在具體實(shí)踐中,我們更關(guān)注它的一個(gè)子類:

public interface PointcutAdvisor extends Advisor {
 Pointcut getPointcut();
}

這個(gè)子類多了一個(gè) getPointcut 方法,PointcutAdvisor 這個(gè)接口很好的詮釋了 Advisor 的作用:Pointcut+Advice。

2.2 Pointcut

Pointcut 又有眾多的實(shí)現(xiàn)類:

圖片

挑兩個(gè)有意思的說一下,其他的其實(shí)也都差不多。

2.2.1 Pointcut

首先我們先來看下這個(gè)接口:

public interface Pointcut {
 ClassFilter getClassFilter();
 MethodMatcher getMethodMatcher();
 Pointcut TRUE = TruePointcut.INSTANCE;
}

接口里邊有兩個(gè)方法,看名字大概也能猜出來意思:

  1. getClassFilter:這個(gè)是類的過濾器,通過這個(gè)可以刷選出來要攔截的類。
  2. MethodMatcher:這個(gè)是方法過濾器,通過這個(gè)可以刷選出來需要攔截的方法。

至于 ClassFilter 本身其實(shí)就很好懂了:

@FunctionalInterface
public interface ClassFilter {
 boolean matches(Class<?> clazz);
 ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

就一個(gè) matches 方法,傳入一個(gè) 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;
}

這里三個(gè)方法,兩個(gè)是做匹配的 matches 方法,當(dāng) isRuntime 方法返回 true 的時(shí)候,才會執(zhí)行第二個(gè)帶 args 參數(shù)的 matches 方法。

舉個(gè)簡單的使用案例,假設(shè)我現(xiàn)在要攔截所有方法,那么我可以按照如下方式定義:

public class AllClassAndMethodPointcut implements Pointcut {
    @Override
    public ClassFilter getClassFilter() {
        return ClassFilter.TRUE;
    }
    @Override
    public MethodMatcher getMethodMatcher() {
        return MethodMatcher.TRUE;
    }
}

這是自帶的兩個(gè)常量,表示攔截所有類和所有方法。

再假如,我要攔截 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

我們平時(shí)寫 AOP,比較常用的是通過表達(dá)式來定義切面,那么這里就可以使用 AspectJExpressionPointcut,這是一個(gè)類,所以可以不用繼承新類,直接使用創(chuàng)建使用即可,如下:

AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* org.javaboy.bean.aop.ICalculator.add(..))");

如上切點(diǎn)就表示攔截 ICalculator 類中的 add 方法。

2.3 Advice

這個(gè)好說,就是增強(qiáng)/通知,在本文第 1.1、1.2 小節(jié)中均已演示過,不再贅述。

2.4 Advisor 實(shí)踐

接下來松哥通過一個(gè)案例來和小伙伴們演示一下如何添加一個(gè) 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)部也是被自動(dòng)轉(zhuǎn)為了一個(gè) 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)為一個(gè) DefaultPointcutAdvisor 對象,然后調(diào)用了 addAdvisor 方法進(jìn)行添加操作。

public DefaultPointcutAdvisor(Advice advice) {
 this(Pointcut.TRUE, advice);
}

可以看到,在 DefaultPointcutAdvisor 初始化的時(shí)候,設(shè)置了 Pointcut.TRUE,也就是所有類的所有方法都會被攔截。

也就是 Advice 最終都會被轉(zhuǎn)為 Advisor。

到此這篇關(guān)于SpringAOP中的Advisor詳解的文章就介紹到這了,更多相關(guān)Spring中的Advisor內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用restTemplate.postForEntity()的問題

    使用restTemplate.postForEntity()的問題

    這篇文章主要介紹了使用restTemplate.postForEntity()的問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java數(shù)據(jù)長度獲取方式對比之length屬性、length()和size()方法詳解

    Java數(shù)據(jù)長度獲取方式對比之length屬性、length()和size()方法詳解

    在Java編程語言中l(wèi)ength、length()和size()是三個(gè)常見的用來獲取不同數(shù)據(jù)類型對象長度或大小的方法,但它們各自適用于不同的上下文,這篇文章主要給大家介紹了關(guān)于Java數(shù)據(jù)長度獲取方式對比之length屬性、length()和size()方法詳解
    2024-07-07
  • Java中Queue的poll()和remove()區(qū)別詳解

    Java中Queue的poll()和remove()區(qū)別詳解

    這篇文章主要介紹了Java中Queue的poll()和remove()區(qū)別詳解,Queue接口提供了許多方法,其中poll()和remove()是兩個(gè)常用的方法,它們的區(qū)別在于,當(dāng)隊(duì)列為空時(shí),poll()方法返回null,而remove()方法會拋出,需要的朋友可以參考下
    2023-07-07
  • 老生常談spring boot 1.5.4 日志管理(必看篇)

    老生常談spring boot 1.5.4 日志管理(必看篇)

    下面小編就為大家?guī)硪黄仙U剆pring boot 1.5.4 日志管理(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • java實(shí)現(xiàn)計(jì)算器模板及源碼

    java實(shí)現(xiàn)計(jì)算器模板及源碼

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)計(jì)算器模板及源碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • Spring Aware標(biāo)記接口使用案例解析

    Spring Aware標(biāo)記接口使用案例解析

    這篇文章主要介紹了Spring Aware標(biāo)記接口使用案例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • javaweb如何使用華為云短信通知公共類調(diào)用

    javaweb如何使用華為云短信通知公共類調(diào)用

    這篇文章主要介紹了javaweb使用華為云短信通知公共類調(diào)用的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • 詳解Java的類加載機(jī)制及熱部署的原理

    詳解Java的類加載機(jī)制及熱部署的原理

    今天我要講的就是Java的熱部署的原理,由于熱部署的原理和類的加載機(jī)制有關(guān),所以打算講一下類加載的機(jī)制,文中介紹的非常詳細(xì),需要的朋友可以參考下
    2021-05-05
  • spring boot使用自定義的線程池執(zhí)行Async任務(wù)

    spring boot使用自定義的線程池執(zhí)行Async任務(wù)

    這篇文章主要介紹了spring boot使用自定義的線程池執(zhí)行Async任務(wù)的相關(guān)資料,需要的朋友可以參考下
    2018-02-02
  • 淺談spring boot使用thymeleaf版本的問題

    淺談spring boot使用thymeleaf版本的問題

    這篇文章主要介紹了spring boot使用thymeleaf版本的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08

最新評論