1. 程式人生 > >“萬惡之源”的動態代理

“萬惡之源”的動態代理

前言

最近準備整理一下框架學習的只是,發現不管是RPC框架,還是Spring的框架,Mybatis的框架都有很多地方地方用到了動態代理的方式,例如我們強大的Spring AOP、Mybatis中的介面方法查詢都是用到了JDK動態代理,為了後期鞏固知識的方便,我希望自己能從基礎入手,真正理解框架。也要求自己以這樣的方式來記錄學習/複習過程。

一、代理模式

1.1什麼是代理模式?

代理模式的定義:代理模式是指給某一個物件提供一個代理物件,並由代理物件來控制對原物件(被代理)的引用。引戰的說法就是,婚介所來介紹物件?

1.3代理模式分類

如果按照代理建立的時期來分類的話,可以分成靜態代理動態代理

  • 靜態代理:由程式設計師建立或者特定的工具自動生成的原始碼,程式執行之前,代理類的位元組碼檔案已經建立好了。
  • 動態代理:通過反射機制動態建立,在程式執行時才建立。

二、靜態代理

實現靜態代理有四個步驟:

  • 定義業務介面
  • 被代理的類來實現業務介面
  • 定義代理類並實現業務介面
  • 客戶端呼叫

下面我們將按照這個步驟來實現一個靜態代理,仿照AOP,我們來實現一個記錄資料庫插入資料的前後日誌需求。

2.1定義業務介面

IService.java

package com.proxy.stat;
/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.stat
 * author       : wujw
 * date         : 2018/9/12 21:47
 * version      : 1.0.0
 * description  : 業務介面
 */
public interface IService {
    /**
     * 將資料庫插入一條資料
     * @param data data
     */
    void insert(String data);
}

2.2被代理類實現業務介面

ServiceImpl.java

package com.proxy.stat;

/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.stat
 * author       : wujw
 * date         : 2018/9/12 21:50
 * version      : 1.0.0
 * description  : 被代理類實現業務介面
 */
public class ServiceImpl implements IService{
    public void insert(String data) {
        System.out.println("insert into database : "+ data);
    }
}

2.3定義代理類並實現業務介面

ServiceProxy.java

package com.proxy.stat;

/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.stat
 * author       : wujw
 * date         : 2018/9/12 21:52
 * version      : 1.0.0
 * description  : 代理類並實現業務介面
 */
public class ServiceProxy implements IService{
    //被代理物件
    private IService iService;
    //構造方式注入
    public ServiceProxy(final IService iService) {
        this.iService = iService;
    }

    public void insert(String data) {
        System.out.println("ready to insert into database");
        iService.insert(data);
        System.out.println("insert into database success");
    }
}

2.4客戶端呼叫測試

package com.proxy.stat;

/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.stat
 * author       : wujw
 * date         : 2018/9/12 21:59
 * version      : 1.0.0
 * description  : 客戶端呼叫
 */
public class StaticProxyGo {
    public static void main(String[] args) {
        
        IService iService = new ServiceImpl();
        //將被代理物件注入到代理類中
        ServiceProxy proxy = new ServiceProxy(iService);

        proxy.insert("Apple凌晨會發布什麼產品呢?");
    }
}
結果::::::::::::::::::::::::
ready to insert into database
insert into database : Apple凌晨會發布什麼產品呢?
insert into database success

Process finished with exit code 0

這樣我們就實現了一個靜態代理。但是靜態代理中每出現一個服務都需要為其去建立一個代理類,工作量太大了。

三、動態代理(JDK)

使用JDK動態代理步驟

  • 建立被代理的介面和類
  • 建立InvocationHandler介面的實現類。在invoke方法中實現代理邏輯
  • 通過Proxy的靜態方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)建立一個代理物件。
  • 客戶端呼叫

3.1建立被代理的介面和類

我們依舊使用上面提到的靜態代理中的介面和類。

Iservice.javaServiceImpl

3.2建立InvcationHandler介面的實現類

package com.proxy.dynamic;

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

/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.dynamic
 * author       : wujw
 * date         : 2018/9/12 22:19
 * version      : 1.0.0
 * description  : TODO
 */
public class MyInvocationHandler implements InvocationHandler {
    //被代理的物件,Object型別
    private Object object;

    public MyInvocationHandler(Object object){
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("ready to insert into database");
        
        Object returnValue = method.invoke(object,args);
        
        System.out.println("insert into database success");
        
        return returnValue;
    }
}

3.4客戶端呼叫

package com.proxy.dynamic;

import com.proxy.IService;
import com.proxy.ServiceImpl;

import java.lang.reflect.Proxy;

/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.dynamic
 * author       : wujw
 * date         : 2018/9/12 22:23
 * version      : 1.0.0
 * description  : TODO
 */
public class DynamicProxyGo {
    public static void main(String[] args) {
        IService iService = new ServiceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(iService);
        //第一個引數是指定代理類的類載入器(我們傳入當前測試類的類載入器)
        //第二個引數是代理類需要實現的介面(我們傳入被代理類實現的介面,這樣生成的代理類和被代理類就實現了相同的介面) 
        //第三個引數是invocation handler,用來處理方法的呼叫。這裡傳入我們自己實現的handler 
        IService proxyObject = (IService) Proxy.newProxyInstance(DynamicProxyGo.class.getClassLoader(),iService.getClass().getInterfaces(),handler);
        proxyObject.insert("Apple 凌晨會發布什麼產品呢?");
    }
}
結果::::::::::::::::::::::::::::::::
ready to insert into database
insert into database : Apple 凌晨會發布什麼產品呢?
insert into database success

Process finished with exit code 0

看到結果和靜態代理一致,說明我們的實現是成功的。

JDK來實現動態代理需要實現類通過介面業務定義方法(可看需要的引數),那對於沒有介面的類,那我們如果實現動態代理呢?這時候就需要CGLIB了。CGLIB採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLIB動態代理均是實現Spring AOP的基礎。

四、動態代理(CGLIB)

4.1建立CGLIB代理類

package com.proxy.dynamic;

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

import java.lang.reflect.Method;

/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.dynamic
 * author       : wujw
 * date         : 2018/9/12 22:35
 * version      : 1.0.0
 * description  : TODO
 */
public class CGBILProxy implements MethodInterceptor {
    //被代理物件
    private Object object;
    public Object getInstance(final Object object){
        this.object = object;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.object.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
    
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("ready to insert into database");

        Object returnValue = methodProxy.invoke(object,objects);

        System.out.println("insert into database success");

        return returnValue;
    }
}

4.2客戶端呼叫

package com.proxy.dynamic;

import com.proxy.ServiceImpl;

/**
 * created with IntelliJ IDEA.
 * packageName  : com.proxy.dynamic
 * author       : wujw
 * date         : 2018/9/12 22:46
 * version      : 1.0.0
 * description  : TODO
 */
public class CGlibProxyGo {
    public static void main(String[] args) {
        //這次我們不使用介面
        ServiceImpl iService = new ServiceImpl();

        CGBILProxy cgbilProxy = new CGBILProxy();
        ServiceImpl service = (ServiceImpl) cgbilProxy.getInstance(iService);

        service.insert("Apple 凌晨會發布什麼產品呢?");
    }
}
結果::::::::::::::::::::::::::::::::
ready to insert into database
insert into database : Apple 凌晨會發布什麼產品呢?
insert into database success

自此,我們已經實現了三類代理方式,接下來我們將繼續深究代理模式的內部程式碼及其常用的框架中是實現案例。

我們將在下一篇幅中記錄討論,這裡感謝前輩們的案例,給了我很多的參考案例