Java動態代理


Java動態代理

在介紹動態代理之前,我們先來說說靜態代理。

靜態代理

假設,現在有這么一個需求場景:項目依賴了一個三方庫,現在想要在項目調用三方庫時記錄調用日志。那么我們如何能夠在無法修改三方庫代碼的前提下,完成這個需求呢?

相信大家能夠想到很多種方法來實現,其中最簡單粗暴的就是靜態代理了。大概的做法是:

  1. 為每一個被調用的三方類(委托類)都編寫一個對應的代理類,並實現相同的接口,通過代理類創建出代理對象。
  2. 在創建代理對象時,通過構造器塞入原委托對象,在代理對象的方法內部調用原委托對象的同名方法,並在調用前后打印日志。

也就是說,代理對象=原委托對象+增強代碼,有了代理對象后,就不用原委托對象了。

大概的代碼是這樣的:

/**
 * 代理類與被代理類(委托類)都實現了UserService接口
 */
public interface UserService {
    public void select();
}

/**
 * 實現了UserService接口的委托類
 */
public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("invoke UserServiceImpl::select");
    }
}

/**
 * 實現了UserService接口的代理類
 */
public class UserServiceProxy implements UserService {
  // 被代理的原委托對象
  private UserService target;
  
  public UserServiceProxy(UserService target) {
    this.target = target;
  }
  
  public void select() {
    before();
    // 調用原委托對象的原方法,也是真正調用三方接口的代碼
    target.select();
    after();
  }
  
  private void before() {
    System.out.println("invoe UserServiceProxy::before");
  }
  
  private void after() {
    System.out.println("invoe UserServiceProxy::after");
  }
}

/**
 * 在其他的對象中使用代理類
 */
public class Client {
  public void selectUser() {
    UserService proxy = new UserServiceProxy(new UserServiceImpl());
    proxy.select();
  }
}

像這樣的通過對應的代理對象來代理原委托對象的方法,我們稱之為靜態代理。

靜態代理的優點就是簡單粗暴,分別為原委托對象實現各自的代理對象即可;但缺點也很明顯,靜態代理需要編寫大量的代理代碼,實現起來非常的繁瑣,此外一旦原委托對象需要增加新的方法或修改已有的方法時,代理對象都需要進行相應的修改,在維護性上較差。

動態代理

靜態代理在代碼冗余、維護性上都存在問題,回到文章開篇的場景下,如果項目中大量調用了三方庫,那靜態代理就不是最優解了。所以,我們的解決方向應該是如何少寫或者不寫代理類但也能夠完成代理功能

現在的方向是少寫或不寫代理類也能完成代理,那么我們可不可以想辦法自動生成代理類不需要手動編寫呢,讓我們從基礎的對象創建開始。

我們知道JVM創建對象的過程如下:

BCznZd.png

其中的Class對象,是Class類的實例,而Class類是Java中用來描述所有類的類,所以要創建一個對象,首先是得到對應的Class對象,既然如此,那么是不是可以想辦法得到委托對象的Class對象,然后根據Class對象創建代理實例。

在Java中,JDK已經為我們提供了java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler,這兩個API相互配合,Proxy是整體的入口,而InvocateionHandler是中介類,連接委托類和代理類,主要用來增強代碼的實現。

Proxy有個靜態方法:getProxyClass(ClassLoader loader, Class<?>... interfaces),這個方法會根據傳入的類加載器Class接口列表返回代理對象的Class類對象,同時這個Class類對象會繼承實現所傳入的接口Class。

簡單點的理解就是這樣:

Bp5ay8.png BCMjI0.png

到這里我們已經能夠通過委托類的Class創建出代理類的Class,那么接下來就是生成代理實例,同時插入增強代碼。

這里的增強代碼就需要借助中介類java.lang.reflect.InvocationHandler了,中介類主要是作為調用處理器攔截對委托類方法的調用。

一個簡單的中介類大概是這樣的:

public class LogHandler implements InvocationHandler { 
    // obj為委托對象; 
    private Object obj; 
 
    public LogHandler(Object obj) {
        this.obj = obj;
    } 
 
    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        System.out.println("invoke before"); 
        // 調用委托對象的方法
        Object result = method.invoke(obj, args); 
        System.out.println("invoke after"); 
        return result;
    }
} 

其內部的調用邏輯是這樣的:

BC31eJ.png

也可以理解成這樣的:

BC7XvR.png

這樣的話我們能夠得到全部的代碼是這樣的:

public class ProxyTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        // 拿到委托類的代理對象的Class對象
        Class userServiceClass = Proxy.getProxyClass(UserService.class.getClassLoader(), UserService.class);
        // 得到Class對象的構造器$Proxy0(java.lang.reflect.InvocationHandler)
        Constructor constructor = userServiceClass.getConstructor(InvocationHandler.class);

        // 增強代碼中介類對象
        InvocationHandler handler = new LogHandler(new UserServiceImpl());
        // 反射創建代理對象
        UserService impl = (UserService) constructor.newInstance(handler);
        impl.select();
    }

}

class LogHandler implements InvocationHandler {
    // obj為委托對象
    private Object obj;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke before");
        Object result = method.invoke(obj, args);
        System.out.println("invoke after");
        return result;
    }
}

最終的輸出結果:

invoke before
invoke UserServiceImpl::select
invoke after

到了這里動態代理的基本內容是差不多了,但回頭看看我們的代碼,會發現在代碼存在多處的硬編碼,如Proxy.getProxyClass(UserService.class.getClassLoader(), UserService.class);等等,這種寫法並不優雅,如果后面想代理其他委托對象就很是麻煩,所以接下來,對代碼進行優化下:

public class ProxyTest {
    public static void main(String[] args) throws Exception {
        // 根據委托對象獲取代理對象
        UserService impl = (UserService) getProxy(new UserServiceImpl());
        impl.select();
    }

    private static Object getProxy(Object obj) throws Exception {
        // 拿到委托類的代理對象的Class對象
        // 參數1:委托對象的類加載器 參數2:委托對象的接口列表,這樣代理對象能夠繼承實現相同的接口
        Class objClass = Proxy.getProxyClass(obj.getClass().getClassLoader(), obj.getClass().getInterfaces());
        // 得到Class對象的構造器$Proxy0(java.lang.reflect.InvocationHandler)
        Constructor constructor = objClass.getConstructor(InvocationHandler.class);

        // 反射創建代理對象,傳入中介類對象
        return constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("invoke before");
                Object result = method.invoke(obj, args);
                System.out.println("invoke after");
                return result;
            }
        });
    }
}

改進后的代碼就好多了,無論現有系統有多少類,只需要將委托對象傳進去就可以了。

實際上,Proxy還提供了一步到位的靜態方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h),方法可以直接返回代理對象,省去了獲取代理對象的Class對象的過程:

public class ProxyTest {

    public static void main(String[] args) throws Exception {
        // 根據委托對象獲取代理對象
        UserService impl = (UserService) getProxy(new UserServiceImpl());
        impl.select();
    }

    private static Object getProxy(Object obj) throws Exception {
        // 直接一步到位
        return Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                // lambda改進
                (proxy, method, args) -> {
                    System.out.println("invoke before");
                    Object result = method.invoke(obj, args);
                    System.out.println("invoke after");
                    return result;
                });
    }
}

動態代理實際上有很多應用,比如Spring AOP的實現,RPC框架的實現,一些第三方工具庫的內部使用等等。這里就不扯了這些了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM