1. 程式人生 > >動態代理、CGlib、AOP

動態代理、CGlib、AOP

Java 代理模式實現方式,主要有如下五種方法

  • 靜態代理,工程師編輯代理類程式碼,實現代理模式;在編譯期就生成了代理類。

  • 基於 JDK 實現動態代理,通過jdk提供的工具方法Proxy.newProxyInstance動態構建全新的代理類(繼承Proxy類,並持有InvocationHandler介面引用 )位元組碼檔案並例項化物件返回。(jdk動態代理是由java內部的反射機制來例項化代理物件,並代理的呼叫委託類方法)

  • 基於CGlib 動態代理模式 基於繼承被代理類生成代理子類,不用實現介面。只需要被代理類是非final 類即可。(cglib動態代理底層是藉助asm位元組碼技術

  • 基於 Aspectj 實現動態代理(修改目標類的位元組,織入代理的位元組,在程式編譯的時候 插入動態代理的位元組碼,不會生成全新的Class )

  • 基於 instrumentation 實現動態代理(修改目標類的位元組碼、類裝載的時候動態攔截去修改,基於javaagent) -javaagent:spring-instrument-4.3.8.RELEASE.jar (類裝載的時候 插入動態代理的位元組碼,不會生成全新的Class )

Notes

  • 委託類 即指的是代理模式中的被代理物件

  • 代理類 指的是生成的代表委託類的一個角色

靜態代理實現

靜態代理是代理類在編譯期間就建立好了,不是編譯器生成的代理類,而是手動建立的類。在編譯時就已經將介面,被代理類,代理類等確定下來。,軟體設計中所指的代理一般是指靜態代理,也就是在程式碼中顯式指定的代理。

實現步驟

  • 委託類和代理類之間的約束介面Cat

  • 約束介面實現類 Lion,實現 Cat 介面,委託角色

  • 代理類實現 FeederProxy,實現Cat 介面,並含有一個 Cat介面引用屬性。 代理角色,代理 cat介面屬性引用例項的行為並可以新增公共邏輯

Cat介面

package org.vincent.proxy.staticproxy;/**
 * @author PengRong
 * @package org.vincent.proxy.staticproxy
 * @date 2018/12/15 - 17:12
 * @ProjectName JavaAopLearning
 * @Description: 靜態代理類介面, 委託類和代理類都需要實現的介面規範。
 * 定義了一個貓科動物的兩個行為介面,吃東西,奔跑。
 * 作為代理類 和委託類之間的約束介面
 */public interface Cat {    public String eatFood(String foodName);    public boolean running();
}

委託類 Lion

package org.vincent.proxy.staticproxy;/**
 * @author PengRong
 * @package org.vincent.proxy.staticproxy
 * @date 2018/12/15 - 17:15
 * @ProjectName JavaAopLearning
 * @Description: 獅子 實現了貓科動物介面Cat, 並實現了具體的行為。作為委託類實現
 */public class Lion implements Cat {    private String name;    private int runningSpeed;    public String getName() {        return name;
    }    public void setName(String name) {        this.name = name;
    }    public int getRunningSpeed() {        return runningSpeed;
    }    public void setRunningSpeed(int runningSpeed) {        this.runningSpeed = runningSpeed;
    }    public Lion() {
    }    @Override
    public String eatFood(String foodName) {
        String eat = this.name + " Lion eat food. foodName = " + foodName;
        System.out.println(eat);        return eat;
    }    @Override
    public boolean running() {
        System.out.println(this.name + " Lion is running . Speed :" + this.runningSpeed);        return false;
    }
}

代理類角色(FeederProxy)

package org.vincent.proxy.staticproxy;/**
 * @author PengRong
 * @package org.vincent.proxy.staticproxy
 * @date 2018/12/15 - 17:19
 * @ProjectName JavaAopLearning
 * @Description: 飼養員 實現Cat介面,作為靜態代理類實現。代理獅子的行為。
 * 代理類中可以新增一些其他行為,在實踐中主要做的是引數校驗的功能。
 */public class FeederProxy implements Cat {    private Cat cat;    public FeederProxy(){}    public FeederProxy(Cat cat) {        if (cat instanceof Cat) {            this.cat = cat;
        }
    }    public void setCat(Cat cat) {        if (cat instanceof Cat) {            this.cat = cat;
        }
    }    @Override
    public String eatFood(String foodName) {
        System.out.println("proxy Lion exec eatFood ");        return cat.eatFood(foodName);
    }    @Override
    public boolean running() {
        System.out.println("proxy Lion exec running.");        return cat.running();
    }
}

靜態代理類測試

package org.vincent.proxy;import org.vincent.proxy.staticproxy.Cat;import org.vincent.proxy.staticproxy.FeederProxy;import org.vincent.proxy.staticproxy.Lion;/**
 * @author PengRong
 * @package org.vincent.proxy
 * @date 2018/12/15 - 18:31
 * @ProjectName JavaAopLearning
 * @Description: 靜態代理類測試
 */public class staticProxyTest {    public static void main(String[] args) {
        Lion lion = new Lion();
        lion.setName("獅子 小王");
        lion.setRunningSpeed(100);        /**
         * new 靜態代理類,靜態代理類在編譯前已經建立好了,和動態代理的最大區別點
         */
        Cat proxy = new FeederProxy(lion);

        System.out.println(Thread.currentThread().getName()+" -- " + proxy.eatFood("水牛"));
        proxy.running();
    }
}

靜態代理很好的詮釋了代理設計模式,代理模式最主要的就是有一個公共介面(Cat),一個委託類(Lion),一個代理類(FeederProxy),代理類持有委託類的例項,代為執行具體類例項方法。 上面說到,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。這裡的間接性就是指客戶端不直接呼叫實際物件的方法,客戶端依賴公共介面並使用代理類。 那麼我們在代理過程中就可以加上一些其他用途。 就這個例子來說在 eatFood方法呼叫中,代理類在呼叫具體實現類之前新增System.out.println("proxy Lion exec eatFood ");語句 就是新增間接性帶來的收益。代理類存在的意義是為了增加一些公共的邏輯程式碼。

動態代理類(基於介面實現)

靜態代理是代理類在程式碼執行前已經建立好,並生成class檔案;動態代理類 是代理類在程式執行時建立的代理模式。

動態代理類的代理類並不是在Java程式碼中定義的,而是在執行時根據我們在Java程式碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。 想想你有100個靜態代理類,現在有一個需求,每個代理類都需要新增一個處理邏輯,你需要開啟100個代理類在每個代理方法裡面新增處理邏輯嗎? 有或者代理類有5個方法,每個方法都需要新增一個處理邏輯, 你需要在每個方法都手動新增處理邏輯嗎? 想想就挺無趣的。動態代理類幫你一鍵搞定。

動態代理類涉及角色

  • 委託類和代理類實現的公共介面(Person.java)

  • 實現公共介面的具體委託類(SoftwareEngineer.java)

  • InvocationHandler介面被Proxy類回撥處理,一般實現 InvocationHandler 介面的類具有委託類引用,介面方法 invoke 中新增公共程式碼並呼叫委託類的介面方法。(PersonInvocationHandler.java)

  • JDK提供生成動態代理類的核心類Proxy ( JDK 提供的Proxy.java)

基於JDK技術 動態代理類技術核心 Proxy類和一個 InvocationHandler 介面

java的java.lang.reflect包下提供了Proxy類和一個 InvocationHandler 介面,這個類Proxy定義了生成JDK動態代理類的方法 getProxyClass(ClassLoader loader,Class<?>... interfaces)生成動態代理類,返回class例項代表一個class檔案。可以儲存該 class 檔案檢視jdk生成的代理類檔案長什麼樣

該生成的動態代理類繼承Proxy類,(重要特性) ,並實現公共介面。

InvocationHandler這個介面 是被動態代理類回撥的介面,我們所有需要增加的針對委託類的統一處理邏輯都增加到invoke 方法裡面在呼叫委託類介面方法之前或之後 結束戰鬥。

案例

公共介面

package org.vincent.proxy.dynamicproxy;/**
 * Created by PengRong on 2018/12/25.
 * 建立Person 介面 用於定義 委託類和代理類之間的約束行為
 */public interface Person{    /**
     *
     * @param name 人名
     * @param dst 工作目的地
     */
    void goWorking(String name, String dst);    /**
     * 獲取名稱
     * @return
     */
    String getName( );    /**
     * 設定名稱
     * @param name
     */
    void  setName(String name);
}

具體實現類,等下被委託,被代理的類 SoftwareEngineer.java

package org.vincent.proxy.dynamicproxy;/**
 * Created by PengRong on 2018/12/25.
 * 動態代理委託類實現, 實現介面 Person。 被動態生成的代理類代理
 */public class SoftwareEngineer implements Person{    public  SoftwareEngineer(){}    public  SoftwareEngineer(String name){        this.name=name;
    }    private  String name;    public String getName() {        return name;
    }    public void setName(String name) {        this.name = name;
    }    @Override
    public void goWorking(String name, String dst) {
        System.out.println("name ="+name+" , 去 "+dst +" 工作");
    }
}

InvocationHandler 介面實現 PersonInvocationHandler.java

package org.vincent.proxy.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Arrays;/**
 * Created by PengRong on 2018/12/25.
 * PersonInvocationHandler 類 實現InvocationHandler介面,這個類中持有一個被代理物件(委託類)的例項target。該類別JDK Proxy類回撥
 * InvocationHandler 介面中有一個invoke方法,當一個代理例項的方法被呼叫時,代理方法將被編碼並分發到 InvocationHandler介面的invoke方法執行。
 */public class PersonInvocationHandler<T> implements InvocationHandler {    /**
     * 被代理物件引用,invoke 方法裡面method 需要使用這個 被代理物件
     */
    T target;    public PersonInvocationHandler(T target) {        this.target = target;
    }    /**
     * 在
     * @param proxy  代表動態生成的 動態代理 物件例項
     * @param method 代表被呼叫委託類的介面方法,和生成的代理類例項呼叫的介面方法是一致的,它對應的Method 例項
     * @param args   代表呼叫介面方法對應的Object引數陣列,如果介面是無參,則為null; 對於原始資料型別返回的他的包裝型別。
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        /**
         * 在轉調具體目標物件之前,可以執行一些功能處理
         */
        System.out.println("被動態代理類回撥執行, 代理類 proxyClass ="+proxy.getClass()+" 方法名: " + method.getName() + "方法. 方法返回型別:"+method.getReturnType()
        +" 介面方法入引數組: "+(args ==null ? "null" : Arrays.toString(args)));        /**
         * 代理過程中插入監測方法,計算該方法耗時
         */
        MonitorUtil.start();
        Thread.sleep(1);        /** 呼叫唄代理物件的真實方法,*/
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());        return result;
    }
}

