成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

從動(dòng)態(tài)代理到SpringAop以及AspectJ風(fēng)格

msup / 906人閱讀

摘要:具體的動(dòng)態(tài)代理運(yùn)行原理這里暫不展開(kāi),網(wǎng)上有很多相關(guān)的內(nèi)容,比如這篇翻譯過(guò)來(lái)就是面向方面切面編程。所以切面可以理解為和的集合。

1.靜態(tài)代理

在提及動(dòng)態(tài)代理前先說(shuō)明一下靜態(tài)代理模式,靜態(tài)代理模式是一種很常見(jiàn)的通用設(shè)計(jì)模式,實(shí)現(xiàn)也很簡(jiǎn)單,uml類圖如下:

如上圖所示,代理類ProxyImpl和委托類都實(shí)現(xiàn)了同一個(gè)接口ObjectInterface,代理類和委托類是關(guān)聯(lián)關(guān)系。
舉個(gè)栗子,現(xiàn)在有一個(gè)發(fā)送短信消息的類SmsMessagePush,實(shí)現(xiàn)了MessagePush

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
    }
}

一般情況下,用戶直接調(diào)用SmsMessage.push()即可,為什么要用代理呢?一般有兩種情況
1:用戶無(wú)法直接訪問(wèn)目標(biāo)對(duì)象(委托對(duì)象)或者是不想讓用戶直接訪問(wèn)目標(biāo)對(duì)象,這時(shí)候proxy對(duì)象就承擔(dān)了一種類似跳板機(jī)或者防火墻的角色,代替用戶訪問(wèn)目標(biāo)對(duì)象或者對(duì)訪問(wèn)者的權(quán)限進(jìn)行篩選。
2:對(duì)委托對(duì)象的功能增強(qiáng),比如上面的例子,在發(fā)送短信前添加對(duì)手機(jī)號(hào)碼進(jìn)行校驗(yàn)之類的功能。

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在調(diào)用push方法前會(huì)對(duì)手機(jī)號(hào)碼進(jìn)行過(guò)濾。代理類作為中介提供了對(duì)委托資源的訪問(wèn),但是代理和委托對(duì)象本質(zhì)上是一樣的,都實(shí)現(xiàn)了同一個(gè)接口,所以當(dāng)接口變化時(shí)代理類就需要對(duì)應(yīng)的修改。而且沒(méi)增加一個(gè)委托類就需要增加一個(gè)對(duì)應(yīng)的代理類,管理起來(lái)十分不方便且難以維護(hù)。為了解決這種情況,便引入了jdk動(dòng)態(tài)代理。

2.動(dòng)態(tài)代理

靜態(tài)代理是在編碼階段就寫(xiě)好的,而動(dòng)態(tài)代理是在程序運(yùn)行時(shí)通過(guò)類反射動(dòng)態(tài)創(chuàng)建的。java的動(dòng)態(tài)代理分為jdk動(dòng)態(tài)代理和cglib動(dòng)態(tài)代理,二者使用上最大的區(qū)別是jdk動(dòng)態(tài)代理是面向接口的,cglib動(dòng)態(tài)代理是面向?qū)ο蟮摹<磈dk proxy的委托對(duì)象必須實(shí)現(xiàn)了某個(gè)接口。這里暫不討論cglib的實(shí)現(xiàn),只講下jdk。

動(dòng)態(tài)代理主要涉及的類放在java.lang.reflect包下面(由此可見(jiàn)是基于反射實(shí)現(xiàn)的),主要涉及兩個(gè)類:調(diào)用處理器java.lang.reflect.InvocationHandle和主類java.lang.reflect.Proxy
其中InvocationHandler是一個(gè)接口,只定義了一個(gè)方法

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

從參數(shù)名就可以猜測(cè),invoke方法是用來(lái)執(zhí)行委托對(duì)象的具體方法的
Proxy類有很多靜態(tài)方法,最常用的一個(gè)方法是newProxyInstance,該方法會(huì)根據(jù)傳入的參數(shù)創(chuàng)建一個(gè)代理對(duì)象

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

