掌握Spring AOP(一)核心概念及動(dòng)態(tài)代理

一攘乒、什么是Aop?

  1. Aop與Oop一樣脐彩,都是一種編程思想。
  2. Aop面向切換編程犁河, 其實(shí)就是無侵入的進(jìn)行功能增強(qiáng)鳖枕,使用Aop可以實(shí)現(xiàn)業(yè)務(wù)代碼和系統(tǒng)代碼分離(如日志記錄、權(quán)限控制桨螺、性能統(tǒng)計(jì)等通用功能)宾符。
  3. Aop的實(shí)現(xiàn)方式主要有幾種:AspectJ、Spring Aop灭翔、Spring整合AspectJ魏烫。
  4. Aop的核心思想,就是通過織入去增強(qiáng)代碼肝箱,織入又分為靜態(tài)織入和動(dòng)態(tài)織入哄褒。
  • 靜態(tài)織入:指的是不修改原代碼,只對(duì)class文件進(jìn)行修改煌张,實(shí)現(xiàn)功能增強(qiáng)呐赡。(AspectJ使用的就是這種,一般不使用)。
  • 動(dòng)態(tài)織入:指的是運(yùn)行時(shí)骏融,通過動(dòng)態(tài)代理技術(shù)链嘀,產(chǎn)生代理對(duì)象,完成功能增強(qiáng)档玻。(Spring Aop怀泊、Spring整合AspectJ使用的就是這種)。

二窃肠、Aop的基本概念

Aop核心功能圖解
Aop核心功能圖解.png
  1. 目標(biāo)對(duì)象
    被一個(gè)或多個(gè)切面增強(qiáng)的對(duì)象包个。
  2. 連接點(diǎn)(joinpoint)
    程序執(zhí)行中的某個(gè)具體的執(zhí)行點(diǎn)。
  3. 切入點(diǎn)(pointcut)
    對(duì)連接點(diǎn)的特征進(jìn)行描述,可以用正則表達(dá)式碧囊。增強(qiáng)處理和一個(gè)切入點(diǎn)表達(dá)式相關(guān)聯(lián)树灶,并在與這個(gè)切入點(diǎn)匹配的某個(gè)連接點(diǎn)上運(yùn)行。
  4. 代理對(duì)象
    由Aop框架所創(chuàng)建的對(duì)象糯而,實(shí)現(xiàn)執(zhí)行增強(qiáng)處理的方法天通。
  5. 織入(weave)
    將增強(qiáng)處理連接到應(yīng)用程序中的類型或?qū)ο笊系倪^程。
  6. 通知(advice)(增強(qiáng)功能)
    攔截到連接點(diǎn)之后要執(zhí)行的代碼熄驼,分為前置通知像寒、后置通知、異常通知瓜贾、最終通知诺祸、環(huán)繞通知五類。
  7. 切面(aspect)
    一個(gè)模塊化的橫切邏輯祭芦,可能會(huì)橫切多個(gè)對(duì)象筷笨。
  8. 通知器
    是一個(gè)特殊的切面。
  9. 引入(introduction)
    在不修改代碼的前提下龟劲,引入可以在運(yùn)行期為類動(dòng)態(tài)地添加一些方法或字段胃夏。

三、動(dòng)態(tài)代理

為什么使用代理昌跌?

簡單來說仰禀,代理就是幫【目標(biāo)對(duì)象】去完成它應(yīng)該做,但不想或不擅長做的事情蚕愤。(如社保代繳答恶,黃牛掛號(hào)等)

代理技術(shù)分為靜態(tài)代理和動(dòng)態(tài)代理。

  • 靜態(tài)代理:
    編寫一個(gè)代理類萍诱,去代理【目標(biāo)對(duì)象】亥宿。就是在寫源碼的時(shí)候,為目標(biāo)類編寫一個(gè)對(duì)應(yīng)的代理類(java文件)砂沛。
  • 動(dòng)態(tài)代理:
    在運(yùn)行期間,通過反射曙求,對(duì)【目標(biāo)對(duì)昂】產(chǎn)生一個(gè)【代理對(duì)象】碍庵。就是在運(yùn)行時(shí),通過不同的技術(shù)實(shí)現(xiàn)悟狱,去創(chuàng)建新的對(duì)象静浴。
