Java動態代理的兩種實現方法


 

那么動態代理是如何實現將切面邏輯(advise)織入到目標類方法中去的呢?下面我們就來詳細介紹並實現AOP中用到的兩種動態代理。

AOP的源碼中用到了兩種動態代理來實現攔截切入功能:jdk動態代理和cglib動態代理。兩種方法同時存在,各有優劣。

jdk動態代理是由java內部的反射機制來實現的,cglib動態代理底層則是借助asm來實現的。

總的來說,反射機制在生成類的過程中比較高效,而asm在生成類之后的相關執行過程中比較高效(可以通過將asm生成的類進行緩存,這樣解決asm生成類過程低效問題)。還有一點必須注意:jdk動態代理的應用前提,必須是目標類基於統一的接口。如果沒有上述前提,jdk動態代理不能應用。由此可以看出,jdk動態代理有一定的局限性,cglib這種第三方類庫實現的動態代理應用更加廣泛,且在效率上更有優勢。。

1、定義接口和實現

1     package com.meituan.hyt.test3.service;
2      
3      
4     public interface UserService {
5     public String getName(int id);
6      
7     public Integer getAge(int id);
8     }

 

 

 1     package com.meituan.hyt.test3.service.impl;
 2      
 3     import com.meituan.hyt.test3.service.UserService;
 4      
 5      
 6     public class UserServiceImpl implements UserService {
 7     @Override
 8     public String getName(int id) {
 9     System.out.println("------getName------");
10     return "Tom";
11     }
12      
13     @Override
14     public Integer getAge(int id) {
15     System.out.println("------getAge------");
16     return 10;
17     }
18     }

 

 

2、jdk動態代理實現

jdk動態代理是jdk原生就支持的一種代理方式,它的實現原理,就是通過讓target類和代理類實現同一接口,代理類持有target對象,來達到方法攔截的作用,這樣通過接口的方式有兩個弊端,一個是必須保證target類有接口,第二個是如果想要對target類的方法進行代理攔截,那么就要保證這些方法都要在接口中聲明,實現上略微有點限制。

 1     package com.meituan.hyt.test3.jdk;
 2      
 3     import java.lang.reflect.InvocationHandler;
 4     import java.lang.reflect.Method;
 5      
 6      
 7     public class MyInvocationHandler implements InvocationHandler {
 8     private Object target;
 9      
10     MyInvocationHandler() {
11     super();
12     }
13      
14     MyInvocationHandler(Object target) {
15     super();
16     this.target = target;
17     }
18      
19     @Override
20     public Object invoke(Object o, Method method, Object[] args) throws Throwable {
21     if("getName".equals(method.getName())){
22     System.out.println("++++++before " + method.getName() + "++++++");
23     Object result = method.invoke(target, args);
24     System.out.println("++++++after " + method.getName() + "++++++");
25     return result;
26     }else{
27     Object result = method.invoke(target, args);
28     return result;
29     }
30      
31     }
32     }

 

 

 1     package com.meituan.hyt.test3.jdk;
 2      
 3     import com.meituan.hyt.test3.service.UserService;
 4     import com.meituan.hyt.test3.service.impl.UserServiceImpl;
 5      
 6     import java.lang.reflect.InvocationHandler;
 7     import java.lang.reflect.Proxy;
 8      
 9      
10     public class Main1 {
11     public static void main(String[] args) {
12     UserService userService = new UserServiceImpl();
13     InvocationHandler invocationHandler = new MyInvocationHandler(userService);
14     UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),
15     userService.getClass().getInterfaces(), invocationHandler);
16     System.out.println(userServiceProxy.getName(1));
17     System.out.println(userServiceProxy.getAge(1));
18     }
19     }

 


運行結果

++++++before getName++++++
------getName------
++++++after getName++++++
Tom
------getAge------
10

3、cglib動態代理實現