PersonInvocationHandler invoke 方法中新增的公共程式碼,這裡簡單以統計方法執行時間為邏輯

package org.vincent.proxy.dynamicproxy;/**
 * Created by PengRong on 2018/12/25.
 * 方法用時監控類
 */public class MonitorUtil {    private static ThreadLocal<Long> tl = new ThreadLocal<>();    public static void start() {
        tl.set(System.currentTimeMillis());
    }    /**
     * 結束時列印耗時
     * @param methodName 方法名
     */
    public static void finish(String methodName) {        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法執行耗時" + (finishTime - tl.get()) + "ms");
    }
}

最後的是 怎麼建立代理類

package org.vincent.proxy.jdkdynamicProxy;import org.vincent.proxy.dynamicproxy.Person;import org.vincent.proxy.dynamicproxy.PersonInvocationHandler;import org.vincent.proxy.dynamicproxy.SoftwareEngineer;import sun.misc.ProxyGenerator;import java.io.FileOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Arrays;import java.util.Properties;/**
 * 動態代理類測試
 * Created by PengRong on 2018/12/25.
 */public class JdkDynamicProxyTest {    public static void main(String[] args) throws Exception {        // 開啟儲存JDK動態代理生成的類檔案
        saveGeneratedJdkProxyFiles();        /**
         * 第一種方法: 通過 Proxy.newProxyInstance 方法 獲取代理物件
         */
        System.out.println("-------------------第一種建立代理類方法--------------");        //建立一個例項物件,這個物件是被代理的物件,委託類
        Person person = new SoftwareEngineer("Vincent");        //建立一個與代理類相關聯的InvocationHandler,每一個代理類都有一個關聯的 InvocationHandler,並將代理類引用傳遞進去
        InvocationHandler Handler = new PersonInvocationHandler<>(person);        //建立一個 代理物件 personProxy 來代理 person,建立的代理物件的每個執行方法都會被替換執行Invocation介面中的invoke方法
        Person personProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, Handler);        /** 代理類資訊 */
        System.out.println("package = " + personProxy.getClass().getPackage() + " SimpleName = " + personProxy.getClass().getSimpleName() + " name =" + personProxy.getClass().getName() + " CanonicalName = " +                "" + personProxy.getClass().getCanonicalName() + " 實現的介面 Interfaces = " + Arrays.toString(personProxy.getClass().getInterfaces()) +                " superClass = " + personProxy.getClass().getSuperclass() + " methods =" + Arrays.toString(personProxy.getClass().getMethods()));        // 通過 代理類 執行 委託類的程式碼邏輯
        personProxy.goWorking(personProxy.getName(), "深圳");

        System.out.println("-------------------第二種建立代理類方法--------------");        /**
         *  動態代理物件步驟
         *      1、 建立一個與代理物件相關聯的 InvocationHandler,以及真實的委託類例項
         *      2、Proxy類的getProxyClass靜態方法生成一個動態代理類stuProxyClass,該類繼承Proxy類,實現 Person.java介面;JDK動態代理的特點是代理類必須繼承Proxy類
         *      3、通過代理類 proxyClass 獲得他的帶InvocationHandler 介面的建構函式 ProxyConstructor
         *      4、通過 建構函式例項 ProxyConstructor 例項化一個代理物件,並將  InvocationHandler 介面例項傳遞給代理類。
         */
        // 1、建立 InvocationHandler 例項並設定代理的目標類物件
        Person persontwo = new SoftwareEngineer("Vincent");
        InvocationHandler Handlertwo = new PersonInvocationHandler<>(persontwo);        // 2 建立代理類,是一個位元組碼檔案, 把 proxyClass 儲存起來就能看到 他繼承Proxy 類,實現Person介面
        Class<?> proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[]{Person.class});        /** 代理類資訊 */
        System.out.println("package = " + proxyClass.getPackage() + " SimpleName = " + proxyClass.getSimpleName() + " name =" + proxyClass.getName() + " CanonicalName = " +                "" + proxyClass.getCanonicalName() + " 實現的介面 Interfaces = " + Arrays.toString(proxyClass.getInterfaces()) +                " superClass = " + proxyClass.getSuperclass() + " methods =" + Arrays.toString(proxyClass.getMethods()));        // 3、  通過 proxyClass 獲得 一個帶有InvocationHandler引數的構造器constructor
        Constructor<?> ProxyConstructor = proxyClass.getConstructor(InvocationHandler.class);        // 4、通過構造器建立一個  動態代理類 例項
        Person stuProxy = (Person) ProxyConstructor.newInstance(Handlertwo);        /** 檢測生成的類是否是代理類 */
        System.out.println("stuProxy isProxy "+Proxy.isProxyClass(stuProxy.getClass()));        /** 獲取 代理類關聯的 InvocationHandler 是哪個*/
        InvocationHandler handlerObject = Proxy.getInvocationHandler(stuProxy);
        System.out.println(handlerObject.getClass().getName());
        stuProxy.goWorking(stuProxy.getName(), "廣州");        // 儲存代理類
        saveClass("$PersonProxy0", proxyClass.getInterfaces(), "D:/123/");
    }    /**
     * 生成代理類 class 並保持到檔案中
     *
     * @param className  生成的代理類名稱
     * @param interfaces 代理類需要實現的介面
     * @param pathdir    代理類儲存的目錄路徑,以目錄分隔符結尾
     */
    public static void saveClass(String className, Class<?>[] interfaces, String pathdir) {        /**
         * 第一個引數是 代理類 名 。
         * 第二個引數是 代理類需要實現的介面
         */
        byte[] classFile = ProxyGenerator.generateProxyClass(className, interfaces);        /**
         * 如果目錄不存在就新建所有子目錄
         */
        Path path1 = Paths.get(pathdir);        if (!path1.toFile().exists()){
            path1.toFile().mkdirs();
        }
        String path = pathdir + className + ".class";        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理類class檔案寫入成功");
        } catch (Exception e) {
            System.out.println("寫檔案錯誤");
        }
    }    /**
     * 設定儲存Java動態代理生成的類檔案。
     *
     * @throws Exception
     */
    public static void saveGeneratedJdkProxyFiles() throws Exception {
        Field field = System.class.getDeclaredField("props");
        field.setAccessible(true);
        Properties props = (Properties) field.get(null);
        props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    }

}