為什么使用動(dòng)態(tài)代理?

增強(qiáng)對(duì)象的功能挤渐。這種增強(qiáng)苹享,符合開閉原則,不會(huì)對(duì)目標(biāo)對(duì)象就行修改,只需要擴(kuò)展就可以實(shí)現(xiàn)增強(qiáng)得问。

動(dòng)態(tài)代理技術(shù)常用的兩種(兩個(gè)方式生成的代理類都是繼承了Proxy):JDK動(dòng)態(tài)代理技術(shù)囤攀、CGLib動(dòng)態(tài)代理技術(shù)。

  • JDK動(dòng)態(tài)代理技術(shù)(基于接口):
    【目標(biāo)類】必須有接口才能使用的這種方式宫纬。
    【代理類】和【目標(biāo)類】其實(shí)都是【目標(biāo)類接口】的一個(gè)實(shí)現(xiàn)類焚挠,使用的是接口實(shí)現(xiàn)。
  • CGLib動(dòng)態(tài)代理技術(shù)(基于繼承):
    【目標(biāo)類】只要不是final修飾的就可以(可以被繼承的普通類)漓骚,不需要有接口蝌衔。
    【代理類】是【目標(biāo)類】的子類,通過繼承的方式產(chǎn)生的蝌蹂。

Spring默認(rèn)使用的是JDK噩斟,可以人為指定使用CGLib.

其它區(qū)別:

  1. JDK1.7之前,CGLib運(yùn)行比JDK要快孤个,之后效率差不多剃允,但是JDK產(chǎn)生代理對(duì)象的效率要高。
  2. CGLib底層是通過ASM字節(jié)碼工具包去實(shí)現(xiàn)的字節(jié)碼重寫硼身。而JDK只是相當(dāng)于幫程序員在后臺(tái)寫了java文件硅急,并編譯、加載 佳遂。
JDK和CGLib產(chǎn)生代理對(duì)象的方式和處理步驟是怎么樣的营袜?
  • JDK動(dòng)態(tài)技術(shù)產(chǎn)生代理對(duì)象:
  1. 產(chǎn)生代理對(duì)象
    Proxy.newProxyInstance(classloader,interfaces,【InvocationHandler實(shí)現(xiàn)類】)
/**
 * 主要作用就是生成代理類 使用JDK的動(dòng)態(tài)代理實(shí)現(xiàn) 它是基于接口實(shí)現(xiàn)的
 * 
 * @author think
 *
 */
public class JDKProxyFactory {

    /**
     * @return
     */
    public Object getProxy(Object target) {

        Class<?> clazz = UserService.class;
        Class<?> clazz2 = target.getClass();
        System.out.println(clazz);
        System.out.println(clazz2);
        System.out.println(clazz2.getInterfaces());
        System.out.println(target.getClass().getInterfaces());
        // 如何生成一個(gè)代理類呢?
        // 1丑罪、編寫源文件(java文件)----目錄類接口interface實(shí)現(xiàn)類(調(diào)用了目標(biāo)對(duì)象的方法)
        // class Proxy4{

        // InvocationHandler
        // 目標(biāo)對(duì)象
        // 目標(biāo)對(duì)象的方法
        // void saveUer(){
        // 動(dòng)態(tài)生成的
        // 需要自定義編寫
        // InvocationHandler.invoke(){
        // 編寫其他邏輯
        // 調(diào)用目標(biāo)對(duì)象的方法
        // }

        // }
        // }
        // 2荚板、編譯源文件為class文件
        // 3、將class文件加載到JVM中(ClassLoader)
        // 4吩屹、將class文件對(duì)應(yīng)的對(duì)象進(jìn)行實(shí)例化(反射)

        // Proxy是JDK中的API類
        // 第一個(gè)參數(shù):目標(biāo)對(duì)象的類加載器
        // 第二個(gè)參數(shù):目標(biāo)對(duì)象的接口
        // 第三個(gè)參數(shù):代理對(duì)象的執(zhí)行處理器
//       Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[] { clazz2 },new MyInvocationHandler(target));
        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
                new MyInvocationHandler(target));
        return proxy;
    }

2.調(diào)用InvocationHandler去完成增強(qiáng)跪另。

/**
 * JDK動(dòng)態(tài)代理使用的動(dòng)態(tài)增強(qiáng)的類
 * 
 * @author think
 *
 */
public class MyInvocationHandler implements InvocationHandler {

    // 目標(biāo)對(duì)象的引用
    private Object target;

    // 通過構(gòu)造方法將目標(biāo)對(duì)象注入到代理對(duì)象中
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    /**
     * 代理對(duì)象會(huì)執(zhí)行的方法
     * 第一個(gè)參數(shù):代理對(duì)象本身
     * 第二個(gè)參數(shù):目標(biāo)對(duì)象的方法對(duì)象(Method對(duì)象),一個(gè)方法針對(duì)一個(gè)Method對(duì)象
     * 第三個(gè)參數(shù):目標(biāo)對(duì)象的方法參數(shù)
     * 
     * 代理對(duì)象執(zhí)行的邏輯:
     *      需要執(zhí)行目標(biāo)對(duì)象的原方法煤搜?
     *          如何執(zhí)行目標(biāo)對(duì)象的原方法呢免绿?
     *          該處使用的是反射
     *          【要調(diào)用方法的Method對(duì)象.invoke(要調(diào)用方法的對(duì)象,要調(diào)用方法的參數(shù))】
     *      只是在調(diào)用目標(biāo)對(duì)象的原方法前邊和后邊可能要加上一些增強(qiáng)功能的代碼
     * 
     * 增強(qiáng)代碼比如:
     *      在原方法調(diào)用之前,開啟事務(wù)擦盾,源方法結(jié)束之后嘲驾,提交和回滾事務(wù)
     * 
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("這是jdk的代理方法");
        // 下面的代碼,是反射中的API用法
        // 該行代碼迹卢,實(shí)際調(diào)用的是[目標(biāo)對(duì)象]的方法
        // 利用反射辽故,調(diào)用[目標(biāo)對(duì)象]的方法
        Object returnValue = method.invoke(target, args);
        // AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        
        // ReflectiveMethodInvocation.proceed()進(jìn)行遞歸增強(qiáng)
        
        // 增強(qiáng)的部分
        return returnValue;
    }
}

3.生成的代理對(duì)象

public final class $Proxy4 extends Proxy implements Proxy4 {
    private static Method m1;
    private static Method m6;
    private static Method m2;
    private static Method m7;
    private static Method m11;
    private static Method m13;
    private static Method m0;
    private static Method m8;
    private static Method m12;
    private static Method m3;
    private static Method m5;
    private static Method m10;
    private static Method m4;
    private static Method m9;

    public $Proxy4(InvocationHandler var1) throws  {
        super(var1);
    }
   
    .......

     public final void saveUser() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

  static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m6 = Class.forName("com.sun.proxy.$Proxy4").getMethod("getInvocationHandler", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m7 = Class.forName("com.sun.proxy.$Proxy4").getMethod("getProxyClass", Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"));
            m11 = Class.forName("com.sun.proxy.$Proxy4").getMethod("getClass");
            m13 = Class.forName("com.sun.proxy.$Proxy4").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m8 = Class.forName("com.sun.proxy.$Proxy4").getMethod("wait");
            m12 = Class.forName("com.sun.proxy.$Proxy4").getMethod("notify");
            m3 = Class.forName("com.sun.proxy.$Proxy4").getMethod("saveUser");
            m5 = Class.forName("com.sun.proxy.$Proxy4").getMethod("newProxyInstance", Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler"));
            m10 = Class.forName("com.sun.proxy.$Proxy4").getMethod("wait", Long.TYPE);
            m4 = Class.forName("com.sun.proxy.$Proxy4").getMethod("isProxyClass", Class.forName("java.lang.Class"));
            m9 = Class.forName("com.sun.proxy.$Proxy4").getMethod("wait", Long.TYPE, Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4.測試

    @Test
    public void testJDKProxy() {

        // 1、創(chuàng)建目標(biāo)對(duì)象
        UserService service = new UserServiceImpl();
        // 2腐碱、生成代理對(duì)象
        JDKProxyFactory proxyFactory = new JDKProxyFactory();
        // 得到代理對(duì)象
        UserService proxy = (UserService) proxyFactory.getProxy(service);

        // 生成class文件
        generatorClass(proxy);

        // 3誊垢、調(diào)用目標(biāo)對(duì)象的方法
        service.saveUser();
        System.out.println("===============");
        // 4、調(diào)用代理對(duì)象的方法
        proxy.saveUser();
    }
  • CGLib動(dòng)態(tài)技術(shù)產(chǎn)生代理對(duì)象:
    1.生成代理對(duì)象
    Enhance enhance = new...
    //設(shè)置超類
    //設(shè)置回調(diào)----代理對(duì)象的方法被調(diào)用時(shí)

enhancer.create

/**
 * 主要作用就是生成代理類 使用CGLib動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn) 它是基于繼承的方式實(shí)現(xiàn)的
 * cglib底層是通過asm包去實(shí)現(xiàn)的字節(jié)碼的編寫
 * @author think
 *
 */
public class CgLibProxyFactory {

