設計模式學習---代理模式

代理模式---咱們編程的代碼能夠分爲業務代碼與非業務代碼,非業務代碼可能有日誌記錄,事務管理,權限校驗等,使用到代理模式把業務代碼和非業務代碼區分開來幫助下降耦合且具備良好的擴展性,就是把一些事情不須要業務類執行的操做交給代理類執行java

代理模式主要分爲三種,靜態代理、動態代理、Cglib代理編程

1、靜態代理數組

靜態代理的條件是目標類與代理類必須實現同一個接口而後經過調用相同的函數完成對目標函數的調用 ,業務操做由目標類實現,非業務操做由代理類實現maven

1.接口ide

public interface Test {
    void test();
}

2.目標類函數

public class TestImpl implements Test{

    @Override
    public void test() {
        System.out.println("業務代碼靜態代理測試");
    }
}

3.代理類工具

public class TestProxy implements Test{

    private Test test;

    public TestProxy(Test test) {
        this.test = test;
    }

    @Override
    public void test() {
        System.out.println("業務代碼執行以前執行");
        test.test();
        System.out.println("業務代碼執行以後執行");
    }
}

下面是測試代理的方法,須要經過代理類指向一個目標類測試

public static void main(String[] args) {
    Test test = new TestProxy(new TestImpl());
    test.test();
}

執行以後結果:this

使用靜態代理使不一樣職能的代碼區分降耦合,並且提供了良好的擴展性,可是代理類也必須實現接口,若是某個業務又須要記錄日誌,又須要管理實務,並且A類代理只能代理A類目標,這樣就會多出不少類,而不能經過一個代理類完成對全部目標類的代理spa

2、動態代理

動態代理解決類靜態代理A類代理只能代理A類目標的問題,由於動態代理的代理類不須要實現與目標類相同的接口,而是經過Java JDK提供的API java.lang.reflect.Proxy 實現,雖然代理類不用再與目標類實現同一個接口,可是目標類仍是須要依賴接口實現才能完成代理。主要是經過ClassLoader對象來指定須要被代理的類,經過Interface[]來聲明須要代理的函數再實現InvocationHandler重寫invoke()並使用它進行代理

1.接口

public interface Test {
    String test();
}

2.目標類

public class TestImpl implements Test {
    @Override
    public String test() {
        System.out.println("業務代碼動態代理測試");
        return "動態代理返回值";
    }
}

3.代理類,這裏經過代理工廠獲取代理類

public class ProxyFactory {

    private Object obj;

    public ProxyFactory(Object obj) {
        this.obj = obj;
    }

    public Object getInstance(){
        // 實現InvocationHandlet接口建本身的調理器
        return Proxy.newProxyInstance(
                // ClassLoader指定須要被代理的                obj.getClass().getClassLoader(),
                // Interfaces數組來代理裏面全部函                obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    // 重 invoke() 編寫代理的邏輯
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("業務代碼執行以前執行");
                        Object returnObj = method.invoke(obj, args);
                        System.out.println("業務代碼執行以後執行");
                        return returnObj;
                    }
                });
    }

}

下面是測試代理的方法,須要經過代理類指向一個目標類

public static void main(String[] args) {
    Test test = new TestImpl();
    Test instance = (Test)new ProxyFactory(test).getInstance();
    instance.test();
}

執行結果:

這樣就可使用一個代理類代理多種目標類,不用實現相同接口,可是目標類依然要實現接口

3、Cglib代理( Code Generation Library )

Cglib代理也是動態代理的另一種實現方案,JDK的動態代理有個缺陷,就是目標類必須實現一個接口才能被代理,這也是早期SpringAOP必須實現接口的一個緣由,目前Spring支持JDK動態代理(下面稱爲Java Proxy)也支持Cglib動態代理

Cglib代理須要引入Cglib包,目標類與代理類都不須要再實現接口,首先須要引入maven包,它的主要原理是在內存中動態修改咱們的class字節碼文件,而且代理類不能是final,目標類的函數也不能是fianl或者static修飾

 

1.不須要實現接口的目標類

public class Test {
    public void test(){
        System.out.println("業務代碼Cglib代理測試");
    }
}

2.代理類工廠

public class ProxyFactory implements MethodInterceptor {

    private Object obj;

    public ProxyFactory(Object obj) {
        this.obj = obj;
    }

    //標對建一代理    public Object getInstance(){
        //1.CGlib工具        Enhancer en = new Enhancer();
        //2.置父類(目標類)
        en.setSuperclass(obj.getClass());
        //3.置回調        en.setCallback(this);
        //4.建子(代理)
        return en.create();

    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("業務代碼執行以前執行");
        Object returnObj = method.invoke(obj, objects);
        System.out.println("業務代碼執行以後執行");
        return returnObj;
    }
}

3.測試函數

public static void main(String[] args) {
    Test test = (Test)new ProxyFactory(new Test()).getInstance();
    test.test();
}

執行結果:

SpringAOP其實使用的就是代理模式,它目前同時支持CGlib和java Proxy的動態代理,Spring在之前一些舊版本中不支持CGlib的狀況下,咱們的Service都會實現一個接口,從而方便AOP管理,可是如今一些不必實現接口的類可使用CGlib動態代理,減小代碼量,除非某個函數真的有多種實現方式,這個時候咱們才選擇java Proxy的代理模式

總結

其實不管java Proxy動態代理或者CGlib動態代理都是經過新增咱們的class字節碼動態改變代碼結構實現的,例如CGlib的類不能使用fianl的緣由,是由於CGlib會幫咱們的目標類建立一個子類來進行代理,若是類或者函數是final修飾的則沒法被繼承致使沒法使用CGlib代理,下面簡單寫個例子

1.目標類

public class Test {
    public void test(){
        System.out.println("自編簡單繼承代理");
    }
}

2.代理類

public class TestProxy extends Test{

    @Override
    public void test() {
        System.out.println("業務代碼執行以前執行");
        super.test();
        System.out.println("業務代碼執行以後執行");
    }
}

測試函數

public static void main(String[] args) {
    Test test = new TestProxy();
    test.test();
}

執行結果

若是咱們在目標類的test()加上final來修飾

public final void test(){
    System.out.println("自編簡單繼承代理");
}

public static void main(String[] args) {
    Test test = new TestProxy();
    test.test();
}

上面的測試結果與CGlib代理的結果是同樣的,因此CGlib代理原理就是在咱們代碼編譯的時候動態建立子類,且繼承須要代理的函數從而實現動態代理。例以下面的圖

可是java Proxy雖然也是在編譯的時候動態建立代理類,不過與CGlib不同的是,java Proxy會動態建立一個實現了與目標類相同接口的代理類,例以下面的圖

Java Proxy與CGlib 都是經過新增class字節碼來完成動態代理,可是它們實現新增字節碼的方式是不同的,java Proxy是直接操做字節碼,而CGlib則是經過ASM操做字節碼。固然也存在修改class字節碼來完成代理的解決方案(Aspect,javaagent)

相關文章
相關標籤/搜索