講一些你所不知道的Java動態代理

簡介

Proxy 是設計模式中的一種。當須要在已存在的 class 上添加或修改功能時,能夠經過建立 proxy object 來實現java

一般 proxy object 和被代理對象擁有相同的方法,而且擁有被代理對象的引用,能夠調用其方法程序員

代理模式應用場景包括面試

  • 在方法執行先後打印和記錄日誌
  • 認證、參數檢查
  • lazy instantiation (Hibernate, Mybatis)
  • AOP (transaction)
  • mocking

代理有兩種實現方式sql

  • 靜態代理:在編譯時期,建立代理對象
  • 動態代理:在運行時期,動態建立

對於重複性工做,如打印日誌,靜態代理須要爲每一個 class 都建立 proxy class,過程繁瑣和低效,而動態代理經過使用反射在運行時生成 bytecode 的方式來實現,更加方便和強大設計模式

過程

由於 JDK 自帶的 Dynamic proxy 只可以代理 interfaces,所以被代理對象須要實現一個或多個接口。架構

先來看一些概念:併發

  • proxy interface proxy class 實現的接口
  • proxy class 運行時建立的代理 class,並實現一個或多個 proxy interface
  • proxy instance proxy class 的實例
  • InvocationHandler 每一個 proxy instance 都有一個關聯的 invocation handler,當調用 proxy 對象的方法時,會統一封裝,並轉發到 invoke() 方法

InvocationHandler 接口的定義以下分佈式

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

只定義了一個方法 invoke(),參數含義以下ide

  • Object proxy 生成的代理對象
  • Method method 調用的方法,類型爲 java.lang.reflect.Method
  • Object[] args 調用方法的參數,array of objects

簡單來講就是,調用 proxy object 上的方法,最終都會轉換成對關聯 InvocationHandler invoke() 方法的調用高併發

能夠使用 java.lang.reflect.Proxy 的靜態方法 newProxyInstance 來建立 Proxy object

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    ...
}

參數說明

  • loader 定義 proxy class 的 ClassLoader
  • interfaces 須要代理的接口
  • h 關聯的 InvocationHandler

例子

使用動態代理打印方法的執行耗時

定義代理接口

public interface Foo {
    String doSomething();
}

實現接口

public class FooImpl implements Foo {
    @Override
    public String doSomething() {
        return "finished";
    }
}

定義 InvocationHandler,target 爲被代理對象的引用,在方法執行完後打印耗時

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimingInvocationHandler implements InvocationHandler {
    private Object target;
    public TimingInvocationHandler(Object target) {
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
        long start = System.nanoTime();
        Object result = method.invoke(target, args);
        long elapsed = System.nanoTime() - start;
        System.out.println(String.format("Executing %s finished in %d ns",
                        method.getName(),
                        elapsed));
        return result;
    }
}

測試

import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
    @Test
        public void test() {
        ClassLoader cl = DynamicProxyTest.class.getClassLoader();
        Class[] interfaces = new Class[]{Foo.class};
        FooImpl fooImpl = new FooImpl();
        InvocationHandler timingInvocationHandler = new TimingInvocationHandler(fooImpl);
        Foo foo = (Foo) Proxy.newProxyInstance(cl, interfaces, timingInvocationHandler);
        foo.doSomething();
    }
}

執行完會打印相似

Executing doSomething finished in 23148 ns

細節

生成 proxy class 的一些屬性和細節

  • public, final, and not abstract.
  • 類名不肯定,以 $Proxy 開頭
  • 繼承 java.lang.reflect.Proxy,且 Proxy 實現了 java.io.Serializable 接口,所以 proxy instance 是能夠序列化的
  • 按照 Proxy.newProxyInstance() 傳入 interfaces 參數中的接口順序來實現接口
  • 在 proxy class 上調用 getInterfaces,getMethods,getMethod 方法,會返回實現的接口中定義的方法,順序和建立時的參數保持一致
  • 當調用 proxy instance 同名、同 parameter signature 方法時,invoke() 方法的 Method 參數會是最先定義這個方法的 interface 的方法,不管實際調用的方法是什麼
  • 當 Foo 爲實現的代理接口之一時, proxy instanceof Foo 返 true,而且能夠轉換 (Foo) proxy
  • Proxy.getInvocationHandler 靜態方法會返回 proxy object 關聯的 invocation handler

讀者福利

分享免費學習資料

針對於Java程序員,我這邊準備免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)

爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個小贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!

資料領取方式:加入Java技術交流羣963944895點擊加入羣聊,私信管理員便可免費領取

怎麼提升代碼質量?——來自阿里P8架構師的研發經驗總結

阿里P8分享Java架構師的學習路線,第六點尤其重要

每一個Java開發者應該知道的八個工具

想面試Java架構師?這些最基本的東西你都會了嗎?

畫個圖來找你的核心競爭力,變中年危機爲加油站

哪有什麼中年危機,不過是把定目標當成了有計劃

被裁人不是寒冬重點,重點是怎麼破解職業瓶頸

相關文章
相關標籤/搜索