從動態代理到SpringAop以及AspectJ風格

1.靜態代理

在說起動態代理前先說明一下靜態代理模式,靜態代理模式是一種很常見的通用設計模式,實現也很簡單,uml類圖以下:html

clipboard.png

如上圖所示,代理類ProxyImpl和委託類都實現了同一個接口ObjectInterface,代理類和委託類是關聯關係。
舉個栗子,如今有一個發送短信消息的類SmsMessagePush,實現了MessagePushjava

public interface MessagePush {
    public void push(String receiver, String msg);
}
public class SmsMessagePush implements MessagePush {
    @Override
    public void push(String receiver, String msg) {
        //do push
    }
}

通常狀況下,用戶直接調用SmsMessage.push()便可,爲何要用代理呢?通常有兩種狀況
1:用戶沒法直接訪問目標對象(委託對象)或者是不想讓用戶直接訪問目標對象,這時候proxy對象就承擔了一種相似跳板機或者防火牆的角色,代替用戶訪問目標對象或者對訪問者的權限進行篩選。
2:對委託對象的功能加強,好比上面的例子,在發送短信前添加對手機號碼進行校驗之類的功能。spring

public class SmsMessagePushProxy implements MessagePush {

    private SmsMessagePush smsMessagePush;

    public SmsMessagePushProxy(SmsMessagePush smsMessagePush) {
        this.smsMessagePush = smsMessagePush;
    }
    @Override
    public void push(String mobile, String msg) {
        if (!checkMobile(mobile)){
            return;
        }
        smsMessagePush.push(mobile, msg);
    }

    private Boolean checkMobile(String mobile) {
        //do check
    }
}
public class App {
    public static void main() {
        SmsMessagePushProxy smsMessagePushProxy = new SmsMessagePushProxy(new SmsMessagePush());
        smsMessagePushProxy.push("10086", "老子明天不上班");
    }
}

上面的代理SmsMessagePushProxy在調用push方法前會對手機號碼進行過濾。代理類做爲中介提供了對委託資源的訪問,可是代理和委託對象本質上是同樣的,都實現了同一個接口,因此當接口變化時代理類就須要對應的修改。並且沒增長一個委託類就須要增長一個對應的代理類,管理起來十分不方便且難以維護。爲了解決這種狀況,便引入了jdk動態代理。express

2.動態代理

靜態代理是在編碼階段就寫好的,而動態代理是在程序運行時經過類反射動態建立的。java的動態代理分爲jdk動態代理和cglib動態代理,兩者使用上最大的區別是jdk動態代理是面向接口的,cglib動態代理是面向對象的。即jdk proxy的委託對象必須實現了某個接口。這裏暫不討論cglib的實現,只講下jdk。編程

動態代理主要涉及的類放在java.lang.reflect包下面(因而可知是基於反射實現的),主要涉及兩個類:調用處理器java.lang.reflect.InvocationHandle和主類java.lang.reflect.Proxy
其中InvocationHandler是一個接口,只定義了一個方法json

public Object invoke(Object proxy, Method method, Object[] args)

從參數名就能夠猜想,invoke方法是用來執行委託對象的具體方法的
Proxy類有不少靜態方法,最經常使用的一個方法是newProxyInstance,該方法會根據傳入的參數建立一個代理對象設計模式

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

該方法接受三個參數,第一個參數指定要使用的類加載器,第二個參數是委託對象實現的一組接口。第三個參數指定使用哪一個調用處理器。
第二個參數我開始還糾結爲何是個接口數組,難道一個代理實例能夠代理多個實現了不一樣接口的委託對象?在invoke方法中也區分不了當前要執行的是哪一個對象啊,並且這樣兩個接口有相同名稱的方法會產生衝突啊。。。後來發現大多數狀況下是由於委託對象實現了多個接口。。。
下面舉個栗子:
假設有一個類PushServiceImpl實現了SmsPushInterface和MailPushInterface兩個接口數組

public class PushServiceImpl implements SmsPushInterface, MailPushInterface {
    @Override
    public void pushEmail(String address, String msg) {
        System.out.println("push a email to " + address + ",message is " + msg);
    }

    @Override
    public void pushSms(String mobile, String msg) {
        System.out.println("push a sms to " + mobile + ",message is " + msg);
    }
}

public interface SmsPushInterface {
    public void pushSms(String mobile, String msg);
}

public interface MailPushInterface {
    public void pushEmail(String address, String msg);
}

