MyBtis(二)—— 動態代理

概念

按照代理的創建時期,代理類可以分爲兩種:

靜態:由程序員創建代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經存在了。

動態:在程序運行時運用反射機制動態創建而成。

靜態代理

先來看看靜態代理的實現吧?

/**
 * @ClassName Star
 * @Description 公共接口
 * @Author lzq
 * @Date 2019/8/1 10:39
 * @Version 1.0
 **/
public interface Star {
    public void sell();   //賣商品
}
/**
 * @ClassName RealStar
 * @Description 真實對象 委託類
 * @Author lzq
 * @Date 2019/8/1 10:39
 * @Version 1.0
 **/
public class RealStar implements Star{
    @Override
    public void sell() {
        System.out.println("賣商品");
    }
}
/**
 * @ClassName StarProxy
 * @Description 代理類
 * @Author lzq
 * @Date 2019/8/1 10:38
 * @Version 1.0
 **/
public class StarProxy implements Star{
    private RealStar realStar = new RealStar();

    @Override
    public void sell() {
        realStar.sell();
    }
}

測試代碼,我們只需要和委託類交互即可,不必管它的底層實現:

/**
 * @ClassName Test
 * @Description 測試
 * @Author lzq
 * @Date 2019/8/1 11:08
 * @Version 1.0
 **/
public class Test {
    public static void main(String[] args) {
        StarProxy proxy = new StarProxy();
        proxy.sell();
    }
}

運行結果:

賣商品

其實,動態代理也是這麼個原理,只不過它的創建對象的方式是通過反射來完成的,

動態代理

在Java中,實現動態代理的技術有很多,比如JDK自帶的、CGLIB、Javassist、ASM等,其中最常用的就是JDK、CGLIB,下面重點解釋這兩種;

JDK動態代理

上面我們用靜態代理的時候,是定義一個公共接口,然後讓委託類和代理類分別去實現這個接口(相當於後面說的,代理對象和真實對象都是掛在在這個公共接口下面的),最後在代理類裏面定義一個委託類的對象,通過底層去調用委託類的相應的方法去實現代理邏輯的,在動態代理裏面也是一樣,也需要一個公共接口,一個已經實現了的委託類,但是不同的是,它的代理類是根據接口、委託類動態生成的;

首先我們定義一個接口HelloWorld:

/**
 * @ClassName HelloWorld
 * @Description 公共接口
 * @Author lzq
 * @Date 2019/8/1 11:26
 * @Version 1.0
 **/
public interface HelloWorld {
    public void sayHelloWorld();
}

定義一個委託類實現這個接口:

/**
 * @ClassName HelloWorldImpl
 * @Description 委託類  真實對象類
 * @Author lzq
 * @Date 2019/8/1 11:27
 * @Version 1.0
 **/
public class HelloWorldImpl implements HelloWorld{
    @Override
    public void sayHelloWorld() {
        System.out.println("Hello World");
    }
}

在JDK動態代理中,要實現代理邏輯必須去實現java.lang.reflect.InvocationHandler接口,它裏面定義了一個invoke方法,並提供接口數組用於掛在代理對象,下面這個類主要實現動態代理綁定和代理邏輯實現:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 1. @ClassName JDKProxyExample
 2. @Description 動態代理邏輯代碼
 3. @Author lzq
 4. @Date 2019/8/1 11:28
 5. @Version 1.0
 **/
public class JDKProxyExample implements InvocationHandler {
    private Object object = null;  //真實對象 委託類對象

    /**
     * 獲取代理對象
     * @param o
     * @return
     */
    public Object bind(Object o) {
        this.object = o;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),o.getClass().getInterfaces(),this);
    }

    /**
     * 代理方法邏輯
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進入代理邏輯前的處理...");
        System.out.println("調用真實對象前的服務...");
        Object obj = method.invoke(object,args);
        System.out.println("調用真實對象之後的服務...");
        return obj;
    }
}

在這個類裏面,我們需要:

1、建立代理對象和真實對象之間的關係

在這個類裏面,我們是提供bind方法實現的,方法裏面首先用類的屬性object保存了真實對象,然後通過如下代碼建立並生成代理對象:

Proxy.newProxyInstance(object.getClass().getClassLoader(),o.getClass()
.getInterfaces(),this);

這三個參數的意思是:

  • 第一個是類加載器,就是要加載這個代理對象用哪個類加載器加載,這裏我們用的是加載真實對象的類的類加載器;
  • 第二個是把生成的代理對象下掛在哪些接口下面,這個寫法就是放在object實現的接口下,HelloWorldImpl對象的接口顯然就是HelloWorld;
  • 第三個是定義實現方法邏輯的代理類,this表示當前對象,它必須實現InvocationHandler接口中的invoke方法,它就是代理邏輯方法的實現方法;

2、實現代理邏輯方法

invoke方法可以實現代理邏輯,它的三個參數含義如下:

  • proxy,代理對象,就是bind方法生成的對象;
  • method,當前調度的方法;
  • args,調度方法的參數;

當我們使用代理對象調度方法之後,它就會進入到invoke方法裏面:

Object obj = method.invoke(object,args); 
//method.invoke(" 要調用的方法的名字所隸屬的對象實體",方法的參數值);

這行代碼相當於調度真實對象的的方法,只不過是通過發射實現而已。

這些各部分的關係就相當於:

  • proxy 相當於商務對象,代理對象的意思;
  • object 相當於軟件工程師對象,真實對象,委託者;
  • bind 這個方法就是建立商務和軟件工程師代理關係的方法;
  • invoke 這就是商務邏輯,它將控制軟件工程師的訪問;

測試代碼如下:

/**
 * @ClassName Test
 * @Description 測試代碼
 * @Author lzq
 * @Date 2019/8/1 11:32
 * @Version 1.0
 **/
public class Test {
    public static void main(String[] args) {
        JDKProxyExample jdk = new JDKProxyExample();
        HelloWorld proxy = (HelloWorld)jdk.bind(new HelloWorldImpl());
        proxy.sayHelloWorld();
    }
}

那麼其實動態代理的好處在哪呢?因爲它的代理對象都是通過反射動態生成的,如果需要新的功能,那麼只需要在公共接口中添加方法,真實對象實現該方法即可,動態代理綁定和代理邏輯根本就不需要動;

測試代碼運行結果:
在這裏插入圖片描述

CGLIB動態代理

JDK動態代理必須提供公共接口才可以使用,在一些不能提供接口的環境中,只能採用其他的第三方技術,比如CGLIB動態代理,它的優勢在於不需要提供公共接口,只要一個非抽象類就能實現動態代理;

CGLIB依賴:

        <!--CGlib動態代理-->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.2</version>
        </dependency>

代碼如下:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @ClassName CglibProxyExample
 * @Description
 * @Author lzq
 * @Date 2019/8/1 12:12
 * @Version 1.0
 **/
public class CglibProxyExample implements MethodInterceptor {
    /**
     * 生成CGLIB代理對象
     * @param cls
     * @return
     */
    public Object getProxy(Class cls) {
        //CGLIB Enhancer 增強類對象
        Enhancer enhancer = new Enhancer();
        //設置增強類型
        enhancer.setSuperclass(cls);
        //帶你一代理邏輯對象爲當前對象,這就要求當前對象實現MethodInterceptor的方法intercept
        enhancer.setCallback(this);
        //生成並返回代理對象
        return enhancer.create();
    }

    /**
     * 代理邏輯方法
     * @param o  代理對象
     * @param method  方法
     * @param objects  方法參數
     * @param methodProxy  方法代理
     * @return  代理邏輯返回
     * @throws Throwable 異常
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("調用真實對象之前的處理邏輯...");
        Object result=  methodProxy.invokeSuper(o,objects);
        System.out.println("調用真實對象之後的處理邏輯");
        return result;
    }
}

這裏用到了CGLIB的加強者類Enhancer,通過設置超類的方法(setSuperclass),然通過setCallback方法設置哪個類爲它的代理類,其中,參數this就意味着當前對象,那就要求this這個對象實現接口MethodInterceptor的方法——intercept,然後返回代理對象;

那麼此時當前類的intercept方法就是代理邏輯方法,其參數含義見代碼註解;

再寫一個真實對象類;

/**
 * @ClassName Test
 * @Description 委託類  真實對象類
 * @Author lzq
 * @Date 2019/8/1 12:28
 * @Version 1.0
 **/
public class Test {
    public void say(String x) {
        System.out.println(x);
    }
}

測試代碼:

   public static void main(String[] args) {
        CglibProxyExample cpe = new CglibProxyExample();
        Test test = (Test)cpe.getProxy(Test.class);
        test.say("你好");
    }

運行結果:
在這裏插入圖片描述

動態代理的實現其實都很相似,它們都是用getProxy方法生成代理對象,制定代理的邏輯類,而代理邏輯類要實現一個接口的一個方法,那麼這個接口定義的方法就是代理邏輯方法,它可以控制真實對象的方法;

JDK動態代理和CGLIB動態代理的區別:

在JDK的動態代理裏面,我們可以看到:
在這裏插入圖片描述
在這裏插入圖片描述
那它生成的代理對象和真實對象是同級的,都是公共接口下的實現類,它們是橫向關係;

再來看看CGLIB的:
在這裏插入圖片描述
它的原理是生成一個代理對象來攔截真實對象的方法,但這個代理對象卻是真實對象的子類,繼承關係,所以在CGLIB裏面,代理對象是真實對象的子類,它們是縱向關係;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章