解析JDK生成的動態代理類

saveGeneratedJdkProxyFiles方法 打開了儲存jdk生成的動態代理類 以 介面方法 goWorking 為例講解

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package com.sun.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import org.vincent.proxy.dynamicproxy.Person;public final class $Proxy0 extends Proxy implements Person {    private static Method m1;    private static Method m4;    private static Method m3;    private static Method m2;    private static Method m5;    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 void setName(String var1) throws  {        try {            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {            throw var3;
        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);
        }
    }    public final String getName() throws  {        try {            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {            throw var2;
        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);
        }
    }    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);
        }
    }    /**
    * 對介面  goWorking 的呼叫 轉變成   super.h.invoke(this, m5, new Object[]{var1, var2}); 呼叫。
    * h 就是Proxy.java類的一個 InvocationHandler 介面 屬性,
    * 我們在建立 動態代理類例項時候都必須 傳一個 InvocationHandler 介面的例項過去。 這裡就是剛才我們定義的 PersonInvocationHandler 。
    * 回到過後是不是就回到了 PersonInvocationHandler.invoke方法裡面,所以 PersonInvocationHandler 是我們生成的動態代理類的攔截器,攔截所有方法呼叫。
    */
    public final void goWorking(String var1, String var2) throws  {        try {            super.h.invoke(this, m5, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {            throw var4;
        } catch (Throwable var5) {            throw new UndeclaredThrowableException(var5);
        }
    }    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);
        }
    }/**
* 靜態程式碼塊,根據動態代理實現的公共介面類介面方法 獲取到所有介面方法 的 Method 例項*/
    static {        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m4 = Class.forName("org.vincent.proxy.dynamicproxy.Person").getMethod("setName", new Class[]{Class.forName("java.lang.String")});
            m3 = Class.forName("org.vincent.proxy.dynamicproxy.Person").getMethod("getName", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m5 = Class.forName("org.vincent.proxy.dynamicproxy.Person").getMethod("goWorking", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
            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());
        }
    }
}

Jdk為我們的生成了一個叫$Proxy0(這個名字後面的0是編號,有多個代理類會一次遞增)的代理類,這個類檔案時預設不會儲存在檔案,放在記憶體中的,我們在建立代理物件時,就是通過反射獲得這個類的構造方法,然後建立代理物件例項。通過對這個生成的代理類原始碼的檢視,我們很容易能看出,動態代理實現的具體過程。

我們可以對 InvocationHandler 看做一箇中介類,中介類持有一個被代理物件,被Proxy類回撥。在invoke方法中呼叫了被代理物件的相應方法。通過聚合方式持有被代理物件的引用,把客戶端對invoke的呼叫最終都轉為對被代理物件的呼叫。

客戶端程式碼通過代理類引用呼叫介面方法時,通過代理類關聯的中介類物件引用來呼叫中介類物件的invoke方法,從而達到代理執行被代理物件的方法。也就是說,動態代理Proxy類提供了模板實現,對外提供擴充套件點,外部通過實現InvocationHandler介面將被代理類納入JDK代理類Proxy。

一個典型的基於JDK動態代理建立物件過程可分為以下四個步驟:

1、通過實現InvocationHandler介面建立自己的呼叫處理器 IvocationHandler handler = new InvocationHandlerImpl(...);

2、通過為Proxy類指定ClassLoader物件和一組interface代理類需要實現的介面,建立動態代理類類檔案,預設JDK並不會儲存這個檔案到檔案中;可以儲存起來觀察生成的代理類結構Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

