Cglib、Javassist、JDK動態代理

1、簡介

Java的動態代理真的很是重要,特別是想要了解一些框架的原理的時候,若是對Java動態代理不熟悉,甚至不瞭解Java動態代理,那基本上就只能說一句「我太難了」。java

好比想要知道MyBatis爲何不用實現方法,只須要一個接口和一個對應的xml文件就能實現數據庫操做。git

Spring中爲何咱們使用一個@Transactional註解就能實現事務自動回滾。github

固然瞭解了Java的動態代理,咱們本身也能夠實現一些無侵入,對用戶透明友好的附加功能。spring

因此想要了解這些原理,就先來了解一下Java動態代理吧。數據庫

2、JDK動態代理

首先咱們來看一些JDK的動態代理怎樣實現。app

要理解JDK的動態代理,只須要理解Proxy類和InvocationHandler接口就能夠了。框架

不過在這以前咱們先介紹2個概念: 目標對象(target):被代理的對象 代理對象(proxy):Proxy動態生成並加載的對象ide

2.1 InvocationHandler

首先咱們來看InvocationHandler,InvocationHandler接口很是簡單就一個方法,以下所示:測試

public Object invoke(Object proxy, Method method, Object[] args)

這個方法就是實現JDK代理邏輯的地方,proxy是代理對象,這個是在Proxy類中建立的。this

Method就是目標對象要執行的方法,args就是調用Method的時候傳入的參數。

咱們在invoke中的邏輯通常是這樣的;

//原方法執行以前的代理邏輯
method.invoke(target, args);//原方法
//原方法執行以後的代理邏輯

咱們能夠看到,基本沒有使用到代理對象proxy,而是使用了目標對象,由於代理部分通常仍是會執行原對象的邏輯。

注意:必定不要在method.invoke方法上使用proxy,而要使用target對象

2.2 Proxy

InvocationHandler實現了,他會在什麼地方被調用呢?

咱們來看一些Proxy,就清楚了。

Proxy關注一個重要的靜態方法:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

上面的方法就是建立一個代理對象proxy方法。

仔細想一下,建立對象得先有類啊,Proxy使用newProxyInstance生成的代理對象是一個什麼類?

這裏直接說結論:Proxy使用newProxyInstance建立對象使用的類是動態生成並建立的

這個類繼承了Proxy類,並實現了newProxyInstance方法的第二個參數傳入的interfaces接口

生成字節碼的是ProxyGenerator.generateProxyClass方法,有興趣能夠跟一下newProxyInstance方法的getProxyClass0方法。

字節碼使用的加載器是newProxyInstance方法的第一個參數傳入的ClassLoader。

那我要這InvocationHandler有何用?別急,咱們看一下生成的代理對象中的方法大概長什麼樣的

public final int insert(User paramUser) throws {
    try{
      return ((Integer)this.h.invoke(this, m3, new Object[] { paramUser })).intValue();
    }
    catch (Error|RuntimeException localError){
      throw localError;
    }
    catch (Throwable localThrowable){
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

上面的this.h就是newProxyInstance建立代理對象傳入的參數InvocationHandler,咱們能夠看到實現interfaces的接口中的方法實際上都是調用InvocationHandler的invoke方法。

m3是Method對象,是經過Class.forName獲取,參數就是interfaces的對應的全限定名稱。

m3 = Class.forName("xxxx.UserMapper").getMethod("insert", new Class[] { Class.forName("xxxx.User") });

這裏就不給具體的例子了,能夠參考一下後面的其它文章,或者看BeanPostProcessor與Spring無侵入擴展,順便還能夠了解一下BeanPostProcessor。

3、cglib

cglib和JDK的動態代理很類似,cglib最讓人想吐槽的地方就是沒有文檔,不過也沒有太大關係,咱們主要看MethodInterceptor這個接口就能夠了。

3.1 代理邏輯

Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy)

先看參數:

  1. proxy:cglib生成的代理對象
  2. method:被代理對象方法,也就是目標對象方法
  3. args:代理方法
  4. methodProxy:代理方法
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class DoSomethingMethodInterceptor implements MethodInterceptor {

    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//        return method.invoke(o,objects);
//        System.out.println(proxy);
        return methodProxy.invokeSuper(proxy,objects);
    }
}

在MethodInterceptor中調用目標對象使用的是在代理對象proxy上調用代理方法methodProxy,使用的也是invokeSuper,而不是invoke,這容易讓人困惑。

用英語老師的話說,下面是固定用法,記住就能夠了:

methodProxy.invokeSuper(proxy,objects);

