Spring AOP原理

參考示例源代碼:

https://github.com/huangzhenshi/SpringFreamworkDemo/tree/master

概述

  1. Spring代理模式的運用場景: 操作日志改橘、事務(wù)控制识啦、異常處理(AOP ThrowsAdvice)、權(quán)限認(rèn)證恍涂、緩存

  2. Spring AOP實現(xiàn)的默認(rèn)兩種方式:DK動態(tài)代理右蕊、CGLIB

  • JDK動態(tài)代理 反射原理的類依賴其接口砰奕,代理接口的實現(xiàn)方法,普通方法無法代理燎含。
  • CGLIB宾濒,字節(jié)碼增強(qiáng)技術(shù),根據(jù)代理類實現(xiàn)一個增強(qiáng)的子類,采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用屏箍,并順勢織入橫切邏輯
  1. 設(shè)置proxy-target-class=true绘梦,全部都用CGLIB橘忱,false則接口類用反射,非接口類用CGLIB

  2. Spring實現(xiàn)代理有4種方式:子類繼承卸奉、@Aspect注解钝诚、JDK動態(tài)代理、CGLIB榄棵,各有各適用的場景

代理模式分析

  • 繼承的方法凝颇,最靈活,可以精準(zhǔn)實現(xiàn)一個被代理類的一個方法疹鳄。

  • @Aspect 先定義一個自定義注解拧略,再定義一個@Aspect類,并且把切點指定為自定義注解瘪弓,編寫不同業(yè)務(wù)場景下的代理邏輯垫蛆,最后直接在需要代理的方法上使用。優(yōu)點是非常容易復(fù)用腺怯,而且封裝了復(fù)雜性袱饭。編譯期生成代碼。

  • JDK動態(tài)代理瓢喉,半靈活宁赤,把需要代理的方法寫在繼承接口里面舀透,不希望被代理的方法栓票,寫在普通方法里面。

  • CGLIG最方便愕够,不依賴接口走贪,全部方法都用相同的代理邏輯

  1. 可以通過繼承的方式,然后重寫父類的方法惑芭,這樣實現(xiàn)代理模式坠狡。
    優(yōu)勢:特別靈活,可以根據(jù)業(yè)務(wù)需求遂跟,為某個代理類的每個方法實現(xiàn)不同的代理邏輯
    缺點:代碼量太大逃沿。如果一個類的多個方法代理邏輯一樣,或者多各類的代理邏輯一樣幻锁,無法復(fù)用凯亮。

  2. @Aspect是針對某一類方法而創(chuàng)造的,比如對service層的所有新增的方法哄尔,添加日志的功能假消。
    優(yōu)勢: 編碼簡潔(寫一個工具注解既可以,然后在需要代理的類上做注解即可)岭接,可以實現(xiàn)精準(zhǔn)代理富拗,是針對方法的代理模式

  1. JDK動態(tài)代理臼予,實體類所有的接口方法,都會執(zhí)行相同的代理邏輯
    優(yōu)點:可以批量代理一個代理類里面所有接口中的方法啃沪;不同的代理類繼承相同的接口粘拾,可以復(fù)用代理工具類,只要代理邏輯一樣
    缺點:完全依賴接口谅阿,所有要代理的方法必須要在接口中存在半哟,否則無法代理

  2. CGLIG代理模式:
    優(yōu)點:沒有束縛,通過特殊的處理签餐,通過繼承和織入寓涨,對該類的所有的方法,都織入相同的代理邏輯
    缺點:缺乏靈活性氯檐,無法為代理類的某一個方法定制特殊的代理邏輯

JDK動態(tài)代理實現(xiàn)

實現(xiàn)步驟:

  • 寫一個接口Subject戒良,把所有要代理的方法都要寫在接口里面
  • 被代理類RealSubject實現(xiàn)接口,實現(xiàn)自己本身的業(yè)務(wù)邏輯冠摄,當(dāng)然也可以包含一些不想被代理的方法糯崎,只要接口里沒有,都不會被代理
  • 寫一個代理工具類Dynamicproxy河泳,實現(xiàn)InvocationHandler接口沃呢,實現(xiàn)invoke方法,編寫代理邏輯
  • 創(chuàng)建代理類拆挥,調(diào)用代理方法

注意事項:被代理類里面可以包含接口里面沒有的方法薄霜,實現(xiàn)保護(hù)某些方法被代理,這些未暴露的方法纸兔,在代理類中是不可見的惰瓜,也不能被調(diào)用。

代碼

public interface Subject {
    public void rent();
    public void hello(String str);
}