3、通過上面新建的代理clazz的反射機制獲取動態代理類的一個建構函式,其建構函式入參型別是呼叫處理器介面(IvocationHandler)型別 Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

4、通過建構函式例項建立代理類例項,此時需將呼叫處理器物件作為引數被傳入 Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler)); 為了簡化物件建立過程,Proxy類中的newInstance工具方法封裝了2~4,只需兩步即可完成代理物件的建立。

JDK動態代理特點總結

  • 生成的代理類:$Proxy0 extends Proxy implements Person,我們看到代理類繼承了Proxy類,Java的繼承機制決定了JDK動態代理類們無法實現對 類 的動態代理。所以也就決定了java動態代理只能對介面進行代理,

  • 每個生成的動態代理例項都會關聯一個呼叫處理器物件,可以通過 Proxy 提供的靜態方法 getInvocationHandler 去獲得代理類例項的呼叫處理器物件。在代理類例項上呼叫其代理的介面中所宣告的方法時,這些方法最終都會由呼叫處理器的 invoke 方法執行

  • 代理類的根類 java.lang.Object 中有三個方法也同樣會被分派到呼叫處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString,可能的原因有:一是因為這些方法為 public 且非 final 型別,能夠被代理類覆蓋; 二是因為這些方法往往呈現出一個類的某種特徵屬性,具有一定的區分度,所以為了保證代理類與委託類對外的一致性,這三個方法也應該被呼叫處理器分派到委託類執行。

JDK動態代理不足

JDK動態代理的代理類位元組碼在建立時,需要實現業務實現類所實現的介面作為引數。如果業務實現類是沒有實現介面而是直接定義業務方法的話,就無法使用JDK動態代理了。(JDK動態代理重要特點是代理介面) 並且,如果業務實現類中新增了介面中沒有的方法,這些方法是無法被代理的(因為無法被呼叫)。

動態代理只能對介面產生代理,不能對類產生代理

基於CGlib 技術動態代理代理類實現 (基於繼承)

Cglib是針對類來實現代理的,他的原理是對代理的目標類生成一個子類,並覆蓋其中方法實現增強,因為底層是基於建立被代理類的一個子類,所以它避免了JDK動態代理類的缺陷。

但因為採用的是繼承,所以不能對final修飾的類進行代理。final修飾的類不可繼承。

匯入maven 依賴

cglib 是基於asm 位元組修改技術。匯入 cglib 會間接匯入 asm, ant, ant-launcher 三個jar 包。

<!-- cglib 動態代理依賴 begin -->
  <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version></dependency><!-- cglib 動態代理依賴 stop -->

業務類實現

cglib是針對類來實現代理的,原理是對指定的業務類生成他的一個子類,並覆蓋其中的業務方法來實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。

package org.vincent.proxy.cglibproxy;/**
 * @Package: org.vincent.proxy.cglibproxy <br/>
 * @Description: Cglib 代理模式中 被代理的委託類 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26-17:55 <br/>
 */public class Dog {    public String  call() {
        System.out.println("wang wang wang");        return "Dog ..";
    }
}

方法攔截器 實現 MethodInterceptor 介面

