欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cglib、Javassist、JDK动态代理

程序员文章站 2022-06-17 19:54:44
...

一、简介

Java的动态代理真的非常重要,特别是想要了解一些框架的原理的时候,如果对Java动态代理不熟悉,甚至不了解Java动态代理,那基本上就只能说一句“我太难了”。

比如想要知道MyBatis为什么不用实现方法,只需要一个接口和一个对应的xml文件就能实现数据库操作。

Spring中为什么我们使用一个@Transactional注解就能实现事务自动回滚。

当然了解了Java的动态代理,我们自己也可以实现一些无侵入,对用户透明友好的附加功能。

所以想要了解这些原理,就先来了解一下Java动态代理吧。

二、JDK动态代理

首先我们来看一些JDK的动态代理怎样实现。

要理解JDK的动态代理,只需要理解Proxy类和InvocationHandler接口就可以了。

不过在这之前我们先介绍2个概念:
目标对象(target):被代理的对象
代理对象(proxy):Proxy动态生成并加载的对象

2.1 InvocationHandler

首先我们来看InvocationHandler,InvocationHandler接口非常简单就一个方法,如下所示:

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

这个方法就是实现JDK代理逻辑的地方,proxy是代理对象,这个是在Proxy类中创建的。

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。

三、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设置代理对象、设置代理逻辑、创建代理对象

四、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.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;
    }
}

Cglib、Javassist、JDK动态代理

六、参考

代理模式

使用ProxyGenerator类生成字节码

Java动态代理细探

javassist