Spring系列第十五講 Spring中的Java動態代理技術(上)

qwer1030274531發表於2020-11-04

為什麼要用代理

我們先來看一個案例。

有一個介面IService,如下:

package com.javacode2018.lesson001.demo15;public interface IService {
    void m1();
    void m2();
    void m3();}1234567

介面有2個實現類ServiceA和ServiceB,如下:

package com.javacode2018.lesson001.demo15;public class ServiceA implements IService {
    @Override
    public void m1() {
        System.out.println("我是ServiceA中的m1方法!");
    }
    @Override
    public void m2() {
        System.out.println("我是ServiceA中的m2方法!");
    }
    @Override
    public void m3() {
        System.out.println("我是ServiceA中的m3方法!");
    }}package com.javacode2018.lesson001.demo15;public class ServiceB implements IService {
    @Override
    public void m1() {
        System.out.println("我是ServiceB中的m1方法!");
    }
    @Override
    public void m2() {
        System.out.println("我是ServiceB中的m2方法!");
    }
    @Override
    public void m3() {
        System.out.println("我是ServiceB中的m3方法!");
    }}123456789101112131415161718192021222324252627282930313233343536

來個測試用例來呼叫上面類的方法,如下:

package com.javacode2018.lesson001.demo15;import org.junit.Test;public class ProxyTest {
    @Test
    public void m1() {
        IService serviceA = new ServiceA();
        IService serviceB = new ServiceB();
        serviceA.m1();
        serviceA.m2();
        serviceA.m3();
        serviceB.m1();
        serviceB.m2();
        serviceB.m3();
    }}123456789101112131415161718

上面的程式碼很簡單,就不解釋了,我們執行一下m1()方法,輸出:

我是ServiceA中的m1方法!我是ServiceA中的m2方法!我是ServiceA中的m3方法!我是ServiceA中的m1方法!我是ServiceA中的m2方法!我是ServiceA中的m3方法!123456

上面是我們原本的程式,突然領導有個需求:呼叫IService介面中的任何方法的時候,需要記錄方法的耗時。

此時你會怎麼做呢?

IService介面有2個實現類ServiceA和ServiceB,我們可以在這兩個類的所有方法中加上統計耗時的程式碼,如果IService介面有幾十個實現,是不是要修改很多程式碼,所有被修改的方法需重新測試?是不是非常痛苦,不過上面這種修改程式碼的方式倒是可以解決問題,只是增加了很多工作量(編碼 & 測試)。

突然有一天,領導又說,要將這些耗時統計傳送到監控系統用來做監控報警使用。

此時是不是又要去一個修改上面的程式碼?又要去測試?此時的系統是難以維護。

還有假如上面這些類都是第三方以jar包的方式提供給我們的,此時這些類都是class檔案,此時我們無法去修改原始碼。

比較好的方式:可以為IService介面建立一個代理類,透過這個代理類來間接訪問IService介面的實現類,在這個代理類中去做耗時及傳送至監控的程式碼,程式碼如下:

package com.javacode2018.lesson001.demo15;// IService的代理類public class ServiceProxy implements IService {
    //目標物件,被代理的物件
    private IService target;
    public ServiceProxy(IService target) {
        this.target = target;
    }
    @Override
    public void m1() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
    }
    @Override
    public void m2() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
    }
    @Override
    public void m3() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
    }}1234567891011121314151617181920212223242526272829303132333435

ServiceProxy是IService介面的代理類,target為被代理的物件,即實際需要訪問的物件,也實現了IService介面,上面的3個方法中加了統計耗時的程式碼,當我們需要訪問IService的其他實現類的時候,可以透過ServiceProxy來間接的進行訪問,用法如下:

@Testpublic void serviceProxy() {
    IService serviceA = new ServiceProxy(new ServiceA());//@1
    IService serviceB = new ServiceProxy(new ServiceB()); //@2
    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();}123456789101112