該方法接受三個(gè)參數(shù),第一個(gè)參數(shù)指定要使用的類加載器,第二個(gè)參數(shù)是委托對(duì)象實(shí)現(xiàn)的一組接口。第三個(gè)參數(shù)指定使用哪個(gè)調(diào)用處理器。
第二個(gè)參數(shù)我開(kāi)始還糾結(jié)為什么是個(gè)接口數(shù)組,難道一個(gè)代理實(shí)例可以代理多個(gè)實(shí)現(xiàn)了不同接口的委托對(duì)象?在invoke方法中也區(qū)分不了當(dāng)前要執(zhí)行的是哪個(gè)對(duì)象啊,而且這樣兩個(gè)接口有相同名稱的方法會(huì)產(chǎn)生沖突啊。。。后來(lái)發(fā)現(xiàn)大多數(shù)情況下是因?yàn)槲袑?duì)象實(shí)現(xiàn)了多個(gè)接口。。。
下面舉個(gè)栗子:
假設(shè)有一個(gè)類PushServiceImpl實(shí)現(xiàn)了SmsPushInterface和MailPushInterface兩個(gè)接口

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

現(xiàn)在想要對(duì)PushServiceImpl對(duì)象進(jìn)行代理,定義一個(gè)getProxy方法,方法接收一個(gè)委托對(duì)象作為參數(shù),返回的類型是Object,實(shí)際上返回的是一個(gè)代理對(duì)象,該代理對(duì)象實(shí)現(xiàn)了SmsPushInterface和MailPushInterface兩個(gè)接口。代理對(duì)象通過(guò)Proxy.newProxyInstance()方法創(chuàng)建。

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", "這一切都是命運(yùn)石之門(mén)的選擇");

        MailPushInterface mailPushService = (MailPushInterface) getProxy(new PushServiceImpl());
        mailPushService.pushEmail("[email protected]", "都是時(shí)臣的錯(cuò)");
    }
}

由于獲取代理類返回的是Object類型,在實(shí)際使用時(shí)要根據(jù)調(diào)用的方法轉(zhuǎn)換成對(duì)應(yīng)的接口類型,注意這里不能轉(zhuǎn)成PushServiceImpl即實(shí)例對(duì)象的類型,因?yàn)榉祷氐拇眍愂荘roxy的子類,實(shí)現(xiàn)了這兩個(gè)接口而已。
在調(diào)用代理類的方法時(shí),實(shí)際上是執(zhí)行處理器在運(yùn)行,如下所示,我們編寫(xiě)一個(gè)PushHandler實(shí)現(xiàn)InvocationHandler接口并覆寫(xiě)invoke方法:

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在構(gòu)造方法中接受一個(gè)委托對(duì)象實(shí)例,最后實(shí)際執(zhí)行的就是這個(gè)對(duì)象的方法。在執(zhí)行g(shù)etProxy返回的代理對(duì)象的方法時(shí)會(huì)調(diào)用PushHandler的invoke方法,其中proxy參數(shù)就是當(dāng)前的代理對(duì)象(java.lang.reflect.Proxy的子類),method是當(dāng)前執(zhí)行方法,args是參數(shù)數(shù)組。在這個(gè)例子里我們根據(jù)方法名來(lái)做對(duì)應(yīng)的檢查之后通過(guò)反射方法method.invoke()執(zhí)行。
具體的動(dòng)態(tài)代理運(yùn)行原理這里暫不展開(kāi),網(wǎng)上有很多相關(guān)的內(nèi)容,比如這篇

http://blog.jobbole.com/104433

3.AOP

aop(Aspect Oriented Programming) 翻譯過(guò)來(lái)就是面向方面/切面編程。關(guān)于aop的定義有許多,這里引用一個(gè)可能不是特別準(zhǔn)確但很容易理解的解釋:(出處www.zhihu.com/question/24863332/answer/253016908)

AOP是對(duì)OOP的一種補(bǔ)充。

面向?qū)ο?OOP)引入了繼承、多態(tài)、封裝,將系統(tǒng)的業(yè)務(wù)功能按照模塊劃分,每個(gè)模塊用一個(gè)或多個(gè)類來(lái)表示。

而對(duì)于一些系統(tǒng)功能,無(wú)法使用OOP的思想來(lái)實(shí)現(xiàn)它們。這些系統(tǒng)功能往往穿插在業(yè)務(wù)功能的各處,和業(yè)務(wù)代碼耦合在一起;而且系統(tǒng)功能往往會(huì)被重復(fù)使用,這就導(dǎo)致了模塊不利于復(fù)用,這就是使用OOP實(shí)現(xiàn)系統(tǒng)功能的弊端。

