既然有動態代理,那麼確定有靜態代理,靜態代理請參考設計模式之靜態代理java
做用: 與靜態代理同樣,但動態代理解決了 靜態代理中 1個靜態代理,只代理1種類型的目標對象 的問題編程
在 JDK 動態代理中涉及以下角色:設計模式
廢話少說先上代碼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
複製代碼
測試方法中就是使用該類的方法來生成代理對象的!
這是JDK動態代理中最爲重要的一個類,經過該類的 newProxyInstance 方法,咱們能夠生成一個代理類
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
複製代碼
如上,newProxyInstance 須要三個參數
生成代理類的過程以下
因爲代理類是在運行時動態生成的,沒有.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);
}
}
}
複製代碼
還記得在 處理模版那節留的疑問嗎—— 「留一個疑問,誰去調用invoke()方法,方法中的三個參數是什麼意思」 如今咱們知道了,是代理類去調用invoke(),它會傳3個參數,本身(代理對象本身)、目標方法、參數列表
JDK的動態代理
特色: 不須要顯式實現與目標類相同的接口,而是將這種實現推遲到程序調用時由 JVM來實現,
優勢:一個代理類就能夠代理多個目標類,避免重複、多餘代碼
缺點
應用場景
與靜態代理的區別
設計模式 | 代理類建立時機 | 原理 | 效率 | 能代理的目標類的數量 |
---|---|---|---|---|
動態代理 | 運行時動態建立 | 反射 | 低 | 能代理多個目標類 |
靜態代理 | 運行前,先顯示建立好 | / | 高 | 只能代理一個目標類 |