如今想要對PushServiceImpl對象進行代理,定義一個getProxy方法,方法接收一個委託對象做爲參數,返回的類型是Object,實際上返回的是一個代理對象,該代理對象實現了SmsPushInterface和MailPushInterface兩個接口。代理對象經過Proxy.newProxyInstance()方法建立。springboot

public class ProxyTest {

    public Object getProxy(Object target) throws Exception {
        InvocationHandler handler = new PushHandler(target);

        return Proxy.newProxyInstance(
                ProxyTest.class.getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );
    }

    @Test
    public void proxyTest() throws Exception {
        SmsPushInterface smsPushService = (SmsPushInterface) getProxy(new PushServiceImpl());
        smsPushService.pushSms("10086", "這一切都是命運石之門的選擇");

        MailPushInterface mailPushService = (MailPushInterface) getProxy(new PushServiceImpl());
        mailPushService.pushEmail("31415926@qq.com", "都是時臣的錯");
    }
}

因爲獲取代理類返回的是Object類型,在實際使用時要根據調用的方法轉換成對應的接口類型,注意這裏不能轉成PushServiceImpl即實例對象的類型,由於返回的代理類是Proxy的子類,實現了這兩個接口而已。
在調用代理類的方法時,其實是執行處理器在運行,以下所示,咱們編寫一個PushHandler實現InvocationHandler接口並覆寫invoke方法:app

public class PushHandler implements InvocationHandler {
    private Object proxied;

    PushHandler(Object proxied) {
        this.proxied = proxied;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("pushSms".equals(method.getName())) {
            String mobile = args[0].toString();
            String msg = args[1].toString();
            if (!checkMobile(mobile)) {
                throw new Exception("mobile invalid");
            }
        }

        if ("pushEmail".equals(method.getName())) {
            String address = args[0].toString();
            String msg = args[1].toString();
            if (!checkMail(address)) {
                throw new Exception("mail address invalid");
            }
        }

        return method.invoke(proxied, args);
    }

    private Boolean checkMobile(String mobile) {
        // check mobile valid
        return true;
    }

    private Boolean checkMail(String mailAddress) {
        // check mail valid
        return true;
    }
}

PushHandler在構造方法中接受一個委託對象實例,最後實際執行的就是這個對象的方法。在執行getProxy返回的代理對象的方法時會調用PushHandler的invoke方法,其中proxy參數就是當前的代理對象(java.lang.reflect.Proxy的子類),method是當前執行方法,args是參數數組。在這個例子裏咱們根據方法名來作對應的檢查以後經過反射方法method.invoke()執行。
具體的動態代理運行原理這裏暫不展開,網上有不少相關的內容,好比這篇

http://blog.jobbole.com/104433

3.AOP

aop(Aspect Oriented Programming) 翻譯過來就是面向方面/切面編程。關於aop的定義有許多,這裏引用一個可能不是特別準確但很容易理解的解釋:(出處www.zhihu.com/question/24863332/answer/253016908)

  • AOP是對OOP的一種補充。
  • 面向對象(OOP)引入了繼承、多態、封裝,將系統的業務功能按照模塊劃分,每一個模塊用一個或多個類來表示。
  • 而對於一些系統功能,沒法使用OOP的思想來實現它們。這些系統功能每每穿插在業務功能的各處,和業務代碼耦合在一塊兒;並且系統功能每每會被重複使用,這就致使了模塊不利於複用,這就是使用OOP實現系統功能的弊端。
  • AOP即爲面向切面編程,它把系統需求按照功能分門歸類,把它們封裝在一個個切面中,而後再指定這些系統功能往業務功能中織入的規則。最後由第三方機構根據你指定的織入規則,將系統功能整合到業務功能中。

aop是一種思想而不是一種技術。因此說,若是拋開spring,我上面寫的動態代理甚至靜態代理的例子也能夠算是一種aop。
spring中的aop實現分爲兩種,基於動態代理的aop和基於AspectJ的aop,這裏不得不吐槽國內的各類文章,根本沒搞清兩者的區別,或者打着spring aop的標題而後開始講aspectJ的使用,你抄我我抄他,越抄越混亂。

什麼是AspectJ?

在網上一搜一大片所謂AspectJ的用法,其實都是AspectJ的「切面語法」,只是AspectJ框架的冰山一角,AspectJ是徹底獨立於Spring存在的一個Eclipse發起的項目,官方關於AspectJ的描述是:

Eclipse AspectJ is a seamless aspect-oriented extension to the Java™ programming language. It is Java platform compatible easy to learn and use.

是的AspectJ甚至能夠說是一門獨立的語言,咱們常看到的在spring中用的@Aspect註解只不過是Spring2.0之後使用了AspectJ的風格而已本質上仍是Spring的原生實現,關於這點Spring的手冊中有說起:

@AspectJ使用了Java 5的註解,能夠將切面聲明爲普通的Java類。@AspectJ樣式在AspectJ 5發佈的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5同樣的註解,並使用AspectJ來作切入點解析和匹配。可是,AOP在運行時仍舊是 純的Spring AOP,並不依賴於AspectJ的編譯器或者織入器(weaver)。

so 咱們經常使用的org.aspectj.lang.annotation包下的AspectJ相關注解只是使用了AspectJ的樣式,至於全套的AspectJ以及織入器,那徹底是另外一套獨立的東西。

名詞解釋

在看aop實現前,先解釋幾個基本名詞,對就是網上一搜一大片那些

advice
advice,常被翻譯成」加強「或者」通知「,實際上advice就是在切面中執行的額外操做,拿上面動態代理的例子來講在PushHandler::invoke()方法中,對手機號碼以及郵箱地址的檢查就是兩個advice。在不少aop框架中advice是以攔截器的形式存在的,advice又常分爲前置型advice和後置型advice,像手機號碼檢查這種就屬於前置的advice,返回結果記錄日誌就屬於後置advice

join point
鏈接點很容易理解,先看下spring手冊上的定義

a· point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

join point 就是程序運行時的一個特徵點,好比方法的執行或者異常的拋出。能夠把join point理解爲一個觸發條件。

point cut
point cut大部分狀況下被翻譯爲’切入點‘。不少人常常搞不清楚join point 和 point cut的區別,實際上兩者徹底不是一個維度的概念,若是說join point是名詞 point cut就是謂詞。pointcut是一個規則,指定了哪些切入點會被切入。
好比:在test.network.message包下全部類的push()方法執行前,對入參作校驗 其中push()就是一個join point , 在xx前,對入參進行驗證是一個advice,而」在test.network.message包下全部類的push()方法「就是一個point cut。
作個比喻的話,一個插排,每一個插孔都是一個join point,而電視插在哪,電腦插在哪,整個一個佈線規則就是一個point cut
aspect
在test.network.message包下全部類的push()方法執行前,對入參作校驗 整個這個行爲就是一個切面了。因此切面能夠理解爲point cut和advice的集合。在使用AspectJ樣式時,被@Aspect註解標註的類就是一個切面。

爲防止翻譯不統一形成的誤解,下面對名詞的使用直接使用英文原文

Spring AOP

spring aop常被人詬病複雜,難用。但又有多少新司機真正的用過而不是上來就被@Aspectj那一套洗腦了,問你爲何spring aop難用,AspectJ的優點在哪又有多少人能答上來。
spring aop相關的內容基本都在org.springframework.aop.framework包下,spring aop是經過代理工廠實現的,主要涉及的類圖以下:
clipboard.png

兩個經常使用的aop工廠類ProxyFactoryBean和ProxyFactoryBean繼承自ProxyCreatorSupport,proxyCreator是代理類的基類,並擁有一個實現了AopProxyFactory接口的屬性DefaultAopProxyFactory,ProxyFactory中的getProxy方法實際上最後是調用的是AopProxy接口中的getProxy方法,而實現了AopProxy接口的對象(JdkDynamicAopProxy或CglibAopProxy)是由DefaultAopProxyFactory建立的。這麼說可能會有點繞,下面用一張時序圖來解釋(圖是我偷的,懶得畫了,出處:https://www.jianshu.com/p/500...
圖片描述

DefaultAopProxyFactory在建立aop代理時會判斷委託類是否實現了某個接口,是的話建立JdkDynamicAopProxy,不然的話建立ObjenesisCglibAopProxy,代碼以下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        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);
        }
    }

下面看一個使用proxyFactory實現aop的例子

public class SpringAopTest {
    @Test
    public void proxy() {
        PushServiceImpl pushService = new PushServiceImpl();
        //建立工廠
        ProxyFactory proxyFactory = new ProxyFactory(pushService);
        //添加advice
        proxyFactory.addAdvice(new SmsPushBeforeAdvice());
        proxyFactory.addAdvice(new SmsPushAfterAdvice());
        //獲取代理
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }
}
public class SmsPushBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        if ("pushSms".equals(method.getName())) {
            String mobile = args[0].toString();
            String msg = args[1].toString();
            if (!checkMobile(mobile)) {
                throw new Exception("mobile invalid");
            }
        }


    }

    private Boolean checkMobile(String mobile) {
        //do mobile check
        System.out.println("check mobile valid");
        return true;
    }

}
public class SmsPushAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        StringBuffer logData = new StringBuffer();
        logData.append("get method return : method:" + method.getName());
        logData.append("  message : \"" + args[1] + "\"was sent");
        if (returnValue == null) {
            logData.append("  and return value is void");
        }

        addLog(logData.toString());
    }

    private void addLog(String logMsg) {
        //do log
        System.out.println("get log info: " + logMsg);
    }
}

下面是測試運行時的輸出:

check mobile valid
push a sms to 10086,message is EL PSY CONGROO
get log info: get method return : method:pushSms  message : "EL PSY CONGROO"was sent  and return value is void

能夠看到,使用proxyFactory實現aop和上面的動態代理的例子十分類似 ,spring aop中的advice就等同於咱們在動態代理中InvocationHandler中的加強操做。
經常使用的advice有四種:
前置advice:在方法執行前觸發的advice,實現了 org.springframework.aop.MethodBeforeAdvice接口
後置advice: 在方法返回時觸發的advice,實現了org.springframework.aop.AfterReturningAdvice接口
異常advice:在拋出異常後觸發,實現了org.springframework.aop.ThrowsAdviceArround
環繞advice:自定義觸發時機,實現和InvokeHandler相似,實現了org.aopaliance.intercept.MethodInterceptor

advisor:
上面的例子,添加的advice在實際運行時會包裝爲Advisor對象,advisor包含了advice和pointcut,能夠理解爲一個切面(aspect),下面是AdvisedSupport類的addAdvice的方法實現,能夠看到在執行addAdvice方法時會封裝爲DefaultPointcutAdvisor實例

public void addAdvice(int pos, Advice advice) throws AopConfigException {
        Assert.notNull(advice, "Advice must not be null");
        if (advice instanceof IntroductionInfo) {
            // We don't need an IntroductionAdvisor for this kind of introduction:
            // It's fully self-describing.
            addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
        }
        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));
        }
    }

在DefaultPointcutAdvisor中,pointCut被默認設置爲Poincut.TRUE,此時會匹配被代理對象的全部方法

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

    private Pointcut pointcut = Pointcut.TRUE;

除了直接設置advice之外,咱們還能夠設置advisor,並指定對應的pointcut,這樣就能夠指定哪些方法會被切入,pointcut和advisor有許多包裝類型,都在org.springframework.aop.support包下,這裏使用最經常使用的正則pointcut:JdkRegexpMethodPointcut舉個栗子

public void pointTest() {
        SmsPushInterface pushService = new PushServiceImpl();
        //建立工廠
        ProxyFactory proxyFactory = new ProxyFactory(pushService);
        //建立advisor並添加advice
        DefaultPointcutAdvisor beforeAdvisor = new DefaultPointcutAdvisor(new SmsPushBeforeAdvice());
        DefaultPointcutAdvisor afterAdvisor = new DefaultPointcutAdvisor(new SmsPushAfterAdvice());
        //建立正則方法pointcut
        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
        //設置正則規則
        pointcut.setPattern(PushServiceImpl.class.getName() + ".push.*");
        beforeAdvisor.setPointcut(pointcut);
        afterAdvisor.setPointcut(pointcut);
        //設置advisor
        proxyFactory.addAdvisor(beforeAdvisor);
        proxyFactory.addAdvisor(afterAdvisor);
        //獲取代理
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }

能夠看到此次咱們沒有直接使用addAdvice而是建立了兩個advisor,並建立了一個pointcut,設置正則pattern匹配全部以push開頭的方法。
這裏有一個坑,我開始在設置pattern時是直接寫成 setPattern("push.*")的,發現沒有匹配,後來發現AbstractRegexpMethodPointcut的matches方法裏是這樣寫的

public boolean matches(Method method, Class<?> targetClass) {
        return ((targetClass != null && matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass))) ||
                matchesPattern(ClassUtils.getQualifiedMethodName(method)));
    }

ClassUtils.getQualifiedMethodName方法會將method名稱拼上類名,即要匹配的方法名其實是 spring.aop.PushServiceImpl.pushSms

在使用時咱們會發現這樣手動設置pointcut和advisor十分麻煩,因此spring aop提供了一些包裝好的advisor,好比上面的這個正則的例子咱們就能夠直接使用org.springframework.aop.supportRegexpMethodPointcutAdvisor,還有一些更方便的包裝advisor好比下面這個例子直接使用了NameMatchMethodPointcutAdvisor設置匹配的方法名

@Test
    public void proxy() {
        PushServiceImpl pushService = new PushServiceImpl();
        //建立工廠
        ProxyFactory proxyFactory = new ProxyFactory(pushService);
        //建立方法名稱匹配advisor並添加advice
        NameMatchMethodPointcutAdvisor beforeAdvisor = new NameMatchMethodPointcutAdvisor(new SmsPushBeforeAdvice());
        NameMatchMethodPointcutAdvisor afterAdvisor = new NameMatchMethodPointcutAdvisor(new SmsPushAfterAdvice());
        //設置匹配的方法名
        beforeAdvisor.setMappedName("pushSms");
        afterAdvisor.setMappedName("pushSms");
        //設置advisor
        proxyFactory.addAdvisor(beforeAdvisor);
        proxyFactory.addAdvisor(afterAdvisor);
        //獲取代理
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }

以上就是SpringAop的基本使用方法,經過上面的例子能夠看出springAop的確存在一些問題,最明顯的就是切面不夠獨立,對業務代碼的侵入性很強,聲明Aspect須要以過程的形式顯示聲明(雖然ProxyFactoryBean能夠將切面部分封裝爲bean,可是我看到xml是在是想吐)。並且advice和pointcut的結合靈活性較差,實際使用時還須要本身寫一些輪子。spring也認識到了這些問題並在spring2.0以後推出了AspectJ樣式的Aop

AspectJ樣式AOP

再次強調一下這裏講的AspectJ樣式的aop只是使用了AspectJ的一些語法特性,底層依舊是SpringAop實現的
首先 使用aspectJ樣式的aop須要一些額外配置

  • springboot
    若是你使用的是springboot,能夠經過在主類使用@EnableAspectJAutoProxy註解來開啓,另外若是你使用了@EnableAutoConfiguration會默認開啓。若是想關閉aop能夠配置設置spring.aop.auto = falsespring.aop.proxy-target-class能夠指定使用jdk代理仍是cglib代理,默認是jdk(false:jdk,true:cglib)
  • spring
    普通的spring框架能夠經過設置<aop:aspectj-autoproxy proxy-target-class="false"/>來開啓,設置爲true使用cglib

前面springAop已經將aop的概念說的很清楚這裏就不扯淡了直接上個例子:
@Aspect註解將當前類標記爲一個切面,@Component將PushAspect註冊爲Bean
@Pointcut註解將一個void方法標記爲一個pointcut,execution、within、args等被稱爲pointcut designators(切點標誌符)簡稱爲PCD,每一個PCD都有對應的表達式,好比最經常使用的execution,下面的例子第一個表示修飾符,即public仍是private之類。後面緊接着是包名+類名+方法名,spring.aop.PushServiceImpl.* 表示匹配 spring.aop包下PushServiceImpl類的全部方法。最後是參數標識(String,Stirng)表示參數是兩個String類型的方法,若要匹配全部參數類型,可使用(..)表示

@Aspect
@Component
public class PushAspect {
    @Pointcut(value = "execution(* spring.aop.PushServiceImpl.pushSms(String,String))")
    public void pushSmsPointcut() {
    }

    @Pointcut("execution(* spring.aop.PushServiceImpl.*(..))")
    public void pushMethodPointcut() {
    }

    @Before("pushSmsPointcut() && args(mobile,msg)")
    public void checkMobile(String mobile, String msg) throws Exception {
        if (!checkMobile(mobile)) {
            throw new Exception("mobile invalid");
        }
    }

    private Boolean checkMobile(String mobile) {
        //do mobile check
        System.out.println("check mobile valid");
        return true;
    }

    @AfterReturning(pointcut = "pushMethodPointcut()", returning = "returnValue")
    public void writeLog(JoinPoint joinPoint, Object returnValue) {
        StringBuffer logData = new StringBuffer();
        logData.append("get method return : method:" + joinPoint.getSignature().getName());
        logData.append("  message : \"" + joinPoint.getArgs()[1].toString() + "\"was sent");
        if (returnValue == null) {
            logData.append("  and return value is void");
        }

        addLog(logData.toString());
    }

    private void addLog(String logMsg) {
        //do log
        System.out.println("get log info: " + logMsg);
    }
}

@Before、@AfterReturn、@Around等註解標識了一個advice方法,advice和pointcut相關聯,像上面這個例子前置advice checkMobile()就是做用於被pushSmsPointcut標識的方法上,pointcut能夠顯示綁定在value或pointcut參數上,或者不寫參數默認第一表達式就是pointcut。也能夠不寫pointcut,在advice表達式內直接寫PCD,可是不建議這樣作

關於怎麼向advice body裏傳遞參數,先看下手冊裏的描述:

To make argument values available to the advice body, you can use the binding form of args. If a parameter name is used in place of a type name in an args expression, then the value of the corresponding argument will be passed as the parameter value when the advice is invoked

因此想要傳遞參數在advcie中使用args表達式便可,注意這裏要在args裏寫參數名,而不是寫參數類型。寫參數類型的話是另外一種用法,通常寫在PCD中,做爲joinpoint匹配時參數類型的篩選。另外傳遞的參數中還有一個特別的參數類型JoinPoint. jointpoint包含了不少實用的反射方法,必須獲取當前的代理類,獲取參數列表等等,joinpoint能夠做爲第一個參數傳入advice且不用再參數列表裏指定。這裏注意使用around advice時傳入的是ProceedingJoinPoint類型的jointpoint

execution表達式中的參數和args
在designator表達式中,有兩種方式能夠限定方法的參數,一個是經過execution表達式的最後一個參數,另外一個是經過args標識符,兩者有什麼區別?

args(java.lang.String)

execution(* *(java.lang.String))

區別是args表達式標識的是在運行時傳入的參數是String類型,而execution表達式表示的是方法簽名在定義的時候是String。
Aspectj風格還有不少designators(with,@annotation等),以及advice類型,這裏再也不贅述,能夠參考spring的手冊。雖然是英文的但寫的很詳細

手冊: https://docs.spring.io/spring...

下面是委託對象的執行(實際上不該該再叫他委託對象了,由於這裏已經將aop剝離出來了,用戶感知不到代理的過程)。能夠看到和IOC結合起來十分方便,切面和業務代碼沒有任何耦合。這裏若是把注入的SmsPushInterface換成SmsBean named 'pushServiceImpl' is expected to be of type 'spring.aop.PushServiceImpl' but was actually of type 'com.sun.proxy.$Proxy61'.能夠看出底層的實現仍是使用的動態代理,若是這裏想聲明Impl類型能夠把代理改爲cglib.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class SpringAopTest {
    @Resource
    SmsPushInterface pushService;

    @Test
    public void aspectjTest() throws Exception {
        pushService.pushSms("10086", "EL PSY CONGROO");
    }

上面的例子都是在IOC環境中自動加載的,若是脫離這個環境想在過程當中執行怎麼辦?spring提供了一個org.springframework.aop.aspectj.annotation.AspectJProxyFactory 代理工廠,和ProxyFactory同樣繼承ProxyCreatorSupport,經過該工廠能夠像proxyFactory同樣直接顯示進行代理

@Test
    public void testAspectJProxyFactory() {
        PushServiceImpl pushService = new PushServiceImpl();
        AspectJProxyFactory proxyFactory = new AspectJProxyFactory(pushService);
        proxyFactory.addAspect(PushAspect.class);
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }

從下面的源碼能夠看出,這裏addAspect實際上仍是把@Aspect解析成Advisor來處理

public void addAspect(Class<?> aspectClass) {
    String aspectName = aspectClass.getName();
    AspectMetadata am = createAspectMetadata(aspectClass, aspectName);
    MetadataAwareAspectInstanceFactory instanceFactory = createAspectInstanceFactory(am, aspectClass, aspectName);
    addAdvisorsFromAspectInstanceFactory(instanceFactory);
}

因而可知,AspectJ樣式的方便不僅是體如今提供了一些方便的註解以及PCD,更體如今和Spring IOC的完美結合關於@Aspect的解析原理,是在沒時間寫了,之後有時間再補吧。寫的比較匆忙若有問題歡迎指正

相關文章
相關標籤/搜索