天天看點

java動态代理詳解

java動态代理詳解

摘要

本文動态代理得意義、主要介紹動态代理得實作原理以及由動态代理引申出來的一些知識點。

插曲

最近在研究javamelody實作的原理,發現他對JDBC的監控就是通過動态代理實作的。由于之前對于動态代理隻是大概知道怎麼回事,沒有細緻的去研究,是以上網百度了一下。發現網上的東西要麼注重原理而忽略應用場景導緻空泛、要麼注重場景而忽略原理、要麼就是隻有基于接口的動态代理而沒有基于cglib的。是以這裡本文盡量做到大而全。其實想總結一下的原因是公司進行代碼review的時候,老大提出同一個類中一個方法調用本類其他方法,其他方法的事務不會生效,本質上我是持懷疑态度的。當時我是出于基于Cglib代理的角度考慮,而實際不會生效是基于動态代理的方式,采用cglib還是會生效,後面會講到。本人作文比較推崇簡約易懂的方式,盡量避免過于斯文的名詞出現。

一、動态代理的意義

首先明白一點,動态代理就是用來生成代理對象的。我們知道傳統的代理模式,通常是先定義一個代理類,該代理類需要持有目标對象(也有叫被代理對象,我覺得都行吧)。假設我們有1000個不同的目标對象(這1000個對象不是同一個類),那麼我們需要預先定義1000個代理類,這是我們不能容忍的。于是乎,動态代理就出現了,它本質上是生成一個外表上和目标對象一樣的代理對象,然後當我們調用代理對象的方法的時候,實際上它在他的方法裡面去調用了目标對象對應的同名方法。

二、動态代理設計的核心思想

其實不要把這些設計想得多麼高尚,假如我是動态代理設計的作者,由動态代理的意義部分我們知道,我們就是要想盡一切辦法,通過目标對象生成代理對象,然後讓代理對象的方法調用作用到目标對象的方法調用。沒錯動态代理的核心思想就是這麼簡單。比如目标類為Person,Person有一個方法叫做purchase(),此方法用于購物。我們期望purchase()方法有代理類去做處理,比如在購物前記錄下購買了哪些東西。我們知道在使用一個類之前,是需要建立一個對象的,我們就在建立的地方動手腳。是以你看到了JDK動态Proxy.newInstance()的方式,也領略過Spring的Enhancer.create()。個人比較喜歡cglib的優雅、幹淨、利落。吐槽一下JDK的InvocationHandler像極了惡心的中間商。下面是JDK動态代理UML示意圖

三、JDK動态代理

1,原理

在了解動态代理之前,我們需要了解Java位元組碼。如果不熟悉Java位元組碼,你可以了解為通過代碼動态生成一個.java檔案,然後将其編譯為class檔案加載到記憶體中。接下來JDK中的動态代理要做的事情就是怎麼去生成一個ProxyPerson位元組碼檔案。其實它就是在生成位元組碼的時候,持有了InvocationHandler對象,然後去實作了ProxyPerson對應的接口。在該接口的所有實作方法中,隻做了一件事情就是調用invocationHandler.invoke()方法。從代碼層面來看如下所示:

複制代碼

public class ProxyPerson implements Purchase{

static{

Method method;// 接口的方法

Object[] args;// 接口參數

}

InvocationHandler handler;

public ProxyPerson(InvocationHandler handler){

this.handler = handler;           

@overrde

public purchage(){

this,handler.invoke(this,method,args);

}

那麼上面這段代碼是在什麼時候生成的呢?

Proxy.newProxyInstance()

在我們調用JDK上面的這個方法的時候,底層就會去生成一個ProxyPerson位元組碼。知道了原理我們來解答一下JDK動态代理為何隻能基于接口代理而不能基于類呢?

1),受限于位元組碼的生成方式,JDK本身就是基于InvocationHandler去做的代理中轉。我們看到代理對象的方法調用于目标對象的調用沒有半毛球關系,調用目标對象是我們自己在invoke方法裡面完成的。

2),受限于同名的方法隻能被向上轉型成功的對象調用。比如有兩個類Boy與Girl,他們都實作了接口Purchase,如果我們先擷取到Girl的purchase()方法method,我們通過method.invoke(new Boy())這樣必定會報錯。但是如果我們擷取到Purchase接口purchase()方法method,我們通過method.invoke(new Boy())這樣是ok的,因為new Boy()可以向上轉型為Purchase。

2,應用

比如無論是傳統的MVC模型還是DDD模型,都離不開Service。我們知道Service方法使用@Transactional是可以開啟事務控制的。那麼這種注解式事務是如何實作的呢? 其實在工程啟動的時候,我們就會有一個Bean的後置處理器去檢查所有Bean一旦發現Bean的方法上有事務注解,他就通過Proxy.newInstance()去建立一個代理對象,将代理對象進行傳回注入,而抛棄原本應該注入到容器的對象。是以我們看起來通過容器拿到的Service其實已經是代理對象了。在調用目标對象前,開啟程式設計式事務即可。

四、cglib動态代理

有了上面的知識,我們要有對于cglib而言隻是在生成位元組碼上面動手腳的覺悟。下面直覺感受與一下生成過程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

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

Enhancer enhancer = new Enhancer();
   enhancer.setSuperclass(Boy.class);
   enhancer.setCallback(new MethodInterceptor(){

       @Override
       public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
           System.out.println("proxy method "+ method.getName());
           if(method.getAnnotation(Transactional.class)!=null){
               System.out.println(method.getName()+"發現注解");
           }
           return methodProxy.invokeSuper(o,args);
       }
   });
   Boy proxy = (Boy) enhancer.create();
   proxy.test();
           

public static class Boy{

public void run(){
       System.out.println("run...");
   }
   @Transactional
   public void walk(){
       System.out.println("walk...");
   }
   @Transactional
   public void test(){
       System.out.println("test...");
       walk();
   }           

可以看到cglib是基于繼承的方式進行位元組碼動态生成。它在子類的實作中,隻是調用了注入的methodIntercptor.interceptor()方法。具體位元組碼實作細節,這裡不在深究。我們在這裡探讨一下,為什麼cglib可以使同一個service方法中的其他帶有事務注解的事務生效?因為基于繼承的動态代理,本質發起上調用的代理對象可以向上轉型為原本的目标對象,是以它可以直接通過代理對象去調目标對象方法。

原文位址

https://www.cnblogs.com/enjoyall/p/11324671.html