AOP即為面向切面編程,它把系統(tǒng)需求按照功能分門(mén)歸類,把它們封裝在一個(gè)個(gè)切面中,然后再指定這些系統(tǒng)功能往業(yè)務(wù)功能中織入的規(guī)則。最后由第三方機(jī)構(gòu)根據(jù)你指定的織入規(guī)則,將系統(tǒng)功能整合到業(yè)務(wù)功能中。

aop是一種思想而不是一種技術(shù)。所以說(shuō),如果拋開(kāi)spring,我上面寫(xiě)的動(dòng)態(tài)代理甚至靜態(tài)代理的例子也可以算是一種aop。
spring中的aop實(shí)現(xiàn)分為兩種,基于動(dòng)態(tài)代理的aop和基于AspectJ的aop,這里不得不吐槽國(guó)內(nèi)的各種文章,根本沒(méi)搞清二者的區(qū)別,或者打著spring aop的標(biāo)題然后開(kāi)始講aspectJ的使用,你抄我我抄他,越抄越混亂。

什么是AspectJ?

在網(wǎng)上一搜一大片所謂AspectJ的用法,其實(shí)都是AspectJ的“切面語(yǔ)法”,只是AspectJ框架的冰山一角,AspectJ是完全獨(dú)立于Spring存在的一個(gè)Eclipse發(fā)起的項(xiàng)目,官方關(guān)于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甚至可以說(shuō)是一門(mén)獨(dú)立的語(yǔ)言,我們??吹降脑趕pring中用的@Aspect注解只不過(guò)是Spring2.0以后使用了AspectJ的風(fēng)格而已本質(zhì)上還是Spring的原生實(shí)現(xiàn),關(guān)于這點(diǎn)Spring的手冊(cè)中有提及:

@AspectJ使用了Java 5的注解,可以將切面聲明為普通的Java類。@AspectJ樣式在AspectJ 5發(fā)布的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5一樣的注解,并使用AspectJ來(lái)做切入點(diǎn)解析和匹配。但是,AOP在運(yùn)行時(shí)仍舊是純的Spring AOP,并不依賴于AspectJ的編譯器或者織入器(weaver)。

so 我們常用的org.aspectj.lang.annotation包下的AspectJ相關(guān)注解只是使用了AspectJ的樣式,至于全套的AspectJ以及織入器,那完全是另一套獨(dú)立的東西。

名詞解釋

在看aop實(shí)現(xiàn)前,先解釋幾個(gè)基本名詞,對(duì)就是網(wǎng)上一搜一大片那些

advice
advice,常被翻譯成”增強(qiáng)“或者”通知“,實(shí)際上advice就是在切面中執(zhí)行的額外操作,拿上面動(dòng)態(tài)代理的例子來(lái)說(shuō)在PushHandler::invoke()方法中,對(duì)手機(jī)號(hào)碼以及郵箱地址的檢查就是兩個(gè)advice。在很多aop框架中advice是以攔截器的形式存在的,advice又常分為前置型advice和后置型advice,像手機(jī)號(hào)碼檢查這種就屬于前置的advice,返回結(jié)果記錄日志就屬于后置advice

join point
連接點(diǎn)很容易理解,先看下spring手冊(cè)上的定義

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 就是程序運(yùn)行時(shí)的一個(gè)特征點(diǎn),比如方法的執(zhí)行或者異常的拋出??梢园裫oin point理解為一個(gè)觸發(fā)條件。