上面程式碼重點在於@1和@2,建立的是代理物件ServiceProxy,ServiceProxy構造方法中傳入了被代理訪問的物件,現在我們訪問ServiceA或者ServiceB,都需要經過ServiceProxy,執行輸出:

我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):90100我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):31600我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo15.ServiceA.m1()方法耗時(納秒):25800我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):142100我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):35000我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo15.ServiceB.m1()方法耗時(納秒):32900123456789101112

上面實現中我們沒有去修改ServiceA和ServiceB中的方法,只是給IService介面建立了一個代理類,透過代理類去訪問目標物件,需要新增的一些共有的功能都放在代理中,當領導有其他需求的時候,我們只需修改ServiceProxy的程式碼,方便系統的擴充套件和測試。

假如現在我們需要給系統中所有介面都加上統計耗時的功能,若按照上面的方式,我們需要給每個介面建立一個代理類,此時程式碼量和測試的工作量也是巨大的,那麼我們能不能寫一個通用的代理類,來滿足上面的功能呢?

通用代理的2種實現:

jdk動態代理

cglib代理

jdk動態代理詳解

jdk中為實現代理提供了支援,主要用到2個類: zhengzhou/

java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler12

jdk自帶的代理使用上面有個限制,只能為介面建立代理類,如果需要給具體的類建立代理類,需要用後面要說的cglib

java.lang.reflect.Proxy

這是jdk動態代理中主要的一個類,裡面有一些靜態方法會經常用到,我們來熟悉一下:

getProxyClass方法

為指定的介面建立代理類,返回代理類的Class物件

public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)12

引數說明:

loader:定義代理類的類載入器

interfaces:指定需要實現的介面列表,建立的代理預設會按順序實現interfaces指定的介面

newProxyInstance方法

建立代理類的例項物件

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)123

這個方法先為指定的介面建立代理類,然後會生成代理類的一個例項,最後一個引數比較特殊,是InvocationHandler型別的,這個是個藉口如下:

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;12

上面方法會返回一個代理物件,當呼叫代理物件的任何方法的時候,會就被InvocationHandler介面的invoke方法處理,所以主要程式碼需要解除安裝invoke方法中,稍後會有案例細說。

isProxy方法

判斷指定的類是否是一個代理類

public static boolean isProxyClass(Class<?> cl)1

getInvocationHandler方法

獲取代理物件的InvocationHandler物件

public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException12

上面幾個方法大家熟悉一下,下面我們來看建立代理具體的2種方式。

建立代理:方式一

步驟

1.呼叫Proxy.getProxyClass方法獲取代理類的Class物件2.使用InvocationHandler介面建立代理類的處理器3.透過代理類和InvocationHandler建立代理物件4.上面已經建立好代理物件了,接著我們就可以使用代理物件了1234

案例

先來個介面IService

package com.javacode2018.lesson001.demo16;public interface IService {
    void m1();
    void m2();
    void m3();}1234567

建立IService介面的代理物件

@Testpublic void m1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    // 1. 獲取介面對應的代理類
    Class<IService> proxyClass = (Class<IService>) Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
    // 2. 建立代理類的處理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被呼叫的方法是:" + method.getName());
            return null;
        }
    };
    // 3. 建立代理例項
    IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
    // 4. 呼叫代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();}12345678910111213141516171819

執行輸出 zzdxb.baikezh.com/xinyang/

我是InvocationHandler,被呼叫的方法是:m1
我是InvocationHandler,被呼叫的方法是:m2
我是InvocationHandler,被呼叫的方法是:m3123

建立代理:方式二

建立代理物件有更簡單的方式。

1.使用InvocationHandler介面建立代理類的處理器2.使用Proxy類的靜態方法newProxyInstance直接建立代理物件3.使用代理物件123

案例

@Testpublic void m2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    // 1. 建立代理類的處理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被呼叫的方法是:" + method.getName());
            return null;
        }
    };
    // 2. 建立代理例項
    IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
    // 3. 呼叫代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();}1234567891011121314151617

