aop學習筆記之代理模式(三)

1 代理概念

織入的時機分三種:
1.編譯期織入(Aspect)
2.類加載時織入(AspectJ 5+)
3.運行時織入(Spring AOP)

那如何實現運行時織入呢?通過代理。
這裏寫圖片描述

上面這張圖描述了調用方、代理對象、目標對象之間的關係,目標對象將自己託管給代理對象。比如當調用方發起請求執行目標對象中的方法,代理對象會響應並決定自己在其之前或之後執行相關動作,而原本目標對象該執行的方法代理對象並不去管它,依然拋給目標對象自己執行。代理對象只處理目標對象方法中沒有的動作。

2 代理模式

代理又分爲哪些呢?
這裏寫圖片描述
這裏列出了兩種動態代理的代表,Aop在創建代理時就是從他們之中選擇。

3 靜態代理

接下來我們用代碼模擬一下靜態代理的實現

這裏寫圖片描述
通過我上面畫的這張圖我們可以看到,Proxy和RealSubject都繼承了一個叫做Subject的接口,當請求從Client發起時,會被代理對象進行處理,執行一些額外的方法,然後又委託目標對象執行目標對象的方法

我們按照這個思路來寫代碼

3.1 Subject

首先我們創建一個接口叫做Subject,裏面只有一個叫做request的方法

package com.example.proxy.pattern;

/**
 * Subject class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public interface Subject {
    void request();
}

3.2 RealSubject

創建目標對象繼承上面的Subject接口,重寫一下request方法

package com.example.proxy.pattern;

/**
 * RealSubject class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("real subject execute request");
    }
}

3.3 Proxy

寫完了目標對象接下來就是代理對象了,同樣繼承Subject接口

package com.example.proxy.pattern;

/**
 * Proxy class
 *
 * aspect
 * @author TransientBa
 * @date 2018/3/10
 */
public class Proxy implements Subject{
    /**私有一個目標對象的屬性**/
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject){
        this.realSubject = realSubject;
    }

    /**
     * 靜態代理 通過重寫request 將目標方法委託給目標對象realSubject執行,自己執行其他額外的操作
     */
    @Override
    public void request() {
        System.out.println("before do something");
        try{
            realSubject.request();
        }catch (Exception e){
            System.out.println("exception:" + e.getMessage());
            /** 代理對象不會真正改變方法  還是要把異常拋出來**/
            throw e;
        }finally {
            System.out.println("after do something");
        }
    }
}

3.4 Client測試

接下來我們測試一下整個流程

package com.example.proxy.pattern;

/**
 * Client class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class Client {
    /**
     * 靜態代理
     * 缺點:
     * 代理對象要對目標對象中的方法進行委託  代理的邏輯越多 重複的邏輯越多
     * @param args
     */
    public static void main(String[] args) {
        /**客戶端通過接口引用目標對象  引入的實現類是代理的實現類  其本身又委託給了目標對象去執行  自己只執行邊緣的操作**/
        Subject subject = new Proxy(new RealSubject());
        subject.request();
    }

運行結果:
這裏寫圖片描述

這樣我們就完成一個簡單的靜態代理,那這種代理模式有哪些問題呢?我們回到代理類Proxy中,每當我們在Subject中新增一個方法,我們都需要手動的在Proxy中將新增的方法重寫。這樣實現起來很麻煩,我們可不可以用一種更通用的辦法去解決呢?

4 jdk動態代理

爲了解決上面提到的問題,我們重寫代理類

4.1 JdkProxySubject

拋棄了原來的Proxy類,重寫JdkProxySubject去替代它,在jdk動態代理中需要繼承InvocationHandler接口,重寫invoke方法

package com.example.proxy.dynamic;

import com.example.proxy.pattern.RealSubject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * JdkProxySubject class
 *
 * aspect
 * @author TransientBa
 * @date 2018/3/10
 */
public class JdkProxySubject implements InvocationHandler{
    private RealSubject realSubject;

    public JdkProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    /***
     * 通過method 利用反射去動態的代理目標類方法  無需像靜態代理一樣去寫每一個要代理的方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before do something");
        Object result = null;
        try{
            result = method.invoke(realSubject,args);
        }catch (Exception e){
            //依然不能對異常進行捕獲 還是要拋出去
            System.out.println("exception:"+e.getMessage());
            throw e;
        }finally {
            System.out.println("after do something");
        }
        return result;
    }
}

4.2 Client測試

在新建一個Client測試類

package com.example.proxy.dynamic;


import com.example.proxy.pattern.RealSubject;
import com.example.proxy.pattern.Subject;

import java.lang.reflect.Proxy;


/**
 * Client class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class Client {
    /**
     * 基於接口的Jdk動態代理
     * 優點:
     * 當Subject中新增一個方法時,靜態代理必須要重新實現新增的方法,而動態代理則根據反射無需再次添加
     * @param args
     */
    public static void main(String[] args) {
        //通過設置系統變量  把生成的字節碼保存下來
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

        /**
         * newProxyInstance傳入三個參數
         * ClassLoader:當前類的類加載器
         * Subject.Class:目標接口
         * JdkProxySubject:InvocationHandler以及創建時引用的目標對象RealSubject
         *
         * */
        Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(),
                new Class[]{Subject.class},
                new JdkProxySubject(new RealSubject()));

