1. 程式人生 > 其它 >靜態代理與動態代理

靜態代理與動態代理

技術標籤:java

靜態代理

靜態代理就是我們自己手寫代理類; aspectJ靜態代理(編譯期生成代理類)
靜態代理可以實現在不修改目標物件程式碼的前提下,對目標物件的功能進行擴充套件;
靜態代理:(目標介面、目標介面實現、代理類)

優點:可以實現不對目標物件進行修改的前提下,對目標物件進行功能的擴充套件和增強,也就是擴充套件原功能,不汙染原始碼。
缺點:因為代理物件,需要實現與目標物件一樣的介面,如果目標介面類繁多,也會導致代理類繁多,另外一旦目標介面增加新方法,則代理類也需要維護;

實現方式

/**
 * 目標介面
 *
 */
public interface TargetInterface
{ public void sayHello(String name); public void sayThanks(String name); }
/**
 * 目標介面的實現
 *
 */
public class TargetInterfaceImpl implements TargetInterface {

    @Override
    public void sayHello(String name) {
        System.out.println("sayHello, " + name);
    }

    @Override
public void sayThanks(String name) { System.out.println("sayThanks, " + name); } }
/**
 * 目標介面的代理類
 *
 */
public class TargetProxy implements TargetInterface {

    //持有目標介面的引用
    private TargetInterface targetInterface;