Cglib是一個優秀的動態代理框架,它的底層使用ASM在內存中動態的生成被代理類的子類,使用CGLIB即使代理類沒有實現任何接口也可以實現動態代理功能。CGLIB具有簡單易用,它的運行速度要遠遠快於JDK的Proxy動態代理:

cglib有兩種可選方式,繼承和引用。第一種是基於繼承實現的動態代理,所以可以直接通過super調用target方法,但是這種方式在spring中是不支持的,因為這樣的話,這個target對象就不能被spring所管理,所以cglib還是才用類似jdk的方式,通過持有target對象來達到攔截方法的效果。


CGLIB的核心類:
    net.sf.cglib.proxy.Enhancer – 主要的增強類
    net.sf.cglib.proxy.MethodInterceptor – 主要的方法攔截類,它是Callback接口的子接口,需要用戶實現
    net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method類的代理類,可以方便的實現對源對象方法的調用,如使用:
    Object o = methodProxy.invokeSuper(proxy, args);//雖然第一個參數是被代理對象,也不會出現死循環的問題。

net.sf.cglib.proxy.MethodInterceptor接口是最通用的回調(callback)類型,它經常被基於代理的AOP用來實現攔截(intercept)方法的調用。這個接口只定義了一個方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;

第一個參數是代理對像,第二和第三個參數分別是攔截的方法和方法的參數。原來的方法可能通過使用java.lang.reflect.Method對象的一般反射調用,或者使用 net.sf.cglib.proxy.MethodProxy對象調用。net.sf.cglib.proxy.MethodProxy通常被首選使用,因為它更快。

 1     package com.meituan.hyt.test3.cglib;
 2      
 3      
 4     import net.sf.cglib.proxy.MethodInterceptor;
 5     import net.sf.cglib.proxy.MethodProxy;
 6      
 7     import java.lang.reflect.Method;
 8      
 9      
10     public class CglibProxy implements MethodInterceptor {
11     @Override
12     public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
13     System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
14     System.out.println(method.getName());
15     Object o1 = methodProxy.invokeSuper(o, args);
16     System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
17     return o1;
18     }
19     }

 



 1     package com.meituan.hyt.test3.cglib;
 2      
 3     import com.meituan.hyt.test3.service.UserService;
 4     import com.meituan.hyt.test3.service.impl.UserServiceImpl;
 5     import net.sf.cglib.proxy.Enhancer;
 6      
 7      
 8      
 9     public class Main2 {
10     public static void main(String[] args) {
11     CglibProxy cglibProxy = new CglibProxy();
12      
13     Enhancer enhancer = new Enhancer();
14     enhancer.setSuperclass(UserServiceImpl.class);
15     enhancer.setCallback(cglibProxy);
16      
17     UserService o = (UserService)enhancer.create();
18     o.getName(1);
19     o.getAge(1);
20     }
21     }

 


運行結果:

++++++before CGLIB$getName$0++++++
getName
------getName------
++++++before CGLIB$getName$0++++++
++++++before CGLIB$getAge$1++++++
getAge
------getAge------

++++++before CGLIB$getAge$1++++++

 

4、需要注意的問題

需要注意的是,當一個方法沒有被aop事務包裹,在該方法內部去調用另外一個有aop事務包裹的方法時,這個方法的aop事務不會生效。比如:

1     public void register() {
2     aopRegister();
3     }
4      
5     @Transactional
6     public void aopRegister() {
7      
8     }

 

因為通過上面的分析我們知道,在spring中,無論通過jdk的形式還是cglib的形式,代理類對target對象的方法進行攔截,其實都是通過讓代理類持有target對象的引用,當外部引用aop包圍的方法時,調用的其實是代理類對應的方法,代理類持有target對象,便可以控制target方法執行時的全方位攔截。
 
而如果在target的內部方法register調用一個aop包圍的target方法aopRegister,調用的其實就是target自身的方法,因為這時候的this指針是不可能指向代理類的。所以事務是不能生效的。
 
 
原文連接:
 
 https://blog.csdn.net/heyutao007/article/details/49738887


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM