代理模式是指給某個對象提供一個代理對象,用戶不直接訪問原對象而是通過代理對象間接訪問。
我們可以使用代理模式實現面向切面編程(AOP), 由動態代理將切面功能織入目標方法而不侵入調用方的業務代碼。
或者使用代理模式實現遠程過程調用(RPC), 調用方像調用本地方法一樣調用代理方法,而不必關心代理調用遠程方法細節。
JDK提供了基於反射機制的動態代理實現,而被廣泛使用的第三方庫CGLIB則基於字節碼操作框架ASM實現動態代理。
本文將簡單介紹兩種動態代理使用方法,做拋磚引玉之用。
content:
代理模式
實現靜態代理模式是非常簡單的, 首先我們定義一個接口:
public interface MyInterface {
void foo();
}
編寫被代理的對象:
public class MyService implements MyInterface {
@Override
public void foo() {
System.out.println("foo");
}
}
在代理模式中被代理的類通常被稱作委託類。
編寫靜態代理:
public class MyProxy implements MyInterface {
private MyInterface subject;
MyProxy(MyInterface subject) {
this.subject = subject;
}
@Override
public void foo() {
long start = System.System.currentTimeMillis();
subject.foo();
long elapseTime = System.currentTimeMillis() - start;
System.out.println("elapse time: " + elapseTime);
}
}
靜態代理是指代理類在編譯時生成,與之相對動態代理則是運行時生成代理類的字節碼並加載到JVM中。
靜態代理的問題在於編寫代理類時必須瞭解接口細節, 即編寫MyProxy
時必須瞭解MyInterface
的定義。
以AOP框架爲例,框架無法預先了解接口信息只能在運行時根據Class對象創建代理對象,因此編寫此類框架必須要有動態代理機制的支持。
java動態代理
Java的反射機制可以在運行時創建類,Java標準庫中提供了基於反射的代理機制。
代理類應該實現java.lang.reflect.InvocationHandler
接口,該接口只有一個方法: invoke
。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
當我們通過代理訪問委託類方法時,會調用代理類的invoke方法。我們可以通過invoke方法的參數獲得調用信息,並用反射機制調用爲委託類的方法。
invoke方法的三個參數爲:
Object porxy
: 被調用方法的動態代理實例Method method
: 被調用的方法Object[] args
: 調用時傳入的參數
java.lang.reflect.Proxy.newProxyInstance
方法用於創建代理對象,它使用反射機制在運行時創建了代理類並加載到JVM中。該方法有三個參數:
ClassLoader loader
: 加載代理類的加載器Class<?>[] interfaces
: 代理類要實現的方法InvocationHandler h
: 在調用代理類方法時,嵌入調用過程的InvocationHandler實例
直接描述較難理解,我們來看代碼:
public class MyProxy implements InvocationHandler {
private Object subject;
public MyProxy(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1
return method.invoke(subject, args);
}
@SuppressWarnings("unchecked")
public static <T> T createProxy(T subject) {
return (T)Proxy.newProxyInstance(subject.getClass().getClassLoader(), // 2
subject.getClass().getInterfaces(), new MyProxy(subject));
}
public static void main(String[] args) {
MyInterface obj = createProxy(new MyService()); // 3
obj.foo();
}
}
MyInterface
和MyService
和上文中完全相同:
public interface MyInterface {
void foo();
}
public class MyService implements MyInterface {
@Override
public void foo() {
System.out.println("foo");
}
}
MyProxy
類實現了InvocationHandler
接口,並提供了createProxy
靜態方法。
在main
方法中我們爲MyService
對象創建了一個代理,並通過代理調用了foo
方法。
在代碼中有幾處細節值得了解:
在
main
方法中通過obj.getClass().getName()
獲得代理類的類名爲:com.sun.proxy.$Proxy0
。這個類名錶示這個類是動態生成的Proxy類,0表示它是當前JVM中第一個動態代理類。- 在註釋2處我們通過
newProxyInstance
創建了代理類即上文中的$Proxy0
類:loader
爲被代理類的加載器,也可以使用MyInterface.class.getClassLoader()
,它們都是AppClassLoader實例。interfaces
參數爲subject.getClass().getInterfaces()
表示動態代理類$Proxy0
實現了subject的所有接口,但$Proxy0
不是Subject類的子類。
因此在main
方法中使用MyInterface obj = createProxy(new MyService());
而不能使用MyService obj = createProxy(new MyService());
。- 我們使用
MyProxy
實例作爲InvocationHandler攔截動態代理類所有方法調用。
- 在註釋1處我們實現了
invoke
方法攔截動態代理對象所有方法調用:- 傳入的實參
Object proxy
是動態代理類的實例,即main
方法中的obj
, 它是$Proxy0
類的實例。注意InvocationHandler實例不是動態代理實例,handler是編譯生成的靜態類。 - 傳入的實參
Method method
是接口類的Method對象,即MyInterface.class.getMethod("foo")
而非委託類MyService
的Method對象 method.invoke(subject, args)
用反射的方式調用了被代理實例的方法,我們可以在invoke
方法中添加其它代碼以增強委託類的功能
- 傳入的實參
cglib動態代理
CGLIB是一個強大的動態代理庫,SpringAOP和dynaop框架使用CGLIB進行方法攔截和增強,Hibernate使用CGLIB進行代理關聯,此外JMock也使用CGLIB提供Mock對象。
在實現上,CGLIB使用高性能輕量級字節碼操作框架ASM來動態生成代理類。值得一提的是,CGLIB比JDK動態代理還要快。
CGLIB將動態代理類實現爲委託類的子類,因此可以代理沒有實現接口或對接口進行了擴展的類。因爲子類無法覆蓋final方法,因此只能代理非final方法。
首先使用maven導入cglib依賴:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.6</version>
</dependency>
編寫CGLIB動態代理:
public class CglibProxy {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T subject) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(subject.getClass());
enhancer.setCallback(new MyInterceptor());
return (T) enhancer.create();
}
private static class MyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
}
public static void main(String[] args) {
MyService obj = createProxy(new MyService());
obj.foo();
}
}
Enhancer
用於對委託類進行增強,它可以攔截方法調用以實現代理機制。
MethodInterceptor.intercept
方法用於攔截方法調用,它的幾個參數爲:
Object obj
: 動態代理對象Method method
: 被攔截的方法,本例main
方法中被攔截的方法是:MyService.class.getMethod("foo")
Object[] args
: 調用實參MethodProxy proxy
: 用來調用委託類方法的快捷代理,不是動態代理類。
示例中定義的動態代理沒有添加任何額外功能,這種特殊的Callback可以使用net.sf.cglib.proxy.NoOp
代替:
public static <T> T createProxy(T subject) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(subject.getClass());
enhancer.setCallback(NoOp.INSTANCE);
return (T) enhancer.create();
}
Enhancer可以使用CallbackFilter爲每個方法調用配置過濾器:
class MyService {
void foo0() {
System.out.println("0");
}
void foo1() {
System.out.println("1");
}
}
public class CglibProxy {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T subject) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(subject.getClass());
Callback callback0 = NoOp.INSTANCE;
Callback callback1 = NoOp.INSTANCE;
enhancer.setCallbacks(new Callback[]{callback0, callback1});
enhancer.setCallbackFilter(new MyCallbackFilter());
return (T) enhancer.create();
}
private static class MyCallbackFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if ("foo0".equals(method.getName())) {
return 0;
} else {
return 1;
}
}
}
public static void main(String[] args) {
MyService obj = createProxy(new MyService());
obj.foo0();
obj.foo1();
}
}
當Enhancer設置了CallbackFilter之後,在攔截方法前會先調用CallbackFilter.accept()
方法判斷使用哪個Callback實例進行攔截。
CallbackFilter.accept()
返回值即爲攔截器的下標,即若accept方法返回0,則調用enhancer.callbacks[0]
進行攔截。