    /**
     * @param clazz
     * @return
     */
    public Object getProxyByCgLib(Class<?> clazz) {
        // 創(chuàng)建增強(qiáng)器
        Enhancer enhancer = new Enhancer();
        // 設(shè)置需要增強(qiáng)的類的類對(duì)象
        enhancer.setSuperclass(clazz);
        // 設(shè)置回調(diào)函數(shù)
        enhancer.setCallback(new MyMethodInterceptor());
        // 獲取增強(qiáng)之后的代理對(duì)象
        Object object = enhancer.create();

        // generatorClass(enhancer);

        return object;
    }

    /*
     * private void generatorClass(Enhancer enhancer) { FileOutputStream out = null;
     * try { byte[] bs = DefaultGeneratorStrategy.INSTANCE.generate(enhancer);
     * FileOutputStream fileOutputStream = new FileOutputStream("Enhancer_cglib" +
     * ".class"); fileOutputStream.write(bs); fileOutputStream.flush();
     * fileOutputStream.close(); } catch (Exception e) { e.printStackTrace(); }
     * finally { if (out != null) { try { out.close(); } catch (IOException e) { //
     * TODO Auto-generated catch block } } }
     * 
     * }
     */
}

2.調(diào)用MethodInterceptor去完成增強(qiáng)。

/**
 * cglib動(dòng)態(tài)代理使用的動(dòng)態(tài)增強(qiáng)的類
 * 
 * @author think
 *
 */
public class MyMethodInterceptor implements MethodInterceptor {