    public TargetProxy(TargetInterface targetInterface)
{ this.targetInterface = targetInterface; } @Override public void sayHello(String name) { System.out.println("start.............."); //中間調目標介面的真正的實現 targetInterface.sayHello(name); System.out.println("end.............."); } @Override public void sayThanks(String name) { System.out.println("start.............."); //中間調目標介面的真正的實現 targetInterface.sayThanks(name); System.out.println("end.............."); } }
public class Test2 {

    public static void main(String[] args) {

        TargetProxy targetProxy = new TargetProxy(new TargetInterfaceImpl());
        targetProxy.sayHello("張無忌");

        System.out.println("---------------------------");

        targetProxy.sayThanks("張三丰");
    }
}

結果輸出
在這裡插入圖片描述

動態代理

JDK動態代理
動態代理,是指在執行期動態的為指定的類生成其代理類;
JDK動態代理會在程式執行時生成一個$Proxy0.Class,該class類在程式碼中看不到,在磁碟中也沒有儲存,是程式執行時候在jvm記憶體中生成的,所以我們感覺動態代理很抽象,要想儲存該class檔案,我們可以在程式碼中設定:
System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
這樣就會在執行時候將該class檔案儲存到我們磁碟上;
動態代理在執行時將介面中宣告的所有方法都轉移到一個集中的InvocationHandler.invoke()方法進行處理,這樣在介面方法數量比較多的時候,我們可以進行靈活處理,而靜態代理需要在每一個方法進行中轉;
動態代理是AOP思想的底層實現,當然JDK的動態代理,目標類必須實現某個介面,如果某個類沒有實現介面則不能生成代理物件;

實現方式

/**
 * 目標介面
 *
 */
public interface TargetInterface {

    public void sayHi();

    public void work();
}
/**
 * 目標介面實現
 *
 */
public class TargetInterfaceImpl implements TargetInterface {

    public void sayHi() {
        System.out.println("Hi, dynamic proxy. sayHi.");
    }

    public void work() {
        System.out.println("Hi, dynamic proxy.work");
    }
}
/**
 * 實現jdk提供的InvocationHandler介面
 *
 * 實現該介面是為了實現jdk的動態代理
 *
 * 此類不是真正的代理類,真正的代理的類在jvm記憶體中,我們看不見摸不著的,這個真正的代理類名字一般是以$Proxy.
 *
 */
public class TargetProxy implements InvocationHandler {

    //持有目標介面的引用,動態代理為了適配各種目標型別,把引用使用Object
    private Object target;

    /**
     * 使用構造方法對目標介面的引用實現初始化
     *
     * @param target
     */
    public TargetProxy(Object target) {
        this.target = target;
    }

    /**
     * 獲取真正的代理類
     *
     * @param interfaces
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class interfaces) {
        //1、jvm記憶體中生成一個class類;
        //2、根據該class類反射建立一個代理物件 [email protected]
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {interfaces},
                this);
    }

    /**
     * 覆蓋InvocationHandler介面的方法
     * 該方法會對目標介面的方法進行攔截
     *
     * @param proxy 這個就是我們那個代理類,就是jdk生成的那個叫$Proxy.代理類
     * @param method 就是目標介面的方法,比如 sayhi(), work()的反射物件Method;
     * @param args 就是目標介面的方法,比如 sayhi(), work()的引數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("前置增強(通知)......");

        //中間是呼叫目標介面的方法
        Object result = method.invoke(target, args);

        System.out.println("後置增強(通知)......");

        return result;
    }
}

Cglib動態代理
官方Github:https://github.com/cglib
CGLib (Code Generation Library) 是一個強大、高效能、高質量的程式碼生成庫,它可以在執行時擴充套件JAVA類並實現介面;
位元組碼生成庫是生成和轉換Java位元組碼的高階API,它被AOP、測試、資料訪問框架用來生成動態代理物件和攔截欄位訪問;
CGLib 比 Java 的 java.lang.reflect.Proxy 類更強大的地方在於它不僅可以接管介面類的方法,還可以接管普通類的方法,為JDK的動態代理提供了很好的補充,通常可以使用Java的動態代理建立代理,但當要代理的類沒有實現介面時,那麼CGLIB是一個更好的選擇;
在一些開源框架中都採用了cglib:
Hibernate
Spring
Guice
CGLib 的底層是Java位元組碼操作框架 —— ASM https://asm.ow2.io
CGLIB類庫結構
net.sf.cglib.core: 底層位元組碼處理類,他們大部分與ASM有關;
net.sf.cglib.transform: 編譯期或執行期類和類檔案的轉換
net.sf.cglib.proxy: 實現建立代理和方法攔截器的類
net.sf.cglib.reflect: 實現快速反射類
net.sf.cglib.util: 集合排序等工具類
net.sf.cglib.beans: javabean相關的工具類
CGLIB實現動態代理
使用CGLib實現動態代理,完全不受代理類必須實現介面的限制,CGLib底層採用ASM位元組碼生成框架,使用位元組碼技術生成代理類,在JDK8之前CGLib動態比JDK的動態代理(使用Java反射)效率要高。
唯一需要注意的是,如果被代理的類被final修飾,那麼它不可被繼承,即不可被代理,同樣,如果被代理的類中存在final修飾的方法,那麼該方法也不可被代理;
因為CGLib原理是動態生成被代理類的子類;
final類不能被繼承,final方法不能被複寫;
使用CGLIB,需要兩個jar包:
cglib-3.3.0.jar
asm-7.1.jar
cglib-nodep-3.3.0.jar:使用nodep包不需要關聯asm的jar包,jar包內部已經包含了asm的類.
cglib-3.3.0.jar:使用此jar包需要關聯asm的jar包,否則執行時報錯;
CGLib動態代理原理是什麼?
CGLIB原理: 動態生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法。在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯;
CGLIB底層:使用位元組碼處理框架ASM,來轉換位元組碼並生成新的類。但不推薦直接使用ASM編碼開發,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉,編碼比較複雜繁瑣;
CGLIB缺點:對於final類和final方法,無法進行代理;
JDK動態代理與CGLib代理的區別?
原理區別:
JDK動態代理是利用反射機制生成一個實現代理介面的類(這個類看不見摸不著,在jvm記憶體中有這個類),在呼叫具體方法前呼叫InvokeHandler來處理。核心是實現InvocationHandler介面,使用invoke()方法進行面向切面的處理,呼叫相應的攔截和處理;
而cglib動態代理是利用asm開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理,核心是實現MethodInterceptor介面,使用intercept()方法進行面向切面的處理,呼叫相應的攔截和處理;
各自侷限:
1、JDK的動態代理機制只能代理實現了介面的類,而沒有實現介面的類就不能實現JDK的動態代理。
2、CGLib的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對final修飾的類進行代理。
JDK Proxy 的優勢:
最小化依賴關係,減少依賴意味著簡化開發和維護,JDK 本身的支援,可能比 cglib 更加可靠;
可以平滑進行 JDK 版本升級,而位元組碼類庫通常需要進行更新以保證在新版 Java 上能夠使用。
CGLib 的優勢:
從某種角度看,必須要求呼叫者實現介面是有些侵入性的實踐,CGLib 動態代理就沒有這種限制,只操作我們關心的類,而不必為其他相關類增加工作量,一個普通的類也可以實現代理;

實現方式

public interface TargetInterface {

    public String sayHello(String name);

    public String sayThanks(String name);
}

public class TargetInterfaceImpl implements TargetInterface {

    @Override
    public String sayHello(String name) {
        return "Hello, " + name;
    }

    @Override
    public String sayThanks(String name) {
        return "Thanks, " + name;
    }
}
/**
 * TargetProxy類還不是一個真正的代理類,它是代理類的一部分
 *
 */
public class TargetProxy implements MethodInterceptor {

    /**
     * 獲取真正的代理類
     *
     *   //1、jvm記憶體中生成一個class類;
     *   //2、根據該class類反射建立一個代理物件 [email protected]
     *   return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
     *                 new Class<?>[] {interfaces},
     *                 this);
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T getProxy(Class<T> clazz) {
        //位元組碼增強的一個類
        Enhancer enhancer = new Enhancer();

        //設定父類
        enhancer.setSuperclass(clazz);
        //enhancer.setInterfaces(new Class[] {clazz});

        //設定回撥類
        enhancer.setCallback(this);

        //建立代理類
        return (T)enhancer.create();
    }

    /**
     * 既可以 sayHello,也可以攔截 sayThanks
     *
     * @param obj
     * @param 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(method.getName() + "資料快取start..........");

        //呼叫目標方法
        Object result = proxy.invokeSuper(obj, args);

        //就像mybatis一樣, 需要自己實現介面
        //System.out.println("sayHello...................");

        System.out.println(method.getName() + "資料快取end..........");

        return result;
    }
}