3.2 建立代理對象

@Test
public void testBusinessDoSomethingMethodInterceptor() {
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\tmp\\code");
    BusinessServiceImpl target = new BusinessServiceImpl();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(target.getClass());
    enhancer.setCallback(new DoSomethingMethodInterceptor());
    BusinessService bs = (BusinessService) enhancer.create();
    System.out.println(bs.doSomething(1, "curitis"));
}

設置系統屬性,是爲了讓cglib把生成的類寫到指定目錄,這樣調試或者分析的時候能夠看一下生成的類的邏輯。

重要的類Enhancer,使用Enhancer分3步走就能夠了:

  1. setSuperclass設置目標類
  2. setCallback設置MethodInterceptor處理代理邏輯
  3. create生成代理類

3.3 小結

  1. JDK動態代理是實現了被代理對象的接口,Cglib是繼承了被代理對象
  2. JDK和Cglib都是在運行期生成字節碼,Cglib使用ASM框架寫Class字節碼
  3. Cglib代理類比JDK效率低
  4. Cglib執行效率比JDK更高
  5. Cglib是沒法代理final修飾的方法的
  6. MethodInterceptor#methodProxy.invokeSuper(proxy,objects)
  7. Enhancer設置代理對象、設置代理邏輯、建立代理對象

4、javassist

javassist的強大之處在於它操做字節碼的能力,能夠動態的修改類,加載類,添加刪除字段、方法等操做。

固然也能夠實現動態代理,這裏咱們就介紹一下經過javassist實現動態代理。

4.1 代理邏輯(MethodHandler)

javassist代理邏輯能夠是在MethodHandler接口中,MethodHandler只有一個方法:

Object invoke(Object self, Method thisMethod, Method proceed,Object[] args)

self:生成的代理類 thisMethod:目標類的方法 proceed:代理類的方法 args:執行方法的參數

javassist的執行邏輯和cglib的很像,是在代理類實例上調用代理方法:

proceed.invoke(self, args)
import javassist.util.proxy.MethodHandler;

import java.lang.reflect.Method;

public class DoSomethingMethodHandler implements MethodHandler {

    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println(self.getClass());
        System.out.println(thisMethod.getName());
        System.out.println(proceed.getName());
        Object result = proceed.invoke(self, args);
        return result;
    }
}

4.2 建立代理類

javassist建立代理類經過ProxyFactory,分4步走:

  1. 建立ProxyFactory實例
  2. 設置父類
  3. 設置代理邏輯處理(MethodHandler)
  4. 建立代理類實例(proxyFactory.createClass().newInstance())
import javassist.util.proxy.ProxyFactory;

public class JavassistProxyFactory<T> {

    private T target;

    public JavassistProxyFactory(T target) {
        this.target = target;
    }

    @SuppressWarnings( {"deprecation","uncheked"})
    public T getProxy() throws InstantiationException, IllegalAccessException {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setSuperclass(target.getClass());
        proxyFactory.setHandler(new DoSomethingMethodHandler());
        return (T) proxyFactory.createClass().newInstance();
    }

    @SuppressWarnings( {"deprecation","uncheked"})
    public static <T> T getProxy(Class<T> clazz) throws InstantiationException, IllegalAccessException {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setSuperclass(clazz);
        proxyFactory.setHandler(new DoSomethingMethodHandler());
        return (T) proxyFactory.createClass().newInstance();
    }
}

4.3 測試

import org.curitis.service.BusinessService;
import org.curitis.service.impl.BusinessServiceImpl;
import org.junit.Test;

public class JavasistTest {

    @Test
    public void test() throws IllegalAccessException, InstantiationException {
        BusinessService proxy = JavassistProxyFactory.getProxy(BusinessServiceImpl.class);
        proxy.doSomething(1,"curitis");
    }

}

5、附錄

5.1 pom

<properties>
    <javassist.version>3.12.1.GA</javassist.version>
    <cglib.version>3.2.5</cglib.version>
</properties>
<dependency>
    <groupId>javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>${javassist.version}</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>${cglib.version}</version>
</dependency>

5.2 測試使用的業務類

public interface BusinessService {
    String doSomething(Integer id,String name);
}
import org.springframework.stereotype.Service;

@Service("businessService")
public class BusinessServiceImpl implements BusinessService{
    @Override
    public String doSomething(Integer id, String name) {
        return id + " " + name;
    }
}

業務類

6、參考

代理模式

使用ProxyGenerator類生成字節碼

Java動態代理細探

javassist

相關文章
相關標籤/搜索