point cut
point cut大部分情況下被翻譯為’切入點(diǎn)‘。很多人經(jīng)常搞不清楚join point 和 point cut的區(qū)別,實(shí)際上二者完全不是一個(gè)維度的概念,如果說(shuō)join point是名詞 point cut就是謂詞。pointcut是一個(gè)規(guī)則,指定了哪些切入點(diǎn)會(huì)被切入。
比如:在test.network.message包下所有類的push()方法執(zhí)行前,對(duì)入?yún)⒆鲂r?yàn) 其中push()就是一個(gè)join point , 在xx前,對(duì)入?yún)⑦M(jìn)行驗(yàn)證是一個(gè)advice,而”在test.network.message包下所有類的push()方法“就是一個(gè)point cut。
做個(gè)比喻的話,一個(gè)插排,每個(gè)插孔都是一個(gè)join point,而電視插在哪,電腦插在哪,整個(gè)一個(gè)布線規(guī)則就是一個(gè)point cut
aspect
在test.network.message包下所有類的push()方法執(zhí)行前,對(duì)入?yún)⒆鲂r?yàn) 整個(gè)這個(gè)行為就是一個(gè)切面了。所以切面可以理解為point cut和advice的集合。在使用AspectJ樣式時(shí),被@Aspect注解標(biāo)注的類就是一個(gè)切面。

為防止翻譯不統(tǒng)一造成的誤解,下面對(duì)名詞的使用直接使用英文原文

Spring AOP

spring aop常被人詬病復(fù)雜,難用。但又有多少新司機(jī)真正的用過(guò)而不是上來(lái)就被@Aspectj那一套洗腦了,問(wèn)你為什么spring aop難用,AspectJ的優(yōu)勢(shì)在哪又有多少人能答上來(lái)。
spring aop相關(guān)的內(nèi)容基本都在org.springframework.aop.framework包下,spring aop是通過(guò)代理工廠實(shí)現(xiàn)的,主要涉及的類圖如下:

兩個(gè)常用的aop工廠類ProxyFactoryBean和ProxyFactoryBean繼承自ProxyCreatorSupport,proxyCreator是代理類的基類,并擁有一個(gè)實(shí)現(xiàn)了AopProxyFactory接口的屬性DefaultAopProxyFactory,ProxyFactory中的getProxy方法實(shí)際上最后是調(diào)用的是AopProxy接口中的getProxy方法,而實(shí)現(xiàn)了AopProxy接口的對(duì)象(JdkDynamicAopProxy或CglibAopProxy)是由DefaultAopProxyFactory創(chuàng)建的。這么說(shuō)可能會(huì)有點(diǎn)繞,下面用一張時(shí)序圖來(lái)解釋(圖是我偷的,懶得畫(huà)了,出處:https://www.jianshu.com/p/500...)

DefaultAopProxyFactory在創(chuàng)建aop代理時(shí)會(huì)判斷委托類是否實(shí)現(xiàn)了某個(gè)接口,是的話創(chuàng)建JdkDynamicAopProxy,否則的話創(chuàng)建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);
        }
    }

下面看一個(gè)使用proxyFactory實(shí)現(xiàn)aop的例子

public class SpringAopTest {
    @Test
    public void proxy() {
        PushServiceImpl pushService = new PushServiceImpl();
        //創(chuàng)建工廠
        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);
    }
}

下面是測(cè)試運(yùn)行時(shí)的輸出:

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實(shí)現(xiàn)aop和上面的動(dòng)態(tài)代理的例子十分相似 ,spring aop中的advice就等同于我們?cè)趧?dòng)態(tài)代理中InvocationHandler中的增強(qiáng)操作。
常用的advice有四種:
前置advice:在方法執(zhí)行前觸發(fā)的advice,實(shí)現(xiàn)了 org.springframework.aop.MethodBeforeAdvice接口
后置advice: 在方法返回時(shí)觸發(fā)的advice,實(shí)現(xiàn)了org.springframework.aop.AfterReturningAdvice接口
異常advice:在拋出異常后觸發(fā),實(shí)現(xiàn)了org.springframework.aop.ThrowsAdviceArround
環(huán)繞advice:自定義觸發(fā)時(shí)機(jī),實(shí)現(xiàn)和InvokeHandler類似,實(shí)現(xiàn)了org.aopaliance.intercept.MethodInterceptor

advisor:
上面的例子,添加的advice在實(shí)際運(yùn)行時(shí)會(huì)包裝為Advisor對(duì)象,advisor包含了advice和pointcut,可以理解為一個(gè)切面(aspect),下面是AdvisedSupport類的addAdvice的方法實(shí)現(xiàn),可以看到在執(zhí)行addAdvice方法時(shí)會(huì)封裝為DefaultPointcutAdvisor實(shí)例

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被默認(rèn)設(shè)置為Poincut.TRUE,此時(shí)會(huì)匹配被代理對(duì)象的所有方法

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

    private Pointcut pointcut = Pointcut.TRUE;