public class RealSubject implements Subject{
    @Override
    public void rent() {
        System.out.println("I want rent my house");
    }
    @Override
    public void hello(String str) {
        System.out.println("hello:"+str);
    }
    public void test(){
        System.out.println("Test");
    }
}

代理工具類

public class Dynamicproxy implements InvocationHandler{
    
    //這就是我們代理的真實對象
    private Object subject;
    
    //構(gòu)造方法汉矿,給我們要代理的真實對象賦值
    public Dynamicproxy(Object subject){
        this.subject = subject;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在代理真實對象前我們可以添加一些自己的操作
        System.out.println("代理開始");
        System.out.println("代理Method:"+method);
        
        //當(dāng)代理對象調(diào)用真實對象的方法時崎坊,其會自動的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進(jìn)行調(diào)用
        method.invoke(subject, args);
        
        //在代理真實對象后我們可以添加一些自己的操作
        System.out.println("代理結(jié)束");
        return null;
    }
}

代理

public class JDK動態(tài)代理 {
    public static void main(String[] args) {
        //我們要代理的真實對象
        RealSubject realSubject = new RealSubject();
        //我們要代理哪個真實對象,就將該對象傳進(jìn)去洲拇,最后通過該真實的對象調(diào)用該方法
        InvocationHandler handler = new Dynamicproxy(realSubject);
        
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}

CGLib實現(xiàn)

  • 通過實現(xiàn)MethodInterceptor的intercept方法奈揍,來對所有的代理類進(jìn)行代理操作。
  • 具體涉及到繼承的增強(qiáng)類子類Enhancer類赋续。并設(shè)置子類的方法調(diào)用觸發(fā)器
public class CglibProxy implements MethodInterceptor {
    // 被代理對象
    Object targetObject;

    //參數(shù)為被代理類男翰,返回結(jié)果為代理類
    public Object getProxyObject(Object object) {
        this.targetObject = object;
        //增強(qiáng)器,動態(tài)代碼生成器
        Enhancer enhancer=new Enhancer();
        //回調(diào)方法
        enhancer.setCallback(this);
        //設(shè)置生成類的父類類型
        enhancer.setSuperclass(targetObject.getClass());
        //動態(tài)生成字節(jié)碼并返回代理對象
        return enhancer.create();
    }
    // 攔截方法
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法調(diào)用前");
        // 調(diào)用方法
        Object result = methodProxy.invoke(targetObject, args);
        System.out.println("方法調(diào)用后");
        return result;
    }
}

Spring中的配置

  • Pointcut(切點):指定在哪些類的哪些方法上織入橫切邏輯
  • Advice(增強(qiáng)):描述橫切邏輯和方法的具體織入點(方法前蚕捉、方法后奏篙、方法的兩端等)
  • Advisor(切面):將Pointcut和Advice兩者組裝起來。有了Advisor的信息,Spring就可以利用JDK或CGLib的動態(tài)代理技術(shù)采用統(tǒng)一的方式為目標(biāo)Bean創(chuàng)建織入切面的代理對象了
 <aop:config expose-proxy="true" proxy-target-class="true">
        <aop:pointcut id="txPointcut" expression="execution(* com.test.service..*.*(..))"/>
        <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
    </aop:config>

自定義注解的使用

http://www.reibang.com/p/7dcd59bdbb0a

  • 通過元注解給注解做修飾
  • 在對應(yīng)的地方使用注解(可以是類上加注解秘通、方法上加注解为严、屬性上加注解)
  • 通過反射獲取到目標(biāo)(類、方法肺稀、屬性)第股,并調(diào)用 ***.getAnnotation(),從而獲取注解類
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMessage {
        String name() default "sam";
        int num() default 0;
        String desc();
}

public class AnnotationTest {
    @MyMessage(num = 10, desc = "參數(shù)a")
    private static int a;


    @MyMessage(name = "Sam test", desc = "測試方法test")
    public void test() {
        System.out.println("test");
    }
}

public static void main(String[] args) throws Throwable{
            //加載annotationTest.class類
            Class clazz = Class.forName("com.test.aop.自定義注解.AnnotationTest");
            //獲取屬性
            Field[] fields = clazz.getDeclaredFields();
            //遍歷屬性
            for (Field field : fields) {
                MyMessage myMessage = field.getAnnotation(MyMessage.class);
                System.out.println("name:" + myMessage.name() + "  num:" + myMessage.num() + "  desc:" + myMessage.desc());
            }
            //獲取類中的方法
            Method[] methods = clazz.getMethods();
            //遍歷方法
            for (Method method : methods) {
                //判斷方法是否帶有MyMessage注解
                if (method.isAnnotationPresent(MyMessage.class)) {
                    // 獲取所有注解 method.getDeclaredAnnotations();
                    // 獲取MyMessage注解
                    MyMessage myMessage = method.getAnnotation(MyMessage.class);
                    System.out.println("name:" + myMessage.name() + "  num:" + myMessage.num() + "  desc:" + myMessage.desc());
                }
            }
    }

