更多 Java 高級知識方面的文章,請參見文集《Java 高級知識》
代理模式
- 委托類:具體提供服務
-
代理類:通過調用委托類對象的方法來提供服務。代理類的作用包括:
- 調用具體方法前進行預處理,包括:
- 數據的驗證及過濾
- 權限的驗證
- 開啟事務
- 查詢緩存
- 建立數據庫連接
- 記錄日志等等。
- 調用具體方法后進行后處理,包括:
- 提交事務
- 更新緩存
- 關閉數據庫連接
- 記錄日志等等。
- 調用具體方法前進行預處理,包括:
代理模式分為兩種形式:
- 靜態代理:程序運行前,代理類已經存在于 class 字節碼文件中,程序運行中只是創建代理類的對象。
- 動態代理:程序運行中動態創建出代理類及其對象(原理:通過反射來動態產生字節碼)
靜態代理
示例如下:
public class Proxy_Test {
public static void main(String[] args) throws Exception {
AddProxy proxy = new AddProxy(new AddImpl());
proxy.add(1, 2);
}
}
// 共同的接口
interface Add {
int add(int a, int b);
}
// 委托類
class AddImpl implements Add {
public int add(int a, int b) {
return a + b;
}
}
// 代理類
class AddProxy implements Add {
// 代理類關聯一個委托類對象
private AddImpl impl;
// 通過構造方法傳入委托類的對象
public AddProxy(AddImpl impl) {
this.impl = impl;
}
public int add(int a, int b) {
// 預處理,記錄日志
System.out.println("Add start");
// 通過調用委托類對象的方法來提供服務
return impl.add(a, b);
}
}
可以看出:
- 委托類和代理類需要實現同一個接口
- 委托類具體提供服務
- 代理類通過調用委托類對象的方法來提供服務,在調用前進行預處理操作,例如記錄日志
- 代理類關聯一個委托類對象,并通過構造方法傳入委托類的對象
靜態代理的問題
代理類與委托類一一對應。
如果有多個委托類,例如在上述的例子中存在 AddImpl
MinusImpl
MultiplyImpl
DivideImpl
,則需要對應地構造多個代理類,產生重復的代碼,因為每個代理類的功能其實都一樣,例如記錄日志。
動態代理
一個代理類完成全部的代理功能,代理一個或多個委托類。
Proxy 類
所在包:import java.lang.reflect.Proxy;
通過 Proxy
類的 newProxyInstance
方法可以為一個或多個接口動態地創建出代理類及其對象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
該方法返回一個代理類對象,該對象實現了指定的接口 Class<?>[] interfaces
。
在 newProxyInstance
方法中,會調用如下代碼:即在運行時動態生成代理類的字節碼,并將字節碼轉換為代理類的對象。
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
InvocationHandler 接口
所在包:import java.lang.reflect.InvocationHandler;
實現如下的 invoke
方法:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.
當調用代理類對象的方法時,會自動調用該 invoke 方法。
在該 invoke 方法中進行預處理,調用委托類方法 method,后處理,返回結果。
通過動態代理類實現如上的靜態代理功能,完整的代碼如下:
public class Proxy_Test {
public static void main(String[] args) throws Exception {
// 動態創建 AddImpl 的代理類對象
Add addProxy = (Add) generateProxy(new AddImpl());
addProxy.add(1, 2);
// 動態創建 MinusImpl 的代理類對象
Minus minusProxy = (Minus) generateProxy(new MinusImpl());
minusProxy.minus(1, 2);
}
public static Object generateProxy(Object obj) {
// 通過 Proxy 類的 newProxyInstance 方法可以為一個或多個接口動態地創建出代理類及其對象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 預處理,記錄日志
System.out.println(method.getName() + " start");
// 實際調用委托類的具體方法
return method.invoke(obj, args);
}
});
}
}
// 共同的接口
interface Add {
int add(int a, int b);
}
interface Minus {
int minus(int a, int b);
}
interface Multiply {
int multiply(int a, int b);
}
interface Divide {
int divide(int a, int b);
}
// 委托類
class AddImpl implements Add {
public int add(int a, int b) {
return a + b;
}
}
class MinusImpl implements Minus {
public int minus(int a, int b) {
return a - b;
}
}
class MultiplyImpl implements Multiply {
public int multiply(int a, int b) {
return a * b;
}
}
class DivideImpl implements Divide {
public int divide(int a, int b) {
return a / b;
}
}
動態代理的原理
調用代理類的方法時 addProxy.add(1, 2);
,會自動觸發 invoke
方法,invoke
方法中調用委托類的方法 method.invoke(obj, args);
。
實際上,invoke
方法不是顯示調用的,它是一個回調函數。
動態代理的問題
委托類需要實現某一個接口,例如 AddImpl
需要實現 Add
,MinusImpl
需要實現 Minus
。
CGLib 可以彌補這個缺陷,不需要實現特定的接口。
CGLib 通過繼承來實現,動態產生的代理類實際上為委托類的子類,代理類通過 Method Override 實現了代碼增強。
例如:
Enhancer e = new Enhancer();
e.setSuperclass(AddImpl.class);
e.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 預處理,記錄日志
System.out.println(method.getName() + " start");
return method.invoke(o, objects);
}
});
// 動態產生的代理類實際上為委托類的子類
AddImpl proxy = (AddImpl)e.create();
proxy.add(1, 2);
關于 CGLib 和 JDK Proxy 的比較,可以參見 Stack Overflow