除了直接設(shè)置advice以外,我們還可以設(shè)置advisor,并指定對(duì)應(yīng)的pointcut,這樣就可以指定哪些方法會(huì)被切入,pointcut和advisor有許多包裝類型,都在org.springframework.aop.support包下,這里使用最常用的正則pointcut:JdkRegexpMethodPointcut舉個(gè)栗子

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

可以看到這次我們沒(méi)有直接使用addAdvice而是創(chuàng)建了兩個(gè)advisor,并創(chuàng)建了一個(gè)pointcut,設(shè)置正則pattern匹配所有以push開(kāi)頭的方法。
這里有一個(gè)坑,我開(kāi)始在設(shè)置pattern時(shí)是直接寫(xiě)成 setPattern("push.*")的,發(fā)現(xiàn)沒(méi)有匹配,后來(lái)發(fā)現(xiàn)AbstractRegexpMethodPointcut的matches方法里是這樣寫(xiě)的

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

ClassUtils.getQualifiedMethodName方法會(huì)將method名稱拼上類名,即要匹配的方法名實(shí)際上是 spring.aop.PushServiceImpl.pushSms

在使用時(shí)我們會(huì)發(fā)現(xiàn)這樣手動(dòng)設(shè)置pointcut和advisor十分麻煩,所以spring aop提供了一些包裝好的advisor,比如上面的這個(gè)正則的例子我們就可以直接使用org.springframework.aop.supportRegexpMethodPointcutAdvisor,還有一些更方便的包裝advisor比如下面這個(gè)例子直接使用了NameMatchMethodPointcutAdvisor設(shè)置匹配的方法名

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

以上就是SpringAop的基本使用方法,通過(guò)上面的例子可以看出springAop的確存在一些問(wèn)題,最明顯的就是切面不夠獨(dú)立,對(duì)業(yè)務(wù)代碼的侵入性很強(qiáng),聲明Aspect需要以過(guò)程的形式顯示聲明(雖然ProxyFactoryBean可以將切面部分封裝為bean,但是我看到xml是在是想吐)。而且advice和pointcut的結(jié)合靈活性較差,實(shí)際使用時(shí)還需要自己寫(xiě)一些輪子。spring也認(rèn)識(shí)到了這些問(wèn)題并在spring2.0之后推出了AspectJ樣式的Aop

AspectJ樣式AOP

再次強(qiáng)調(diào)一下這里講的AspectJ樣式的aop只是使用了AspectJ的一些語(yǔ)法特性,底層依舊是SpringAop實(shí)現(xiàn)的
首先 使用aspectJ樣式的aop需要一些額外配置

springboot
如果你使用的是springboot,可以通過(guò)在主類使用@EnableAspectJAutoProxy注解來(lái)開(kāi)啟,另外如果你使用了@EnableAutoConfiguration會(huì)默認(rèn)開(kāi)啟。如果想關(guān)閉aop可以配置設(shè)置spring.aop.auto = false,spring.aop.proxy-target-class可以指定使用jdk代理還是cglib代理,默認(rèn)是jdk(false:jdk,true:cglib)

spring
普通的spring框架可以通過(guò)設(shè)置來(lái)開(kāi)啟,設(shè)置為true使用cglib

前面springAop已經(jīng)將aop的概念說(shuō)的很清楚這里就不扯淡了直接上個(gè)例子:
@Aspect注解將當(dāng)前類標(biāo)記為一個(gè)切面,@Component將PushAspect注冊(cè)為Bean
@Pointcut注解將一個(gè)void方法標(biāo)記為一個(gè)pointcut,execution、within、args等被稱為pointcut designators(切點(diǎn)標(biāo)志符)簡(jiǎn)稱為PCD,每個(gè)PCD都有對(duì)應(yīng)的表達(dá)式,比如最常用的execution,下面的例子第一個(gè)表示修飾符,即public還是private之類。后面緊接著是包名+類名+方法名,spring.aop.PushServiceImpl.* 表示匹配 spring.aop包下PushServiceImpl類的所有方法。最后是參數(shù)標(biāo)識(shí)(String,Stirng)表示參數(shù)是兩個(gè)String類型的方法,若要匹配所有參數(shù)類型,可以使用(..)表示