package org.vincent.proxy.cglibproxy;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/**
 * @Package: org.vincent.proxy.cglibproxy <br/>
 * @Description: Cglib 方法攔截器,不用依賴被代理業務類的引用。  <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26-17:56 <br/>
 */public class CglibMethodInterceptor implements MethodInterceptor {    /**
     * 用於生成 Cglib 動態代理類工具方法
     * @param target 代表需要 被代理的 委託類的 Class 物件
     * @return
     */
    public Object CglibProxyGeneratory(Class target) {        /** 建立cglib 代理類 start */
        // 建立加強器,用來建立動態代理類
        Enhancer enhancer = new Enhancer();        // 為代理類指定需要代理的類,也即是父類
        enhancer.setSuperclass(target);        // 設定方法攔截器回撥引用,對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept() 方法進行攔截
        enhancer.setCallback(this);        // 獲取動態代理類物件並返回
        return enhancer.create();        /** 建立cglib 代理類 end */
    }    /**
     * 功能主要是在呼叫業務類方法之前 之後新增統計時間的方法邏輯.
     * intercept 因為  具有 MethodProxy proxy 引數的原因 不再需要代理類的引用物件了,直接通過proxy 物件訪問被代理物件的方法(這種方式更快)。
     * 當然 也可以通過反射機制,通過 method 引用例項    Object result = method.invoke(target, args); 形式反射呼叫被代理類方法,
     * target 例項代表被代理類物件引用, 初始化 CglibMethodInterceptor 時候被賦值 。但是Cglib不推薦使用這種方式
     * @param obj    代表Cglib 生成的動態代理類 物件本身
     * @param method 代理類中被攔截的介面方法 Method 例項
     * @param args   介面方法引數
     * @param proxy  用於呼叫父類真正的業務類方法。可以直接呼叫被代理類介面方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before");
        MonitorUtil.start();
        Object result = proxy.invokeSuper(obj, args);        //Object result = method.invoke(target, args);
        System.out.println("after");
        MonitorUtil.finish(method.getName());        return result;
    }
}

一個切面,用於在方法攔截器中intercept 方法中呼叫真正業務方法之前 之後處理邏輯

package org.vincent.proxy.cglibproxy;/**
 * Created by PengRong on 2018/12/25.
 * 方法用時監控類,作為一個切面 ,具有兩個方法
 */public class MonitorUtil {    private static ThreadLocal<Long> tl = new ThreadLocal<>();    public static void start() {
        tl.set(System.currentTimeMillis());
    }    /**
     * 結束時列印耗時
     * @param methodName 方法名
     */
    public static void finish(String methodName) {        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法執行耗時" + (finishTime - tl.get()) + "ms");
    }
}

Cglib測試類

package org.vincent.proxy.cglibproxy;import net.sf.cglib.core.DebuggingClassWriter;import net.sf.cglib.proxy.Enhancer;import org.junit.Test;import java.lang.reflect.Field;import java.util.Properties;/**
 * @Package: org.vincent.proxy.cglibproxy <br/>
 * @Description: TODO <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26-18:05 <br/>
 */public class CglibTest {    @Test
    public void testCglib() throws Exception {

        System.out.println(System.getProperty("user.dir"));        /** 開啟 儲存cglib生成的動態代理類類檔案*/
        saveGeneratedCGlibProxyFiles(System.getProperty("user.dir"));        /** 第一種方法: 建立cglib 代理類 start */
        // 建立加強器,用來建立動態代理類
        Enhancer enhancer = new Enhancer();        // 為代理類指定需要代理的類,也即是父類
        enhancer.setSuperclass(Dog.class);        // new 一個新的方法攔截器
        CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor();        // 設定方法攔截器回撥引用,對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept() 方法進行攔截
        enhancer.setCallback(cglibMethodInterceptor);        // 獲取動態代理類物件並返回
        Dog dog = (Dog) enhancer.create();        /** 建立cglib 代理類 end */
        System.out.println(dog.call());        // 對於上面這幾步,可以新增一個工具方法 放置在 CglibMethodInterceptor 裡面;也就有了第二種方法
        // new 一個新的方法攔截器,該攔截器還順帶一個用於建立代理類的工具方法。看起來簡單很多
        cglibMethodInterceptor = new CglibMethodInterceptor();
        dog = (Dog) cglibMethodInterceptor.CglibProxyGeneratory(Dog.class);
        System.out.println(dog.call());

    }    /**
     * 設定儲存Cglib代理生成的類檔案。
     *
     * @throws Exception
     */
    public void saveGeneratedCGlibProxyFiles(String dir) throws Exception {
        Field field = System.class.getDeclaredField("props");
        field.setAccessible(true);
        Properties props = (Properties) field.get(null);
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, dir);//dir為儲存檔案路徑
        props.put("net.sf.cglib.core.DebuggingClassWriter.traceEnabled", "true");
    }
}

Cglib 總結

  • CGlib可以傳入介面也可以傳入普通的類,介面使用實現的方式,普通類使用會使用繼承的方式生成代理類.

  • 由於是繼承方式,如果是 static方法,private方法,final方法等描述的方法是不能被代理的

  • 做了方法訪問優化,使用建立方法索引的方式避免了傳統JDK動態代理需要通過Method方法反射呼叫.

  • 提供callback 和filter設計,可以靈活地給不同的方法繫結不同的callback。編碼更方便靈活。

  • CGLIB會預設代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone。

靜態代理 基於JDK動態代理 基於Cglib 動態代理

靜態代理是通過在程式碼中顯式編碼定義一個業務實現類的代理類,在代理類中對同名的業務方法進行包裝,使用者通過代理類呼叫被包裝過的業務方法;

JDK動態代理是通過介面中的方法名,在動態生成的代理類中呼叫業務實現類的同名方法;

CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;

靜態代理在編譯時產生class位元組碼檔案,可以直接使用,效率高。動態代理必須實現InvocationHandler介面,通過invoke呼叫被委託類介面方法是通過反射方式,比較消耗系統性能,但可以減少代理類的數量,使用更靈活。 cglib代理無需實現介面,通過生成類位元組碼實現代理,比反射稍快,不存在效能問題,但cglib會繼承目標物件,需要重寫方法,所以目標物件不能為final類。

AOP 實現案例

AOP的原始碼中用到了兩種動態代理來實現攔截切入功能:jdk動態代理和cglib動態代理。兩種方法同時存在,各有優劣。 jdk動態代理是由java內部的反射機制來實現的,cglib動態代理底層則是藉助asm來實現的。 總的來說,反射機制在生成類的過程中比較高效,執行時候通過反射呼叫委託類介面方法比較慢;而asm在生成類之後的相關代理類執行過程中比較高效(可以通過將asm生成的類進行快取,這樣解決asm生成類過程低效問題)。 還有一點必須注意:jdk動態代理的應用前提,必須是委託類基於統一的介面。如果沒有上述前提,jdk動態代理不能應用。 由此可以看出,jdk動態代理有一定的侷限性,cglib這種第三方類庫實現的動態代理應用更加廣泛,且在效率上更有優勢。