    /***
     * Object proxy:這是代理對(duì)象喂走,也就是[目標(biāo)對(duì)象]的子類
     * Method method:[目標(biāo)對(duì)象]的方法 
     * Object[] arg:參數(shù)
     * MethodProxy methodProxy:代理對(duì)象的方法
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
        // 因?yàn)榇韺?duì)象是目標(biāo)對(duì)象的子類
        // 該行代碼殃饿,實(shí)際調(diào)用的是父類目標(biāo)對(duì)象的方法
        System.out.println("這是cglib的代理方法");

        // 通過調(diào)用子類[代理類]的invokeSuper方法,去實(shí)際調(diào)用[目標(biāo)對(duì)象]的方法
        // Object returnValue = method.invoke(target, arg);
        Object returnValue = methodProxy.invokeSuper(proxy, arg);
        
        //  method.invoke(target, arg);
        
        //目標(biāo)對(duì)象的saveUser(){}
        //代理對(duì)象saveUser(){
        //super();
        //}
        
        // 代理對(duì)象調(diào)用代理對(duì)象的invokeSuper方法缴啡,而invokeSuper方法會(huì)去調(diào)用目標(biāo)類的invoke方法完成目標(biāo)對(duì)象的調(diào)用

        return returnValue;
    }

}

3.測試

    @Test
    public void testCgLibProxy() {
        // 創(chuàng)建目標(biāo)對(duì)象
        UserService service = new UserServiceImpl();

        // 生成代理對(duì)象
        CgLibProxyFactory proxyFactory = new CgLibProxyFactory();
        UserService proxy = (UserService) proxyFactory.getProxyByCgLib(service.getClass());

        // 調(diào)用目標(biāo)對(duì)象的方法
        service.saveUser();
        System.out.println("===============");
        // 調(diào)用代理對(duì)象的方法
        proxy.saveUser();
    }
使用代理對(duì)象增強(qiáng)功能的原理壁晒?

代理對(duì)象執(zhí)行時(shí),會(huì)調(diào)用InvocationHandler或者M(jìn)ethodInterceptor去完成增強(qiáng)业栅。

代理對(duì)象執(zhí)行

1.JDK

  InvocationHandler{
    target
    invoke(proxy,method,args){
        
        //調(diào)用目標(biāo)對(duì)象(反射的API)
        method.invoke(target,args)
    }
  }

2.CGLib

  MethodInterceptor{
     
      intercept(proxy,method,args,proxyMethod){
        
        //調(diào)用目標(biāo)對(duì)象(代理對(duì)象的API)
        //代理對(duì)象又會(huì)調(diào)用目標(biāo)對(duì)象
        proxyMethod.invokeSuper(proxy,args)
        //method.invoke(target,args)
      }

  }
什么時(shí)候會(huì)調(diào)用以上的方法秒咐?

當(dāng)代理對(duì)象被創(chuàng)建的時(shí)候,是不會(huì)調(diào)用以上的方法的碘裕。
當(dāng)代理對(duì)象被訪問其中方法的時(shí)候携取,才會(huì)調(diào)用以上兩種(互斥)方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帮孔,一起剝皮案震驚了整個(gè)濱河市雷滋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌文兢,老刑警劉巖晤斩,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姆坚,居然都是意外死亡澳泵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門兼呵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兔辅,“玉大人,你說我怎么就攤上這事击喂∥Γ” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵懂昂,是天一觀的道長介时。 經(jīng)常有香客問我,道長凌彬,這世上最難降的妖魔是什么潮尝? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮饿序,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羹蚣。我一直安慰自己原探,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咽弦,像睡著了一般徒蟆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上型型,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天段审,我揣著相機(jī)與錄音,去河邊找鬼闹蒜。 笑死寺枉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绷落。 我是一名探鬼主播姥闪,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼砌烁!你這毒婦竟也來了筐喳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤函喉,失蹤者是張志新(化名)和其女友劉穎避归,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體管呵,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梳毙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撇寞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顿天。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蔑担,靈堂內(nèi)的尸體忽然破棺而出牌废,到底是詐尸還是另有隱情,我是刑警寧澤啤握,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布鸟缕,位于F島的核電站,受9級(jí)特大地震影響排抬,放射性物質(zhì)發(fā)生泄漏懂从。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一蹲蒲、第九天 我趴在偏房一處隱蔽的房頂上張望番甩。 院中可真熱鬧,春花似錦届搁、人聲如沸缘薛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宴胧。三九已至漱抓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恕齐,已是汗流浹背乞娄。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留显歧,地道東北人仪或。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像追迟,于是被迫代替她去往敵國和親溶其。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容