Aspect

https://www.cnblogs.com/magicalSam/p/7161942.html
原理:通過自定義注解+Aspect技術(shù)實現(xiàn)AOP代理

  • 通過@Aspect注解使該類成為切面類
  • 通過@Pointcut 指定切入點 话原,這里指定的切入點為MyLog注解類型夕吻,也就是被@MyLog注解修飾的方法,進(jìn)入該切入點繁仁。
  • @Before 前置通知:在某連接點之前執(zhí)行的通知涉馅,但這個通知不能阻止連接點之前的執(zhí)行流程(除非它拋出一個異常)。
  • @Around 環(huán)繞通知:可以實現(xiàn)方法執(zhí)行前后操作黄虱,需要在方法內(nèi)執(zhí)行point.proceed(); 并返回結(jié)果稚矿。
  • @AfterReturning 后置通知:在某連接點正常完成后執(zhí)行的通知:例如,一個方法沒有拋出任何異常捻浦,正常返回晤揣。
  • @AfterThrowing 異常通知:在方法拋出異常退出時執(zhí)行的通知。
  • @After 后置通知:在某連接點正常完成后執(zhí)行的通知:例如朱灿,一個方法沒有拋出任何異常昧识,正常返回
@Aspect 
@Component
public class MyLogAspect {
     //切入點
    @Pointcut(value = "@annotation(com.test.aop.aspectj.MyLog)")
    private void pointcut() {

    }
     //pointcut() 設(shè)置切點,也就是aspect綁定Mylog注解的一個紐帶
     //@annotation(myLog) 注入當(dāng)前的mylog盗扒,這樣才可以正常的mylog.requestUrl了
     @Around(value = "pointcut()&& @annotation(myLog)")
     public Object around(ProceedingJoinPoint point, MyLog myLog) {

         System.out.println("++++執(zhí)行了around方法++++");
         //攔截的類名
         Class clazz = point.getTarget().getClass();
         //攔截的方法
         Method method = ((MethodSignature) point.getSignature()).getMethod();

         System.out.println("執(zhí)行了 類:" + clazz + " 方法:" + method + " 自定義請求地址:"+myLog.requestUrl());

         try {
             Object result=point.proceed();
             System.out.println("++++執(zhí)行了around方法結(jié)束++++");
             return  result;//執(zhí)行程序
         } catch (Throwable throwable) {
             throwable.printStackTrace();
             return throwable.getMessage();
         }
     }
}

參考

https://zhuanlan.zhihu.com/p/29483023

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末跪楞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子环疼,更是在濱河造成了極大的恐慌习霹,老刑警劉巖朵耕,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炫隶,死亡現(xiàn)場離奇詭異,居然都是意外死亡阎曹,警方通過查閱死者的電腦和手機(jī)伪阶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來处嫌,“玉大人栅贴,你說我怎么就攤上這事⊙#” “怎么了檐薯?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我坛缕,道長墓猎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任赚楚,我火速辦了婚禮毙沾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宠页。我一直安慰自己左胞,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布举户。 她就那樣靜靜地躺著烤宙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俭嘁。 梳的紋絲不亂的頭發(fā)上门烂,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音兄淫,去河邊找鬼屯远。 笑死,一個胖子當(dāng)著我的面吹牛捕虽,可吹牛的內(nèi)容都是我干的慨丐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泄私,長吁一口氣:“原來是場噩夢啊……” “哼房揭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晌端,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤捅暴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咧纠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓬痒,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年漆羔,在試婚紗的時候發(fā)現(xiàn)自己被綠了梧奢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡演痒,死狀恐怖亲轨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸟顺,我是刑警寧澤惦蚊,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響蹦锋,放射性物質(zhì)發(fā)生泄漏曾撤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一晕粪、第九天 我趴在偏房一處隱蔽的房頂上張望挤悉。 院中可真熱鬧,春花似錦巫湘、人聲如沸装悲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诀诊。三九已至,卻和暖如春阅嘶,著一層夾襖步出監(jiān)牢的瞬間属瓣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工讯柔, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留抡蛙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓魂迄,卻偏偏與公主長得像粗截,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捣炬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355