        subject.request();
    }
}

運行結果如下:
這裏寫圖片描述
爲了驗證jdk動態代理是否解決了靜態代理的問題,我們在添加一個方法dynamicRequest
在Subject中添加

 void dynamicRequest();

在RealSubject中添加

@Override
    public void dynamicRequest() {
        System.out.println("real subject execute dynamicRequest");
    }

回到client中,調用剛纔添加的方法

    subject.request();
    System.out.println();
    subject.dynamicRequest();

運行結果如下:
這裏寫圖片描述

可以看到通過反射動態的代理了目標接口的方法,無需像靜態代理一樣重寫方法。
爲了弄清楚在這個過程中代理類到底做了什麼,我們在Client中添加這樣一句話

 //通過設置系統變量  把生成的字節碼保存下來
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

正常運行程序後會在項目的目錄下找到
這裏寫圖片描述
打開該文件後可以看到

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.example.proxy.pattern.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

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

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

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.example.proxy.pattern.Subject").getMethod("dynamicRequest", new Class[0]);
            m4 = Class.forName("com.example.proxy.pattern.Subject").getMethod("request", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我們可以看到該類中編譯了5個方法
這裏寫圖片描述
其中dynamicRequest和request是我們要代理的方法

5 Cglib動態代理

上面的jdk動態代理基於接口,而Cglib基於繼承

5.1 CglibProxySubject

不同的是jdk動態代理繼承了InvocationHandler接口 ,而Cglib繼承了MethodInterceptor

package com.example.proxy.cglib;


import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CglibProxySubject class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class CglibProxySubject implements MethodInterceptor {
    /**
     * 同樣類似於jdkProxy ,調用methodProxy.invoke傳入要反射的對象和參數
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before in chlib");
        Object result = null;
        try{
            result = methodProxy.invokeSuper(o,objects);
        }catch (Exception e){
            System.out.println("get exception:" + e.getMessage());
            throw e;
        }finally {
            System.out.println("after in cglib");
        }
        return result;
    }
}

5.2 Client測試

package com.example.proxy.cglib;

import com.example.proxy.pattern.RealSubject;
import com.example.proxy.pattern.Subject;
import org.springframework.aop.framework.DefaultAopProxyFactory;
import org.springframework.cglib.proxy.Enhancer;

/**
 * Client class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public class Client {
    /**
     * 基於繼承的cglib動態代理
     * 優點:
     * 同jdkProxy一樣爲動態代理,但其基於繼承所以無接口的類同樣可以進行代理
     * @param args
     */
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //傳入目標對象
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new CglibProxySubject());
        Subject subject = (Subject) enhancer.create();
        subject.dynamicRequest();
    }
}

運行結果如下:
這裏寫圖片描述

6 Aop代理的選擇

上面我們瞭解了Aop代理的模式,那Spring在創建Aop代理時是如何選擇應用哪一種模式的呢?
首先我們要了解Aop在創建代理時都調用了哪些類的哪些方法
這裏寫圖片描述

我們可以看到在最後DefaultAopProxyFactory中調用了createAopProxy方法

  public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            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.");
            } else {
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass)?new ObjenesisCglibAopProxy(config):new JdkDynamicAopProxy(config));
            }
        }
    }

其中

return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass)?new ObjenesisCglibAopProxy(config):new JdkDynamicAopProxy(config));

反過來說,當代理對象原本就是一個接口時,或本身就是jdk代理,spring選擇Jdk代理,不然選擇Cglib
if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config))都不滿足時也選擇應用Jdk動態代理

6.1 強制使用Cglib代理

當proxyTargetClass爲True時,Spring強制使用Cglib代理,例如
這裏寫圖片描述

7 總結

  1. JDK動態代理方法只能針對有接口的類的接口方法進行動態代理 因接口中不允許private方法 所以同樣不能對private方法進行代理
  2. cglib基於繼承來實現代理,無法對static、final這種類進行代理,也無法對private、static方法進行代理
  3. 目標對象實現了接口,則採用JDK代理
  4. 目標對象沒有實現接口,採用Cglib代理
  5. 目標對象實現了接口,但強制使用Cglib代理,也是採用Cglib代理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章