實現AOP關鍵特點是定義好兩個角色 切點 和 切面 。 代理模式中被代理類 委託類處於切點角色,需要新增的其他比如 校驗邏輯,事務,審計邏輯 屬於非功能實現邏輯通過 切面類定義的方法插入進去。

JDK動態代理 aop 實現方式

定義切面介面,完成將通用公共方法注入到被代理類介面呼叫處理中

package org.vincent.aop.dynamicproxy;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: 定義切面介面,切面介面定義了兩個切面方法,分別在切點介面方法執行前和執行後執行 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26 <br/>
 */public interface IAspect {    /**
     * 在切點介面方法執行之前執行
     * @param args 切點引數列表
     * @return
     */
    boolean startTransaction(Object... args);    /**
     * 在切點介面方法執行之後執行
     */
    void endTrasaction();
}

定義切面實現類

package org.vincent.aop.dynamicproxy;import java.util.Objects;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: 改類作為AOP 模型中切面角色類, 實現切面介面,切面介面定義了兩個切面方法,分別在切點介面方法執行前和執行後執行 。 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26 <br/>
 */public class CustomAspect implements IAspect {    /**
     * 對引數 做判空處理
     * @param args 切點引數列表
     * @return
     */
    @Override
    public boolean startTransaction(Object... args) {
        Objects.nonNull(args);        boolean result = true;        for (Object temp :args) {            if (Objects.isNull(temp)){
                 result =false;                 break;
            }
        }        return result;
    }    public void endTrasaction() {
        System.out.println("I get datasource here and end transaction");
    }
}

定義切點角色介面 因為是基於JDK實現的Aop ,所以委託類需要基於介面實現。

package org.vincent.aop.dynamicproxy;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: AOP基於動態代理 實現  <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26 <br/>
 */public interface IUserService {    void saveUser(String username, String password) throws Exception;
}

委託類實現

package org.vincent.aop.dynamicproxy;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: UserService介面實現類UserServiceImpl 該類 作為AOP中切點角色,切面定義的方法插入到切點的介面方法 執行前和執行後執行。 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26 <br/>
 */public class UserServiceImpl implements IUserService{    @Override
    public void saveUser(String username, String password) throws Exception {
        System.out.println("save user[username=" + username + ",password=" + password + "]");
    }
}

JDK動態代理生成器工具類

可以看到 generatorJDKProxy 方法入參只有兩個引數 一個切點介面引用,一個切面介面引用;在InvocationHandler 內部類中可以完整看到切面類方法是怎麼影響切點程式碼執行邏輯的。

package org.vincent.aop.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: JDK動態代理類生成器 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26-16:48 <br/>
 */public class JDKDynamicProxyGenerator {    /**
     * @param targetPoint 需要被代理的委託類物件
     * @param aspect 切面物件,該物件方法將在切點方法之前或之後執行
     * @return
     */
    public static Object generatorJDKProxy(IUserService targetPoint, final IAspect aspect) {        return Proxy.newProxyInstance(                /**
                 *   委託類使用的類載入器
                 */
                targetPoint.getClass().getClassLoader(),                /**
                 * 委託類實現的介面
                 */
                targetPoint.getClass().getInterfaces(),                /**
                 * 生成的動態代理類關聯的 執行處理器,代理我們的業務邏輯被生成的動態代理類回撥
                 * 具體邏輯程式碼執行,返回值為方法執行結果, 在aop模型中,委託類的介面方法稱為切點。
                 */
                new InvocationHandler() {                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        // 執行切面方法,對入參進行校驗
                       boolean prepareAction = aspect.startTransaction(args);                       if (prepareAction){                           // 具體邏輯程式碼執行,返回值為方法執行結果
                           Object result = method.invoke(targetPoint, args);
                           aspect.endTrasaction();                           return result;
                       }else {                           throw  new RuntimeException("args: "+ Arrays.toString(args)+"不能為null ");
                       }
                    }
                });
    }
}

測試類

package org.vincent.aop;import org.junit.Test;import org.vincent.aop.dynamicproxy.CustomAspect;import org.vincent.aop.dynamicproxy.IUserService;import org.vincent.aop.dynamicproxy.JDKDynamicProxyGenerator;import org.vincent.aop.dynamicproxy.UserServiceImpl;/**
 * @Package: org.vincent <br/>
 * @Description: 基於動態代理類AOP測試案例 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26-16:56 <br/>
 */public class testAopJDKProxy {    @Test
    public void testJDKProxy() throws Exception {
        System.out.println("無代理前 呼叫方法 userService.saveUser 輸出......");
        IUserService userService = new UserServiceImpl();
        userService.saveUser("zby", "1234567890");

        System.out.println("有代理後AOP 是怎麼樣的? Proxy......");
        IUserService proxyUserService = (IUserService) JDKDynamicProxyGenerator.generatorJDKProxy(userService, new CustomAspect());
        proxyUserService.saveUser("zby", "1234567890");        /** 製造異常,兩個入參都是null   */
        proxyUserService.saveUser(null, null);
    }
}

Cglib aop 實現方式

定義切面介面

package org.vincent.aop.cglib;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: 定義切面介面,切面介面定義了兩個切面方法,分別在切點介面方法執行前和執行後執行 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26 <br/>
 */public interface IAspect {    /**
     * 在切點介面方法執行之前執行
     */
    void startTransaction();    /**
     * 在切點介面方法執行之後執行
     */
    void endTrasaction();
}

切面實現

package org.vincent.aop.cglib;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: 改類作為AOP 模型中切面角色類, 實現切面介面,切面介面定義了兩個切面方法,分別在切點介面方法執行前和執行後執行 。 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26 <br/>
 */public class CustomAspect implements IAspect {    @Override
    public void startTransaction() {
        System.out.println("cglib. I get datasource here and start transaction");
    }    public void endTrasaction() {
        System.out.println("cglib I get datasource here and end transaction");
    }
}

Cglib 是基於類實現的動態代理即業務類只需要實現類即可,不用強制必須實現某個介面為了突出這個優點這裡沒有實現介面

package org.vincent.aop.cglib;/**
 * @Package: org.vincent.aop.dynamicproxy <br/>
 * @Description: 業務實現類UserServiceImpl 該類 作為AOP中切點角色,切面定義的方法插入到切點的介面方法 執行前和執行後執行。 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26 <br/>
 */public class UserServiceImpl {    public void saveUser(String username, String password) {
        System.out.println("cglib save user[username=" + username + ",password=" + password + "]");
    }
}

Cglib 動態代理生成器工具類

package org.vincent.aop.cglib;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/**
 * @Package: org.vincent.aop.cglib <br/>
 * @Description: 基於Cglib代理類生成器工具類 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26-17:04 <br/>
 */public class CglibProxyGenerator {    /**
     * @param target 需要被代理的委託類物件,Cglib需要繼承該類生成子類
     * @param aspect 切面物件,改物件方法將在切點方法之前或之後執行
     * @return
     */
    public static  Object generatorCglibProxy(final Object target, final IAspect aspect){        //3.1 new Enhancer
        Enhancer enhancer = new Enhancer();        //3.2 設定需要代理的父類
        enhancer.setSuperclass(target.getClass());        //3.3 設定回撥
        enhancer.setCallback(new MethodInterceptor() {            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
                    throws Throwable {                // 執行切面方法
                aspect.startTransaction();                // 具體邏輯程式碼執行,返回值為方法執行結果
                Object result = methodProxy.invokeSuper(proxy, args);                // 執行切面方法
                aspect.endTrasaction();                // 返回方法執行結果
                return result;
            }
        });        // 3.4 建立代理物件
        return enhancer.create();
    }

}

測試類

package org.vincent.aop;import org.junit.Test;import org.vincent.aop.cglib.CglibProxyGenerator;import org.vincent.aop.cglib.CustomAspect;import org.vincent.aop.cglib.UserServiceImpl;/**
 * @Package: org.vincent <br/>
 * @Description: 基於動態代理類AOP測試案例 <br/>
 * @author: lenovo <br/>
 * @Company: PLCC <br/>
 * @Copyright: Copyright (c) 2019 <br/>
 * @Version: 1.0 <br/>
 * @Modified By: <br/>
 * @Created by lenovo on 2018/12/26-16:56 <br/>
 */public class testAopCglibKProxy {    @Test
    public void testCglibProxy() {
        System.out.println("before Proxy......");
        UserServiceImpl userService = new UserServiceImpl();
        userService.saveUser("zby", "1234567890");
        System.out.println("引入Cglib  Proxy代理庫 後......");
        UserServiceImpl proxyUserService = (UserServiceImpl) CglibProxyGenerator.generatorCglibProxy(userService, new CustomAspect());
        proxyUserService.saveUser("zby", "1234567890");
    }
}

AspectJ 實現 AOP 效果

AOP 實現的關鍵就在於 AOP 框架自動建立的 AOP 代理,AOP 代理則可分為靜態代理和動態代理兩大類:

  • 靜態代理是指使用 AOP 框架提供的命令進行編譯,從而在編譯階段通過AOP框架指令生成 AOP 代理類,因此也稱為編譯時增強;還有一種靜態代理是編寫程式碼實現不用工具;這種方式一般是代理模式會使用。

  • 動態代理則在執行時藉助於 JDK 動態代理、CGLIB 等在記憶體中“臨時”生成 AOP 動態代理類,因此也被稱為執行時增強。

基於 AspectJ 的編譯時增強進行 AOP POM 依賴

原生 AspectJ 不依賴Spring案例, 基於 AspectJ 的編譯時增強進行 AOP 它是在編譯期修改位元組碼,增強功能;並不會生成新的代理類位元組碼。

<!-- AspectJ begin--><dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version></dependency><dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version></dependency><!-- AspectJ stop-->

動態代理 使用場景

  • 日誌集中列印

  • 事務

  • 許可權管理

  • AOP