設計模式之動態代理

既然有動態代理,那麼確定有靜態代理,靜態代理請參考設計模式之靜態代理java

做用: 與靜態代理同樣,但動態代理解決了 靜態代理中 1個靜態代理,只代理1種類型的目標對象 的問題編程

在 JDK 動態代理中涉及以下角色:設計模式

  • 目標類的接口 Interface
  • 目標類 target
  • 處理模版 Handler
  • 在內存中生成的動態代理類
  • java.lang.reflect.Proxy

廢話少說先上代碼bash

實例

目標類的接口

要有jdk動態代理,你的類必須實現接口ide

/**
 * UserService 是 目標類的接口
 * login() 就是要被代理的方法
 */
public interface UserService {
    boolean login(String userName, String password);
}

複製代碼

目標類

注意,要代理的方法,必須是從接口中實現的post

/**
 * UserServiceImpl 是目標類,或者叫被代理的類
 * login() 就是要被代理的方法
 */
public class UserServiceImpl implements UserService {

    @Override
    public boolean login(String userName, String password) {
        System.out.println("校驗帳號");
        return false;
    }
}
複製代碼

處理模版類

這是一個InvocationHandler的實現類測試

做用: 定義一種對目標方法作某種操做的模版(這裏運用了面向抽象編程的思想) 以下,定義了一個在目標方法先後輸出log的模版。ui

說明: LogHandler 有一個 屬性 —— 目標對象,能夠看到我特地把它定義成 Object 類型,而不是 UserService 類型,也就是說 LogHandler 的目標對象能夠是任意類型,而不侷限在 UserService。這樣一來 LogHandler 就能夠在 任意類型的任意方法 先後輸出log,因此稱它爲處理模版this

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

/**
 * 這是一個自定義的處理模版,它將在 目標方法 的先後輸出log
 */
public class LogHandler implements InvocationHandler {

    // 目標對象
    //private UserService target;
    private Object target;

    // 初始化時,將目標對象傳進來
    public LogHandler(Object target) {
        this.target = target;
    }

    /**
     * 定義一個在目標方法執行先後輸出log的處理模版
     *
     * @param proxy  動態代理對象
     * @param method 目標方法
     * @param args   目標的參數
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        //調用目標對象的目標方法,target 是初始化時傳人的目標對象
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before() {
        System.out.println("調用目標方法以前輸出的log");
    }

    private void after() {
        System.out.println("調用目標方法以後輸出的log");
    }
}
複製代碼

這裏留一個疑問,誰去調用invoke()方法,方法中的三個參數是什麼意思(雖然我已經寫上去了)?spa

測試類

上面三個類構成了jdk動態代理的最小組成單位,接下來,編寫測試類使用jdk動態代理

import java.lang.reflect.Proxy;

public static void main(String[] args) {
    //建立一個目標對象
    UserService userServiceImpl = new UserServiceImpl();
    // 建立 目標對象的 處理模版
    LogHandler userServiceLogHandler = new LogHandler(userServiceImpl);
    /**
     * Proxy.newProxyInstance 會根據 目標對象的 類加載器、接口的Class、目標對象的處理模版,動態生成一個代理類,並返回代理對象
     * userServiceProxy 就是 動態建立出的代理對象,它實現了 UserService 接口 的login方法
     */
    UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userServiceImpl.getClass().getClassLoader(),
            new Class[]{UserService.class}, userServiceLogHandler);
    userServiceProxy.login("wqlm", "123");
    System.out.println("\n代理類的名稱爲" + userServiceProxy.getClass().getName());
}
複製代碼

輸出結果

/Library/Java/jdk1.8.0_201/Contents/Home/bin/java...
調用目標方法以前輸出的log
校驗帳號
調用目標方法以後輸出的log

代理類的名稱爲com.sun.proxy.$Proxy0

Process finished with exit code 0
複製代碼

java.lang.reflect.Proxy

測試方法中就是使用該類的方法來生成代理對象的!

這是JDK動態代理中最爲重要的一個類,經過該類的 newProxyInstance 方法,咱們能夠生成一個代理類

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
複製代碼

如上,newProxyInstance 須要三個參數

  • ClassLoader ,目標類target 的類加載器
  • Class<?>[] ,目標類的接口 Interface 的Class
  • InvocationHandler , 處理模版類 Handler

生成代理類的過程以下

  1. Proxy.newProxyInstance 方法, 經過傳遞給它的參數 Class<?>[] interfaces, InvocationHandler h 生成代理類的字節碼
  2. Proxy 經過傳遞給它的參數(ClassLoader)來加載生成的代理類的字節碼
  3. 返回加載完成的代理類的對象

動態生成的代理類

因爲代理類是在運行時動態生成的,沒有.java文件,也沒有.class文件。可是根據上面的運行結果咱們知道,生成的動態代理類叫 com.sun.proxy.$Proxy0,能夠經過

byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class<?>[]{userServiceProxy.getClass()});
String pathDir = "/Users/wqlm/Desktop";
String path = "/$Proxy0.class";
File f = new File(pathDir);
path = f.getAbsolutePath() + path;
f = new File(path);
if (f.exists()) {
    f.delete();
}

try {
    f.createNewFile();
} catch (IOException e) {
    e.printStackTrace();
}

try (FileOutputStream fos = new FileOutputStream(path)) {
    fos.write(bytes, 0, bytes.length);
} catch (Exception e) {
    e.printStackTrace();
} 
複製代碼

獲取字節碼流,在通過反編譯,就能夠獲得大概的.java文件。如下是處理過的 (去掉了不須要關注掉內容)代理類的.java文件

import com.example.demo.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

/**
 * 代理類 繼承了 Proxy,並實現了 目標接口 UserService
 */
public final class $Proxy0 extends Proxy implements UserService {

    private static Method m3;

    static {
        try {
            m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("login",
                    new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    // 初始化時,將處理模版 傳遞給父類
    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    /**
     * 這就是代理方法,能夠看到,它是同過調用父類的 處理模版 的invoke 方法,而後把本身,以及目標方法和參數傳遞進去
     */
    public final boolean login(String var1, String var2) {
        try {
            return ((Boolean) super.h.invoke(this, m3, new Object[]{var1, var2})).booleanValue();
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }
}
複製代碼
  • 動態代理類實現了目標類的接口,並實現了接口中的方法,該方法的實現邏輯是, 調用父類 —— Proxy 的 h 的 invoke()方法
  • 其中h 是 在建立動態代理實例時 newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) 傳入的第3個參數InvocationHandler對象
  • 在 InvocationHandler.invoke()中經過反射,調用目標對象

還記得在 處理模版那節留的疑問嗎—— 「留一個疑問,誰去調用invoke()方法,方法中的三個參數是什麼意思」 如今咱們知道了,是代理類去調用invoke(),它會傳3個參數,本身(代理對象本身)、目標方法、參數列表

總結

JDK的動態代理

特色: 不須要顯式實現與目標類相同的接口,而是將這種實現推遲到程序調用時由 JVM來實現,

  • 即:在使用時再建立動態代理類 和 實例
  • 靜態代理則是在代理類實現時就指定與目標相同的接口

優勢:一個代理類就能夠代理多個目標類,避免重複、多餘代碼

缺點

  • 相比與靜態代理,效率低。 靜態代理是直接調用目標對象方法,而動態代理則須要先生成類和對象,在經過Java反射機制間接調用目標對象的方法
  • 應用場景侷限, 因爲每一個代理類都繼承了 java.lang.reflect.Proxy 類,而Java又只能支持單繼承,致使不能針對類 建立代理類,只能針對接口 建立 代理類。即,動態代理只能代理實現了接口的類。

應用場景

  • 須要代理的對象數量較多的狀況下。數量較少時,推薦直接使用靜態代理
  • 面向切面編程時

與靜態代理的區別

設計模式 代理類建立時機 原理 效率 能代理的目標類的數量
動態代理 運行時動態建立 反射 能代理多個目標類
靜態代理 運行前,先顯示建立好 / 只能代理一個目標類
相關文章
相關標籤/搜索