https://mp.weixin.qq.com/s/YeNXq8VkP-IlqZ8rBv486w
導讀:
1、JDK動態代理原理是什么?為什么不支持類的代理?
2、JDK動態代理實例
3、CGLib代理原理是什么?
4、CGLib代理實例
5、JDK動態代理與CGLib代理的區別是什么?
6、總結
注:閱讀本文之前可以先閱讀:什么是代理模式?
1. JDK動態代理原理是什么?為什么不支持類的代理?
jdk動態代理圖:
利用攔截器(攔截器必須實現InvocationHanlder)加上反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。是在程序運行的過程中,根據被代理的接口來動態生成代理類的class文件,並加載運行的過程。
之所以只支持實現了接口的類的代理。從原理上講是因為JVM動態生成的代理類有如下特性:
繼承了Proxy類,實現了代理的接口,最終形式如下(HelloInterface為被代理類實現的接口):
public final class $Proxy0 extends Proxy implements HelloInterface{
.......
}
從使用上講,創建代理類時必須傳入被代理類實現的接口。 因為java不能多繼承,這里已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。
1.1 詳細介紹:
在java的動態代理機制中,有兩個重要的類或接口,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class),這一個類和接口是實現我們動態代理所必須用到的。
1.1.1 InvocationHandler
每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用。
InvocationHandler這個接口的唯一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
這個方法一共接受三個參數,那么這三個參數分別代表如下:
-
proxy: 指代JDK動態生成的最終代理對象
-
method: 指代的是我們所要調用真實對象的某個方法的Method對象
-
args: 指代的是調用真實對象某個方法時接受的參數
1.1.2 Proxy
Proxy這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
這個方法的作用就是得到一個動態的代理對象,其接收三個參數,我們來看看這三個參數所代表的含義:
-
loader: ClassLoader對象,定義了由哪個ClassLoader來對生成的代理對象進行加載。
-
interfaces: Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了。
-
Handler:InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上。
所以我們所說的DynamicProxy(動態代理類)是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,然后該class就宣稱它實現了這些 interface。這個DynamicProxy其實就是一個Proxy,它不會做實質性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作。
2. JDK動態代理實例
2.1 創建接口類
public interface HelloInterface {
void sayHello();
}
2.2 創建被代理類,實現接口
/**
* 被代理類
*/
public class HelloImpl implements HelloInterface{
@Override
public void sayHello() {
System.out.println("hello");
}
}
2.3創建InvocationHandler實現類
/**
* 每次生成動態代理類對象時都需要指定一個實現了InvocationHandler接口的調用處理器對象
*/
public class ProxyHandler implements InvocationHandler{
private Object subject; // 這個就是我們要代理的真實對象,也就是真正執行業務邏輯的類
public ProxyHandler(Object subject) {// 通過構造方法傳入這個被代理對象
this.subject = subject;
}
/**
*當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
*/
@Override
public Object invoke(Object obj, Method method, Object[] objs)
throws Throwable {
Object result = null;
System.out.println("可以在調用實際方法前做一些事情");
System.out.println("當前調用的方法是" + method.getName());
result = method.invoke(subject, objs);// 需要指定被代理對象和傳入參數
System.out.println(method.getName() + "方法的返回值是" + result);
System.out.println("可以在調用實際方法后做一些事情");
System.out.println("------------------------");
return result;// 返回method方法執行后的返回值
}
}
2.4 測試
public class Mytest {
public static void main(String[] args) {
//第一步:創建被代理對象
HelloImpl hello = new HelloImpl();
//第二步:創建handler,傳入真實對象
ProxyHandler handler = new ProxyHandler(hello);
//第三步:創建代理對象,傳入類加載器、接口、handler
HelloInterface helloProxy = (HelloInterface) Proxy.newProxyInstance(
HelloInterface.class.getClassLoader(),
new Class[]{HelloInterface.class}, handler);
//第四步:調用方法
helloProxy.sayHello();
}
}
2.5 結果
可以在調用實際方法前做一些事情
當前調用的方法是sayHello
hello
sayHello方法的返回值是null
可以在調用實際方法后做一些事情
------------------------
3. CGLib代理原理是什么?
CGLib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,並在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。(利用ASM開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理)
3.1 CGLib核心類:
1、 net.sf.cglib.proxy.Enhancer:主要增強類,通過字節碼技術動態創建委托類的子類實例;
Enhancer可能是CGLIB中最常用的一個類,和Java1.3動態代理中引入的Proxy類差不多。和Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創建一個被代理對象的子類並且攔截所有的方法調用(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由於Java final方法語義決定的。基於同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什么不能持久化final class的原因。
2、net.sf.cglib.proxy.MethodInterceptor:常用的方法攔截器接口,需要實現intercept方法,實現具體攔截處理;
public java.lang.Object intercept(java.lang.Object obj,
java.lang.reflect.Method method,
java.lang.Object[] args,
MethodProxy proxy)
throws java.lang.Throwable{}
-
obj:動態生成的代理對象
-
method : 實際調用的方法
-
args:調用方法入參
-
proxy:
-
net.sf.cglib.proxy.MethodProxy:java Method類的代理類,可以實現委托類對象的方法的調用;常用方法:methodProxy.invokeSuper(proxy, args);在攔截方法內可以調用多次
4. CGLib代理實例
4.1 創建被代理類
public class SayHello {
public void say(){
System.out.println("hello");
}
}
4.2 創建代理類
/**
*代理類
*/
public class ProxyCglib implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
//設置需要創建子類的類
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通過字節碼技術動態創建子類實例
return enhancer.create();
}
//實現MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("可以在調用實際方法前做一些事情");
//通過代理類調用父類中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("可以在調用實際方法后做一些事情");
return result;
}
}
4.3 測試
public class Mytest {
public static void main(String[] args) {
ProxyCglib proxy = new ProxyCglib();
//通過生成子類的方式創建代理類
SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
proxyImp.say();
}
}
4.4 結果
可以在調用實際方法前做一些事情
hello
可以在調用實際方法后做一些事情
5. JDK動態代理與CGLib代理的區別是什么?
5.1 原理區別:
java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。核心是實現InvocationHandler接口,使用invoke()方法進行面向切面的處理,調用相應的通知。
而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。核心是實現MethodInterceptor接口,使用intercept()方法進行面向切面的處理,調用相應的通知。
1、如果目標對象實現了接口,默認情況下會采用JDK的動態代理實現AOP
2、如果目標對象實現了接口,可以強制使用CGLIB實現AOP
3、如果目標對象沒有實現了接口,必須采用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換
5.2 性能區別:
1、CGLib底層采用ASM字節碼生成框架,使用字節碼技術生成代理類,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的方法進行代理,因為CGLib原理是動態生成被代理類的子類。
2、在jdk6、jdk7、jdk8逐步對JDK動態代理優化之后,在調用次數較少的情況下,JDK代理效率高於CGLIB代理效率,只有當進行大量調用的時候,jdk6和jdk7比CGLIB代理效率低一點,但是到jdk8的時候,jdk代理效率高於CGLIB代理。
5.3 各自局限:
1、JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理。
2、cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為采用的是繼承,所以不能對final修飾的類進行代理。
6. 總結