詳細解讀JAVA動態代理

引言

Java 動態代理機制的出現,使得 Java 開發人員不用手工編寫代理類,只要簡單地指定一組接口及委託類對象,便能動態地獲得代理類。代理類會負責將所有的方法調用分派到委託對象上反射執行,在分派執行的過程中,開發人員還可以按需調整委託類對象及其功能,這是一套非常靈活有彈性的代理框架。通過閱讀本文,讀者將會對 Java 動態代理機制有更加深入的理解。本文首先從 Java 動態代理的運行機制和特點出發,對其代碼進行了分析,推演了動態生成類的內部實現。

代理:設計模式

代理模式是Java常見的設計模式之一。所謂代理模式是指客戶端並不直接調用實際的對象,而是通過調用代理,來間接的調用實際的對象。

為什麼要採用這種間接的形式來調用對象呢?一般是因為客戶端不想直接訪問實際的對象,或者訪問實際的對象存在困難,因此通過一個代理對象來完成間接的訪問。

在現實生活中,這種情形非常的常見,比如請一個律師代理來打官司。

詳細解讀JAVA動態代理

從UML圖中,可以看出代理類與真正實現的類都是繼承了抽象的主題類,這樣的好處在於代理類可以與實際的類有相同的方法,可以保證客戶端使用的透明性。

代理類實現:靜態代理

代理模式的實現方式有兩種,分別是靜態代理和動態代理,我們先看靜態代理的實現;

針對上圖中的UML圖,我們看下Subject 接口的code:

public interface Subject {

void visit();

}

RealSubject 的實現:

1public class RealSubject implements Subject{

2 @Override

3 public void visit() {

4 System.out.println("real visit....");

5 }

6}

ProxySubject 代理類的實現:

1public class ProxySubject implements Subject{

2

3 private Subject subject;

4

5 public ProxySubject(Subject subject){

6 this.subject = subject;

7 }

8

9 @Override

10 public void visit() {

11 System.out.println("before......");

12 subject.visit();

13 System.out.println("end.........");

14 }

15}

Client的實現:

1public class Client {

2 public static void main(String[] args) {

3 Subject realSubject = new RealSubject();

4 ProxySubject proxySubject = new ProxySubject(realSubject);

5 proxySubject.visit();

6 }

7}

執行結果如下:

1before......

2real visit....

3end.........

為了保持行為的一致性,代理類和委託類通常會實現相同的接口,所以在訪問者看來兩者沒有絲毫的區別。通過代理類這中間一層,能有效控制對委託類對象的直接訪問,也可以很好地隱藏和保護委託類對象,同時也為實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。靜態代理的缺點也很明顯,如果需要代理若干Subject,那麼就需要implement若干Subject接口方法,而Java 動態代理機制以巧妙的方式近乎完美地實踐了代理模式的設計理念。

代理實現:JAVA動態代理

動態代理有別於靜態代理,是根據代理的對象,動態創建代理類。這樣,就可以避免靜態代理中代理類接口過多的問題。動態代理是實現方式,是通過反射來實現的,藉助Java自帶的java.lang.reflect.Proxy,通過固定的規則生成。

其步驟如下:

  1. 編寫一個委託類的接口,即靜態代理的(Subject接口)
  2. 實現一個真正的委託類,即靜態代理的(RealSubject類)
  3. 創建一個動態代理類,實現InvocationHandler接口,並重寫該invoke方法
  4. 在測試類中,生成動態代理的對象。
  5. 第一二步驟,和靜態代理一樣。我們主要看第三步,代碼如下:

1public class JavaProxy implements InvocationHandler {

2

3 private Object object;

4

5 public JavaProxy(Object object) {

6 this.object = object;

7 }

8

9 @Override

10 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

11 System.out.println("before >>>>>>>>");

12 Object result = method.invoke(object, args);

13 System.out.println("end >>>>>>>>>>>");

14 return result;

15 }

16}

第四步,創建動態代理的對象

1 Subject realSubject = new RealSubject();

2 JavaProxy proxy = new JavaProxy(realSubject);

3 ClassLoader classLoader = realSubject.getClass().getClassLoader();

4 Subject subject = (Subject) Proxy.newProxyInstance(classLoader,new Class[]{Subject.class},proxy);

5 subject.visit();

運行結果如下:

1before >>>>>>>>

2real visit....

3end >>>>>>>>>>>

創建動態代理的對象,需要藉助Proxy.newProxyInstance。該方法的三個參數分別是:

  1. ClassLoader loader表示當前使用到的appClassloader。
  2. Class[] interfaces表示目標對象實現的一組接口。
  3. InvocationHandler h表示當前的InvocationHandler實現實例對象。

java 自帶的動態代理為什麼一定要實現接口

java自帶的動態代理為什麼一定要實現接口,其實這個問題也一直困擾著我,我們從創建代理函數看起,即

1public static Object newProxyInstance(ClassLoader loader,

2 Class>[] interfaces, InvocationHandler h)

3 throws IllegalArgumentException

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

1Class cl = getProxyClass(loader, interfaces);

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

1 final Constructor> cons = cl.getConstructor(constructorParams);

2 final InvocationHandler ih = h;

3if (!Modifier.isPublic(cl.getModifiers())) {

4 AccessController.doPrivileged(new PrivilegedAction() {

5 public Void run() {

6 cons.setAccessible(true);

7 return null;

8 }

9 });

10 }

11 return cons.newInstance(new Object[]{ h });

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

第一:驗證

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

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

創建代碼的過程代碼如下:

1 /*

2 * //獲得代理類數字標識

3 */

4 long num = nextUniqueNumber.getAndIncrement();

5 String proxyName = proxyPkg + proxyClassNamePrefix + num;

6

7 /*

8 * 調用class處理文件生成類的字節碼,根據接口列表創建一個新類,這個類為代理類,

9

10 */

11 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(

12 proxyName, interfaces, accessFlags);

13 try {

14//通過JNI接口,將Class字節碼文件定義一個新類

15 return defineClass0(loader, proxyName,

16 proxyClassFile, 0, proxyClassFile.length);

17 } catch (ClassFormatError e) {

18 /*

19 * A ClassFormatError here means that (barring bugs in the

20 * proxy class generation code) there was some other

21 * invalid aspect of the arguments supplied to the proxy

22 * class creation (such as virtual machine limitations

23 * exceeded).

24 */

25 throw new IllegalArgumentException(e.toString());

26 }

27 }

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

1public class $Proxy1 extends Proxy implements 傳入的接口{ }

生成新類的看不到源代碼,不過猜測它的執行原理很有可能是如果類是Proxy的子類,則調用InvocationHandler進行方法的Invoke. 因為新生成的類已經extends 了Proxy,而且JAVA不能多繼承,因此接口是最好的實現方式了。

JDK動態代理的原理是根據定義好的規則,用傳入的接口創建一個新類,這就是為什麼採用動態代理時為什麼只能用接口引用指向代理,而不能用傳入的類引用執行動態類。

動態代理實現:Cglib

cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。

1、首先定義業務類,無需實現接口(當然,實現接口也可以,不影響的)

1public class MyBook {

2

3 void addBook(){

4 System.out.println("add book...");

5 }

6}

2、實現 MethodInterceptor方法代理接口,創建代理類

1public class MyCglibProxy implements MethodInterceptor {

2

3 @Override

4 public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

5

6 System.out.println("before——————");

7 Object result = methodProxy.invokeSuper(o, args); //調用業務類(父類中)的方法

8 System.out.println("end——————");

9

10 return result;

11 }

12}

3、創建業務類和代理類對象

1 MyBook myBook = new MyBook();

2

3 Enhancer enhancer = new Enhancer();

4 enhancer.setSuperclass(myBook.getClass());

5 //設置回調:對於代理類上所有方法的調用,都會調用CallBack,而Callback則需要實現intercept()方法進行攔

6 MyCglibProxy myCglibProxy = new MyCglibProxy();

7 enhancer.setCallback(myCglibProxy);

8 // 創建動態代理類對象並返回

9 MyBook book = (MyBook) enhancer.create();

10

11 book.addBook();

執行結果如下:

1before——————

2add book...

3end——————

總結

1、靜態代理是通過在代碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,用戶通過代理類調用被包裝過的業務方法;

2、JDK動態代理是通過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;

3、CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;


分享到:


相關文章: