posts - 110, comments - 101, trackbacks - 0, articles - 7
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理
          這篇文章試驗了JDK動態代理與CGLIB動態代理。從Spring的AOP框架介紹中得知對于使用接口的類,Spring使用JDK 動態代理(原來做項目中試圖從Bean強制轉換為實現類,結果報錯,原來是這么回事),沒有接口的就使用別的AOP框架aspectj,但這些都是依賴于 Java字節碼工具ASM生成一個原類的新類,調用Callback

          但是JDK動態代理為什么必須使用接口一直很疑惑,難道原理不是像ASM一樣修改字節碼嗎?帶著這個疑問,開始看JDK的Proxy代碼。使用JDK動態代理的代碼代碼。

          Java代碼 復制代碼 收藏代碼
          1. 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 ,

          通過源碼可以看到,這個類第一步生成一個代理類(注意,這里的參數就是接口列表),

          Java代碼 復制代碼 收藏代碼
          1. Class cl = getProxyClass(loader, interfaces);


          然后通過代理類找到構造參數為InvocationHandler的構造函數并生成一個新類。

          Java代碼 復制代碼 收藏代碼
          1. Constructor cons = cl.getConstructor(constructorParams);//這個有用,在后面細說
          2. return (Object) cons.newInstance(new Object[] { h });


          接口起什么作用呢,于是又看getProxyClass方法的代碼,這個源碼很長,就不細說了。大致分為三段:

          第一:驗證

          第二:緩存創建新類的結構,如果創建過,則直接返回。(注意:這里的KEY就是接口列表)

          第三:如果沒有創建過,則創建新類

          創建代碼如下
          Java代碼 復制代碼 收藏代碼
          1. long num;
          2. //獲得代理類數字標識
          3. synchronized (nextUniqueNumberLock) {
          4. num = nextUniqueNumber++;
          5. }
          6. //獲得創建新類的類名$Proxy,包名為接口包名,但需要注意的是,如果有兩個接口而且不在同一個包下,也會報錯
          7. String proxyName = proxyPkg + proxyClassNamePrefix + num;
          8. //調用class處理文件生成類的字節碼,根據接口列表創建一個新類,這個類為代理類,
          9. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
          10. proxyName, interfaces);
          11. //通過JNI接口,將Class字節碼文件定義一個新類
          12. proxyClass = defineClass0(loader, proxyName,
          13. proxyClassFile, 0, proxyClassFile.length);



          根據前面的代碼Constructor cons = cl.getConstructor(constructorParams);

          可以猜測到接口創建的新類proxyClassFile 不管采用什么接口,都是以下結構

          Java代碼 復制代碼 收藏代碼
          1. public class $Proxy1 extends Proxy implements 傳入的接口{
          2. }


          生成新類的看不到源代碼,不過猜測它的執行原理很有可能是如果類是Proxy的子類,則調用InvocationHandler進行方法的Invoke

          到現在大家都應該明白了吧,JDK動態代理的原理是根據定義好的規則,用傳入的接口創建一個新類,這就是為什么采用動態代理時為什么只能用接口引用指向代理,而不能用傳入的類引用執行動態類。

          cglib采用的是用創建一個繼承實現類的子類,用asm庫動態修改子類的代碼來實現的,所以可以用傳入的類引用執行代理類

          JDK動態代理與CGLIB對比如下:

          //JDK動態代理測試代碼

          Java代碼 復制代碼 收藏代碼
          1. ITestBean tb = new TestBean();
          2. tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(), tb.getClass().getInterfaces(), new TestBeanHander(tb));//這句用接口引用指向,不會報錯
          3. TestBean tmp = (TestBean) tb;//強制轉換為實現類,將拋出類強制轉換異常


          //CGLIB測試代碼
          Java代碼 復制代碼 收藏代碼
          1. TestProxy tp = new TestProxy();
          2. tb = (ITestBean) tp.getProxy(TestBean.class);
          3. tmp = (TeatBean) tb;//強制轉換為實現類,不會拋出異常

          補充說明,如果在實現類中,接口定義的方法互相調用不會在調用InvocationHandler的invoke方法,JDK動態代理應該不是嵌入到Java的反射機制中,而是在反射機制上的一個調用。


          應用舉例如下:

          JDK動態代理的簡單使用示例:


          Java代碼 復制代碼 收藏代碼
          1. package com.proxy;
          2. public class ForumServiceImpl implements ForumService{
          3. public void removeTopic(int topicId){
          4. System.out.println("模擬刪除記錄"+topicId);
          5. try{
          6. Thread.currentThread().sleep(20);
          7. }catch(Exception e){
          8. throw new RuntimeException(e);
          9. }
          10. }
          11. public void removeForum(int forumId){
          12. System.out.println("模擬刪除記錄"+forumId);
          13. try{
          14. Thread.currentThread().sleep(20);
          15. }catch(Exception e){
          16. throw new RuntimeException(e);
          17. }
          18. }
          19. }


          創建一個實現java.lang.reflect.InvocationHandler 接口的代理類,如:
          Java代碼 復制代碼 收藏代碼
          1. import java.lang.reflect.InvocationHandler;
          2. import java.lang.reflect.Method;
          3. public class PerformanceHandler implements InvocationHandler{
          4. private Object target; //要進行代理的業務類的實例
          5. public PerformanceHandler(Object target){
          6. this.target = target;
          7. }
          8. //覆蓋java.lang.reflect.InvocationHandler的方法invoke()進行織入(增強)的操作
          9. //在實際應用中, 這里會引用一個Intercepter類來做處理。 然后Intercepter就可以獨立發展
          10. public Object invoke(Object proxy, Method method, Object[] args)
          11. throws Throwable{
          12. System.out.println("Object target proxy:"+target);
          13. System.out.println("模擬代理加強的方法...");
          14. Object obj = method.invoke(target, args); //調用目標業務類的方法
          15. System.out.println("模擬代理加強的方法執行完畢...");
          16. return obj;
          17. }
          18. }



          用java.lang.reflect.Proxy.newProxyInstance()方法創建動態實例來調用代理實例的方法:
          Java代碼 復制代碼 收藏代碼
          1. import java.lang.reflect.Proxy;
          2. public class TestForumService {
          3. public static void main(String args[]){
          4. ForumService target = new ForumServiceImpl();//要進行代理的目標業務類
          5. PerformanceHandler handler = new PerformanceHandler(target);//用代理類把目標業務類進行編織
          6. //創建代理實例,它可以看作是要代理的目標業務類的加多了橫切代碼(方法)的一個子類
          7. ForumService proxy = (ForumService)Proxy.newProxyInstance(
          8. target.getClass().getClassLoader(),
          9. target.getClass().getInterfaces(), handler);
          10. proxy.removeForum(10);
          11. proxy.removeTopic(20);
          12. }
          13. }



          CGLib動態代理示例:


          創建一個實現net.sf.cglib.proxy.MethodInterceptor接口的實例來為目標業務類加入進行代理時要進行的操作或增強:

          Java代碼 復制代碼 收藏代碼
          1. import java.lang.reflect.Method;
          2. import net.sf.cglib.proxy.MethodProxy;
          3. import net.sf.cglib.proxy.Enhancer;
          4. import net.sf.cglib.proxy.MethodInterceptor;
          5. /**
          6. *CGlib采用非常底層的字節碼技術,可以為一個類創建子類,
          7. 并在子類中采用方法攔截技術攔截父類方法的調用,并順勢進行增強,即是織入橫切邏輯
          8. * @author tufu
          9. */
          10. public class CglibProxy implements MethodInterceptor{
          11. private Enhancer enhancer = new Enhancer();
          12. //覆蓋MethodInterceptor接口的getProxy()方法,設置
          13. public Object getProxy(Class clazz){
          14. enhancer.setSuperclass(clazz); //設者要創建子類的類
          15. enhancer.setCallback(this); //設置回調的對象
          16. return enhancer.create(); //通過字節碼技術動態創建子類實例,
          17. }
          18. public Object intercept(Object obj,Method method,Object[] args,
          19. MethodProxy proxy) throws Throwable {
          20. System.out.println("模擬代理增強方法");
          21. //通過代理類實例調用父類的方法,即是目標業務類方法的調用
          22. Object result = proxy.invokeSuper(obj, args);
          23. System.out.println("模擬代理增強方法結束");
          24. return result;
          25. }
          26. }


          通過java.lang.reflect.Proxy的getProxy()動態生成目標業務類的子類,即是代理類,再由此得到代理實例:
          Java代碼 復制代碼 收藏代碼
          1. import com.proxy.ForumServiceImpl;
          2. import java.lang.reflect.Proxy;
          3. public class TestCglibProxy {
          4. public static void main(String args[]){
          5. CglibProxy proxy = new CglibProxy();
          6. //動態生成子類的方法創建代理類
          7. ForumServiceImpl fsi =
          8. (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);
          9. fsi.removeForum(10);
          10. fsi.removeTopic(2);
          11. }
          12. }


          總結下Spring的AOP運用的設計模式 , AOP 主要利用代理模式, 然后依賴通知(本人認為是策略模式)來實現AOP。 這樣通知就可以獨立發展。

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 平陆县| 阳东县| 绥芬河市| 留坝县| 五华县| 宣汉县| 海兴县| 克山县| 武汉市| 油尖旺区| 宁德市| 鄂尔多斯市| 霍山县| 儋州市| 大英县| 工布江达县| 即墨市| 商南县| 屏山县| 屯留县| 珠海市| 任丘市| 合川市| 天津市| 保康县| 镇康县| 赣州市| 临汾市| 尤溪县| 屯昌县| 双峰县| 扎兰屯市| 旬阳县| 长沙市| 年辖:市辖区| 区。| 华坪县| 祁东县| 永德县| 湖北省| 从江县|