這篇文章試驗了JDK動態代理與CGLIB動態代理。從Spring的AOP框架介紹中得知對于使用接口的類,Spring使用JDK 動態代理(原來做項目中試圖從Bean強制轉換為實現類,結果報錯,原來是這么回事),沒有接口的就使用別的AOP框架aspectj,但這些都是依賴于 Java字節碼工具ASM生成一個原類的新類,調用Callback
但是JDK動態代理為什么必須使用接口一直很疑惑,難道原理不是像ASM一樣修改字節碼嗎?帶著這個疑問,開始看JDK的Proxy代碼。使用JDK動態代理的代碼代碼。
于是從創建代理函數看起,即public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException ,
通過源碼可以看到,這個類第一步生成一個代理類(注意,這里的參數就是接口列表),
然后通過代理類找到構造參數為InvocationHandler的構造函數并生成一個新類。
接口起什么作用呢,于是又看getProxyClass方法的代碼,這個源碼很長,就不細說了。大致分為三段:
第一:驗證
第二:緩存創建新類的結構,如果創建過,則直接返回。(注意:這里的KEY就是接口列表)
第三:如果沒有創建過,則創建新類
創建代碼如下
根據前面的代碼Constructor cons = cl.getConstructor(constructorParams);
可以猜測到接口創建的新類proxyClassFile 不管采用什么接口,都是以下結構
生成新類的看不到源代碼,不過猜測它的執行原理很有可能是如果類是Proxy的子類,則調用InvocationHandler進行方法的Invoke
到現在大家都應該明白了吧,JDK動態代理的原理是根據定義好的規則,用傳入的接口創建一個新類,這就是為什么采用動態代理時為什么只能用接口引用指向代理,而不能用傳入的類引用執行動態類。
cglib采用的是用創建一個繼承實現類的子類,用asm庫動態修改子類的代碼來實現的,所以可以用傳入的類引用執行代理類
JDK動態代理與CGLIB對比如下:
//JDK動態代理測試代碼
//CGLIB測試代碼
補充說明,如果在實現類中,接口定義的方法互相調用不會在調用InvocationHandler的invoke方法,JDK動態代理應該不是嵌入到Java的反射機制中,而是在反射機制上的一個調用。
應用舉例如下:
JDK動態代理的簡單使用示例:
創建一個實現java.lang.reflect.InvocationHandler 接口的代理類,如:
用java.lang.reflect.Proxy.newProxyInstance()方法創建動態實例來調用代理實例的方法:
CGLib動態代理示例:
創建一個實現net.sf.cglib.proxy.MethodInterceptor接口的實例來為目標業務類加入進行代理時要進行的操作或增強:
通過java.lang.reflect.Proxy的getProxy()動態生成目標業務類的子類,即是代理類,再由此得到代理實例:
總結下Spring的AOP運用的設計模式 , AOP 主要利用代理模式, 然后依賴通知(本人認為是策略模式)來實現AOP。 這樣通知就可以獨立發展。
但是JDK動態代理為什么必須使用接口一直很疑惑,難道原理不是像ASM一樣修改字節碼嗎?帶著這個疑問,開始看JDK的Proxy代碼。使用JDK動態代理的代碼代碼。
- ITestBean tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(), tb.getClass().getInterfaces(), new TestBeanHander(tb));
于是從創建代理函數看起,即public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException ,
通過源碼可以看到,這個類第一步生成一個代理類(注意,這里的參數就是接口列表),
- Class cl = getProxyClass(loader, interfaces);
然后通過代理類找到構造參數為InvocationHandler的構造函數并生成一個新類。
- Constructor cons = cl.getConstructor(constructorParams);//這個有用,在后面細說
- return (Object) cons.newInstance(new Object[] { h });
接口起什么作用呢,于是又看getProxyClass方法的代碼,這個源碼很長,就不細說了。大致分為三段:
第一:驗證
第二:緩存創建新類的結構,如果創建過,則直接返回。(注意:這里的KEY就是接口列表)
第三:如果沒有創建過,則創建新類
創建代碼如下
- long num;
- //獲得代理類數字標識
- synchronized (nextUniqueNumberLock) {
- num = nextUniqueNumber++;
- }
- //獲得創建新類的類名$Proxy,包名為接口包名,但需要注意的是,如果有兩個接口而且不在同一個包下,也會報錯
- String proxyName = proxyPkg + proxyClassNamePrefix + num;
- //調用class處理文件生成類的字節碼,根據接口列表創建一個新類,這個類為代理類,
- byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
- proxyName, interfaces);
- //通過JNI接口,將Class字節碼文件定義一個新類
- proxyClass = defineClass0(loader, proxyName,
- proxyClassFile, 0, proxyClassFile.length);
根據前面的代碼Constructor cons = cl.getConstructor(constructorParams);
可以猜測到接口創建的新類proxyClassFile 不管采用什么接口,都是以下結構
- public class $Proxy1 extends Proxy implements 傳入的接口{
- }
生成新類的看不到源代碼,不過猜測它的執行原理很有可能是如果類是Proxy的子類,則調用InvocationHandler進行方法的Invoke
到現在大家都應該明白了吧,JDK動態代理的原理是根據定義好的規則,用傳入的接口創建一個新類,這就是為什么采用動態代理時為什么只能用接口引用指向代理,而不能用傳入的類引用執行動態類。
cglib采用的是用創建一個繼承實現類的子類,用asm庫動態修改子類的代碼來實現的,所以可以用傳入的類引用執行代理類
JDK動態代理與CGLIB對比如下:
//JDK動態代理測試代碼
- ITestBean tb = new TestBean();
- tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(), tb.getClass().getInterfaces(), new TestBeanHander(tb));//這句用接口引用指向,不會報錯
- TestBean tmp = (TestBean) tb;//強制轉換為實現類,將拋出類強制轉換異常
//CGLIB測試代碼
- TestProxy tp = new TestProxy();
- tb = (ITestBean) tp.getProxy(TestBean.class);
- tmp = (TeatBean) tb;//強制轉換為實現類,不會拋出異常
補充說明,如果在實現類中,接口定義的方法互相調用不會在調用InvocationHandler的invoke方法,JDK動態代理應該不是嵌入到Java的反射機制中,而是在反射機制上的一個調用。
應用舉例如下:
JDK動態代理的簡單使用示例:
- package com.proxy;
- public class ForumServiceImpl implements ForumService{
- public void removeTopic(int topicId){
- System.out.println("模擬刪除記錄"+topicId);
- try{
- Thread.currentThread().sleep(20);
- }catch(Exception e){
- throw new RuntimeException(e);
- }
- }
- public void removeForum(int forumId){
- System.out.println("模擬刪除記錄"+forumId);
- try{
- Thread.currentThread().sleep(20);
- }catch(Exception e){
- throw new RuntimeException(e);
- }
- }
- }
創建一個實現java.lang.reflect.InvocationHandler 接口的代理類,如:
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- public class PerformanceHandler implements InvocationHandler{
- private Object target; //要進行代理的業務類的實例
- public PerformanceHandler(Object target){
- this.target = target;
- }
- //覆蓋java.lang.reflect.InvocationHandler的方法invoke()進行織入(增強)的操作
- //在實際應用中, 這里會引用一個Intercepter類來做處理。 然后Intercepter就可以獨立發展
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable{
- System.out.println("Object target proxy:"+target);
- System.out.println("模擬代理加強的方法...");
- Object obj = method.invoke(target, args); //調用目標業務類的方法
- System.out.println("模擬代理加強的方法執行完畢...");
- return obj;
- }
- }
用java.lang.reflect.Proxy.newProxyInstance()方法創建動態實例來調用代理實例的方法:
- import java.lang.reflect.Proxy;
- public class TestForumService {
- public static void main(String args[]){
- ForumService target = new ForumServiceImpl();//要進行代理的目標業務類
- PerformanceHandler handler = new PerformanceHandler(target);//用代理類把目標業務類進行編織
- //創建代理實例,它可以看作是要代理的目標業務類的加多了橫切代碼(方法)的一個子類
- ForumService proxy = (ForumService)Proxy.newProxyInstance(
- target.getClass().getClassLoader(),
- target.getClass().getInterfaces(), handler);
- proxy.removeForum(10);
- proxy.removeTopic(20);
- }
- }
CGLib動態代理示例:
創建一個實現net.sf.cglib.proxy.MethodInterceptor接口的實例來為目標業務類加入進行代理時要進行的操作或增強:
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.MethodProxy;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- /**
- *CGlib采用非常底層的字節碼技術,可以為一個類創建子類,
- 并在子類中采用方法攔截技術攔截父類方法的調用,并順勢進行增強,即是織入橫切邏輯
- * @author tufu
- */
- public class CglibProxy implements MethodInterceptor{
- private Enhancer enhancer = new Enhancer();
- //覆蓋MethodInterceptor接口的getProxy()方法,設置
- public Object getProxy(Class clazz){
- enhancer.setSuperclass(clazz); //設者要創建子類的類
- enhancer.setCallback(this); //設置回調的對象
- return enhancer.create(); //通過字節碼技術動態創建子類實例,
- }
- 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;
- }
- }
通過java.lang.reflect.Proxy的getProxy()動態生成目標業務類的子類,即是代理類,再由此得到代理實例:
- import com.proxy.ForumServiceImpl;
- import java.lang.reflect.Proxy;
- public class TestCglibProxy {
- public static void main(String args[]){
- CglibProxy proxy = new CglibProxy();
- //動態生成子類的方法創建代理類
- ForumServiceImpl fsi =
- (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);
- fsi.removeForum(10);
- fsi.removeTopic(2);
- }
- }
總結下Spring的AOP運用的設計模式 , AOP 主要利用代理模式, 然后依賴通知(本人認為是策略模式)來實現AOP。 這樣通知就可以獨立發展。