執行輸出:

我是InvocationHandler,被呼叫的方法是:m1
我是InvocationHandler,被呼叫的方法是:m2
我是InvocationHandler,被呼叫的方法是:m3123

案例:任意介面中的方法耗時統計

下面我們透過jdk動態代理實現一個通用的代理,解決統計所有介面方法耗時的問題。

主要的程式碼在代理處理器InvocationHandler實現上面,如下:

package com.javacode2018.lesson001.demo16;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class CostTimeInvocationHandler implements InvocationHandler {
    private Object target;
    public CostTimeInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.target, args);//@1
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗時(納秒):" + (endTime - starTime));
        return result;
    }
    /**
     * 用來建立targetInterface介面的代理物件
     *
     * @param target          需要被代理的物件
     * @param targetInterface 被代理的介面
     * @param <T>
     * @return
     */
    public static <T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface()) {
            throw new IllegalStateException("targetInterface必須是介面型別!");
        } else if (!targetInterface.isAssignableFrom(target.getClass())) {
            throw new IllegalStateException("target必須是targetInterface介面的實現類!");
        }
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
    }}12345678910111213141516171819202122232425262728293031323334353637383940

上面主要是createProxy方法用來建立代理物件,2個引數:

target:目標物件,需要實現targetInterface介面

targetInterface:需要建立代理的介面

invoke方法中透過method.invoke(this.target, args)呼叫目標方法,然後統計方法的耗時。

測試用例

@Testpublic void costTimeProxy() {
    IService serviceA = CostTimeInvocationHandler.createProxy(new ServiceA(), IService.class);
    IService serviceB = CostTimeInvocationHandler.createProxy(new ServiceB(), IService.class);
    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();}123456789101112

執行輸出

我是ServiceA中的m1方法!class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):61300我是ServiceA中的m2方法!class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):22300我是ServiceA中的m3方法!class com.javacode2018.lesson001.demo16.ServiceA.m1()方法耗時(納秒):18700我是ServiceB中的m1方法!class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):54700我是ServiceB中的m2方法!class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):27200我是ServiceB中的m3方法!class com.javacode2018.lesson001.demo16.ServiceB.m1()方法耗時(納秒):19800123456789101112

我們再來的介面,也需要統計耗時的功能,此時我們無需去建立新的代理類即可實現同樣的功能,如下:

IUserService介面

package com.javacode2018.lesson001.demo16;public interface IUserService {
    /**
     * 插入使用者資訊
     * @param name
     */
    void insert(String name);}123456789

IUserService介面實現類:

package com.javacode2018.lesson001.demo16;public class UserService implements IUserService {
    @Override
    public void insert(String name) {
        System.out.println(String.format("使用者[name:%s]插入成功!", name));
    }}12345678

測試用例

@Testpublic void userService() {
    IUserService userService = CostTimeInvocationHandler.createProxy(new UserService(), IUserService.class);
    userService.insert("路人甲Java");}12345

執行輸出:

使用者[name:路人甲Java]插入成功!class com.javacode2018.lesson001.demo16.UserService.m1()方法耗時(納秒):19300012

上面當我們建立一個新的介面的時候,不需要再去新建一個代理類了,只需要使用CostTimeInvocationHandler.createProxy建立一個新的代理物件就可以了,方便了很多。

Proxy使用注意 ask.baikezh.com/hefei/

  1. jdk中的Proxy只能為介面生成代理類,如果你想給某個類建立代理類,那麼Proxy是無能為力的,此時需要我們用到下面要說的cglib了。

  2. Proxy類中提供的幾個常用的靜態方法大家需要掌握

  3. 透過Proxy建立代理物件,當呼叫代理物件任意方法時候,會被InvocationHandler介面中的invoke方法進行處理,這個介面內容是關鍵


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2732119/,如需轉載,請註明出處,否則將追究法律責任。

相關文章