靜態代理 動態代理 cglib原理區分
本文主要從三個方面介紹代理模式,什麼是代理模式,提供了什麼好處;代理模式的三種實現方式;三種代理的區別
首先簡單說明下為什麼需要代理模式:為其他物件提供一種代理以控制對這個物件的訪問,可以隔離客戶端和委託類的中介。我們還可以藉助代理來在增加一些功能,而不需要修改原有程式碼。
重點是代理模式的三種實現方式:
先給出簡單的介面和實現類:
public interface IHello { void sayHello(); } public final class Hello implements IHello{ @Override public void sayHello() { System.out.println("hello"); } }
1靜態代理模式
public class StaticProxy { IHello hello; public StaticProxy(IHello hello){ this.hello=hello; } public void syaHello(){ System.out.println("before"); hello.sayHello(); System.out.println("after"); } public static void main(String args[]){ new StaticProxy(new Hello()).syaHello(); } }
輸出為:
before hello after
2java動態代理實現的動態代理
//java動態代理實現的動態代理 public class DynamicProxy implements InvocationHandler { Object target; public DynamicProxy(Object target){ this.target=target; } public Object bind(){ return Proxy.newProxyInstance(this.target.getClass().getClassLoader(),this.target.getClass().getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); method.invoke(this.target,args); System.out.println("after"); return null; } public static void main(String args[]){ //此處返回的必定是介面,不可以轉為具體實體類,所以java反射要求被代理類必須實現了介面;但不要求finalIHello hello = (IHello) new DynamicProxy(new Hello()).bind(); hello.sayHello(); } }
java動態代理實現代理步驟:
a定義介面及介面實現類
b定義實現InvocationHandler介面的類並重寫invoke方法
c呼叫 Proxy.newProxyInstance(被代理的類.getClass().getClassLoader(),被代理的類.getClass().getInterfaces(),InvocationHandler的實現類);生成被代理的反射類
d呼叫需要執行的方法
3cglib實現的動態代理
public class CGLibProxy {
public static void main(String args[]){
Enhancer enhancer = new Enhancer();
//注意如果此時將Hello類宣告為final,則會報IllegalArgumentException;但不要求實現介面
enhancer.setSuperclass(Hello.class);
enhancer.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object result = methodProxy.invokeSuper(o,objects);
System.out.println("after");
return result;
}
});
Hello hello = (Hello)enhancer.create();
hello.sayHello();
}
}
Ehancer介紹:
Enhancer Enhancer可能是CGLIB中最常用的一個類,和Java1.3動態代理中引入的Proxy類差不多(如果對Proxy不懂,可以參考這裡)。和Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理介面。Enhancer建立一個被代理物件的子類並且攔截所有的方法呼叫(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由於Java final方法語義決定的。基於同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什麼不能持久化final class的原因。
public class SampleClass { public String test(String input){ return "hello world"; } } 下面我們將以這個類作為主要的測試類,來測試呼叫各種方法
@Test public void testFixedValue(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(SampleClass.class); enhancer.setCallback(new FixedValue() { @Override public Object loadObject() throws Exception { return "Hello cglib"; } }); SampleClass proxy = (SampleClass) enhancer.create(); System.out.println(proxy.test(null)); //攔截test,輸出Hello cglib System.out.println(proxy.toString()); System.out.println(proxy.getClass()); System.out.println(proxy.hashCode()); } 程式的輸出為:
Hello cglib Hello cglib class com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
at com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7.hashCode(<generated>) ... 上述程式碼中,FixedValue用來對所有攔截的方法返回相同的值,從輸出我們可以看出來,Enhancer對非final方法test()、toString()、hashCode()進行了攔截,沒有對getClass進行攔截。由於hashCode()方法需要返回一個Number,但是我們返回的是一個String,這解釋了上面的程式中為什麼會丟擲異常。
Enhancer.setSuperclass用來設定父型別,從toString方法可以看出,使用CGLIB生成的類為被代理類的一個子類,形如:SampleClass$$EnhancerByCGLIB$$e3ea9b7
Enhancer.create(Object…)方法是用來建立增強物件的,其提供了很多不同引數的方法用來匹配被增強類的不同構造方法。(雖然類的構造放法只是Java位元組碼層面的函式,但是Enhancer卻不能對其進行操作。Enhancer同樣不能操作static或者final類)。我們也可以先使用Enhancer.createClass()來建立位元組碼(.class),然後用位元組碼動態的生成增強後的物件。
原理上:
Java動態代理使用Java原生的反射API進行操作,在生成類上比較高效;CGLIB使用ASM框架直接對位元組碼進行操作,在類的執行過程中比較高效
使用上:
Java動態代理只能夠對介面進行代理,不能對普通的類進行代理(因為所有生成的代理類的父類為Proxy,Java類繼承機制不允許多重繼承);CGLIB能夠代理普通類,但是不能代理final修改時的類;
java動態代理是利用反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。而cglib動態代理是利用asm開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。
- 如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP 2、如果目標物件實現了介面,可以強制使用CGLIB實現AOP 3、如果目標物件沒有實現了介面,必須採用CGLIB庫,會自動在JDK動態代理和CGLIB之間轉換
借鑑:https://blog.csdn.net/danchu/article/details/70238002?utm_source=copy