@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等注解標(biāo)識(shí)了一個(gè)advice方法,advice和pointcut相關(guān)聯(lián),像上面這個(gè)例子前置advice checkMobile()就是作用于被pushSmsPointcut標(biāo)識(shí)的方法上,pointcut可以顯示綁定在value或pointcut參數(shù)上,或者不寫(xiě)參數(shù)默認(rèn)第一表達(dá)式就是pointcut。也可以不寫(xiě)pointcut,在advice表達(dá)式內(nèi)直接寫(xiě)PCD,但是不建議這樣做

關(guān)于怎么向advice body里傳遞參數(shù),先看下手冊(cè)里的描述:

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

所以想要傳遞參數(shù)在advcie中使用args表達(dá)式即可,注意這里要在args里寫(xiě)參數(shù)名,而不是寫(xiě)參數(shù)類型。寫(xiě)參數(shù)類型的話是另一種用法,一般寫(xiě)在PCD中,作為joinpoint匹配時(shí)參數(shù)類型的篩選。另外傳遞的參數(shù)中還有一個(gè)特別的參數(shù)類型JoinPoint. jointpoint包含了很多實(shí)用的反射方法,必須獲取當(dāng)前的代理類,獲取參數(shù)列表等等,joinpoint可以作為第一個(gè)參數(shù)傳入advice且不用再參數(shù)列表里指定。這里注意使用around advice時(shí)傳入的是ProceedingJoinPoint類型的jointpoint

execution表達(dá)式中的參數(shù)和args
在designator表達(dá)式中,有兩種方式可以限定方法的參數(shù),一個(gè)是通過(guò)execution表達(dá)式的最后一個(gè)參數(shù),另一個(gè)是通過(guò)args標(biāo)識(shí)符,二者有什么區(qū)別?

args(java.lang.String)

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

區(qū)別是args表達(dá)式標(biāo)識(shí)的是在運(yùn)行時(shí)傳入的參數(shù)是String類型,而execution表達(dá)式表示的是方法簽名在定義的時(shí)候是String。
Aspectj風(fēng)格還有很多designators(with,@annotation等),以及advice類型,這里不再贅述,可以參考spring的手冊(cè)。雖然是英文的但寫(xiě)的很詳細(xì)

手冊(cè):https://docs.spring.io/spring...

下面是委托對(duì)象的執(zhí)行(實(shí)際上不應(yīng)該再叫他委托對(duì)象了,因?yàn)檫@里已經(jīng)將aop剝離出來(lái)了,用戶感知不到代理的過(guò)程)??梢钥吹胶虸OC結(jié)合起來(lái)十分方便,切面和業(yè)務(wù)代碼沒(méi)有任何耦合。這里如果把注入的SmsPushInterface換成SmsBean named "pushServiceImpl" is expected to be of type "spring.aop.PushServiceImpl" but was actually of type "com.sun.proxy.$Proxy61".可以看出底層的實(shí)現(xiàn)還是使用的動(dòng)態(tài)代理,如果這里想聲明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環(huán)境中自動(dòng)加載的,如果脫離這個(gè)環(huán)境想在過(guò)程中執(zhí)行怎么辦?spring提供了一個(gè)org.springframework.aop.aspectj.annotation.AspectJProxyFactory 代理工廠,和ProxyFactory一樣繼承ProxyCreatorSupport,通過(guò)該工廠可以像proxyFactory一樣直接顯示進(jìn)行代理

 @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實(shí)際上還是把@Aspect解析成Advisor來(lái)處理

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

由此可見(jiàn),AspectJ樣式的方便不只是體現(xiàn)在提供了一些方便的注解以及PCD,更體現(xiàn)在和Spring IOC的完美結(jié)合
關(guān)于@Aspect的解析原理,是在沒(méi)時(shí)間寫(xiě)了,以后有時(shí)間再補(bǔ)吧。
寫(xiě)的比較匆忙如有問(wèn)題歡迎指正

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69735.html

相關(guān)文章

  • SpringAOP面向切面詳解(帶實(shí)例)

    摘要:了解的相關(guān)術(shù)語(yǔ)通知通知定義了切面是什么,以及何時(shí)使用。描述了切面要完成的工作和何時(shí)需要執(zhí)行這個(gè)工作。就是用來(lái)配置切面設(shè)置代理模式。 了解AOP的相關(guān)術(shù)語(yǔ) 1.通知(Advice): 通知定義了切面是什么,以及何時(shí)使用。描述了切面要完成的工作和何時(shí)需要執(zhí)行這個(gè)工作。 2.連接點(diǎn)(Joinpoint): 程序能夠應(yīng)用通知的一個(gè)時(shí)機(jī),這些時(shí)機(jī)就是連接點(diǎn),例如方法被調(diào)用時(shí)、異常被拋出時(shí)等等。 ...

    馬忠志 評(píng)論0 收藏0
  • Spring AOP就是這么簡(jiǎn)單啦

    摘要:是一種特殊的增強(qiáng)切面切面由切點(diǎn)和增強(qiáng)通知組成,它既包括了橫切邏輯的定義也包括了連接點(diǎn)的定義。實(shí)際上,一個(gè)的實(shí)現(xiàn)被拆分到多個(gè)類中在中聲明切面我們知道注解很方便,但是,要想使用注解的方式使用就必須要有源碼因?yàn)槲覀円? 前言 只有光頭才能變強(qiáng) 上一篇已經(jīng)講解了Spring IOC知識(shí)點(diǎn)一網(wǎng)打盡!,這篇主要是講解Spring的AOP模塊~ 之前我已經(jīng)寫(xiě)過(guò)一篇關(guān)于AOP的文章了,那篇把比較重要的知...

    Jacendfeng 評(píng)論0 收藏0
  • 慕課網(wǎng)_《Spring入門(mén)篇》學(xué)習(xí)總結(jié)

    摘要:入門(mén)篇學(xué)習(xí)總結(jié)時(shí)間年月日星期三說(shuō)明本文部分內(nèi)容均來(lái)自慕課網(wǎng)。主要的功能是日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等。 《Spring入門(mén)篇》學(xué)習(xí)總結(jié) 時(shí)間:2017年1月18日星期三說(shuō)明:本文部分內(nèi)容均來(lái)自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)示例源碼:https://github.com/zccodere/s...個(gè)人學(xué)習(xí)源碼:https://git...

    Ververica 評(píng)論0 收藏0
  • Learn Spring - Spring AOP

    摘要:下例表示方法入?yún)榈姆椒ㄆヅ湓撉悬c(diǎn),并將和兩個(gè)參數(shù)綁定到切面方法的入?yún)⒅薪壎ù韺?duì)象使用或可以綁定被代理對(duì)象的實(shí)例。 1. 術(shù)語(yǔ) 連接點(diǎn)(JointPoint):代碼中具有邊界性質(zhì)特定點(diǎn);Spring僅支持方法的連接點(diǎn),包含方法和方位兩方面信息 切點(diǎn)(Pointcut):定位到某個(gè)方法 增強(qiáng)(Advice):織入到目標(biāo)連接點(diǎn)上的代碼 目標(biāo)對(duì)象(Target):增強(qiáng)邏輯的目標(biāo)織入類 引...

    kgbook 評(píng)論0 收藏0
  • Spring AOP零單排-織入時(shí)期源碼分析

    摘要:何為簡(jiǎn)單點(diǎn)來(lái)定義就是切面,是一種編程范式。定義一個(gè)切面的載體定義一個(gè)切點(diǎn)定義一個(gè)為,并指定對(duì)應(yīng)的切點(diǎn)一個(gè)注冊(cè)配置類,啟動(dòng)容器,初始化時(shí)期獲取對(duì)象,獲取對(duì)象時(shí)期,并進(jìn)行打印好了,這樣我們整體的代理就已經(jīng)完成。 問(wèn)題:Spring AOP代理中的運(yùn)行時(shí)期,是在初始化時(shí)期織入還是獲取對(duì)象時(shí)期織入? 織入就是代理的過(guò)程,指目標(biāo)對(duì)象進(jìn)行封裝轉(zhuǎn)換成代理,實(shí)現(xiàn)了代理,就可以運(yùn)用各種代理的場(chǎng)景模式。 ...

    honmaple 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<