本文主要记录如何使用 jdk 和 cglib 的方式实现动态代理并比较两者的区别。

jdk 动态代理实现

Proxy:Proxy 是所有动态代理的父类,它提供了一个静态方法来创建动态代理的 class 对象和实例;
InvocationHandler:每个动态代理实例都有一个关联的InvocationHandler。 在代理实例上调用方法时,方法调用将被转发到 InvocationHandler 的 invoke 方法;

因为 jdk 动态代理是基于接口的,所以先创建一个普通接口

普通接口:JdkProxyInterface

public interface JdkProxyInterface {

    /**
     * 添加用户ProxyInter
     */
    void addUser();
}

目标类:JdkProxyInterfaceImpl

/**
 * 要被代理的目标类, 该类必须实现接口才能使用 jdk 代理
 */
public class JdkProxyInterfaceImpl implements JdkProxyInterface {

    @Override
    public void addUser() {
        System.out.println("成功添加了一个用户。");
    }
}

增强类:JdkProxyEnhance

package jdk;

/**
 * 要增强的方法都写在增强类中
 */
public class JdkProxyEnhance {

    public void queryUser() {
        System.out.println("添加用户前查询是否有该用户   ------ Jdk代理增强");
    }

    public void addLog() {
        System.out.println("添加用户之后,新增日志记录   ------ Jdk代理增强");
    }
}

代理类:JdkProxyFactory

package jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Jdk 代理类
 */
public class JdkProxyFactory implements InvocationHandler {

    // 目标类
    private Object object;

    // 增强类
    private JdkProxyEnhance jdkProxyEnhance;


    public Object proxy(Object object, JdkProxyEnhance jdkProxyEnhance) {
        this.object = object;
        this.jdkProxyEnhance = jdkProxyEnhance;
        return Proxy.newProxyInstance(
                this.object.getClass().getClassLoader(), 
                this.object.getClass().getInterfaces(), 
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.jdkProxyEnhance.queryUser();
        Object invoke = method.invoke(object, args);
        this.jdkProxyEnhance.addLog();
        return invoke;
    }
}

测试

package jdk;

/**
 * 测试 jdk 代理
 */
public class JdkProxyMain {

    public static void main(String[] args) {
        // 目标类
        JdkProxyInterface object = new JdkProxyInterfaceImpl();
        // 增强类
        JdkProxyEnhance jdkProxyEnhance = new JdkProxyEnhance();
        // 代理工厂
        JdkProxyFactory jdkProxyFactory = new JdkProxyFactory();
        // 进行代理增强
        JdkProxyInterface jdkProxyInterface = (JdkProxyInterface) jdkProxyFactory.proxy(object, jdkProxyEnhance);
        // 调用增强后的方法
        jdkProxyInterface.addUser();
    }
}

输出结果如下:

可以看出,经过增强后的 addUser 方法每次被调用前都会执行一次 queryUser 方法, 调用后都会执行一次 addLog 方法。

cglib 动态代理实现

CGLIB(Code Generation Library) 是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理
Enhancer:指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用 create() 方法得到代理对象,对这个对象所有非 final 方法的调用都会转发给 MethodInterceptor **
**MethodInterceptor:动态代理对象的方法调用都会转发到 intercept 方法进行增强;

jdk 动态代理是 java 原生支持的,不需要任何外部依赖,但是 cglib 需要导入相关依赖才可以实现。

导入依赖( Maven )

<dependencies>
    <!-- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
    <dependency>
      <groupId>org.ow2.asm</groupId>
      <artifactId>asm</artifactId>
      <version>7.1</version>
    </dependency>
</dependencies>

目标类:CglibProxyImpl

package cglib;

/**
 * 要被代理的目标类
 */
public class CglibProxyImpl {

    public void addUser(String s) {
        System.out.println("添加用户成功! " + s);
    }
}

增强类:CglibProxyEnhance

package cglib;

/**
 * 增强类
 * 要增强的方法都写在增强类中
 */
public class CglibProxyEnhance {

    public void queryUser() {
        System.out.println("添加用户前查询是否有该用户   ------ Cglib代理增强");
    }

    public void addLog() {
        System.out.println("添加用户之后,新增日志记录   ------ Cglib代理增强");
    }

}

代理类:CglibProxyFactory

package cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * cglib 代理类
 */
public class CglibProxyFactory implements MethodInterceptor {

    // 目标类
    private Object object;

    // 增强类
    private CglibProxyEnhance cglibProxyEnhance;

    public Object proxy(Object object, CglibProxyEnhance cglibProxyEnhance) {
        this.object = object;
        this.cglibProxyEnhance = cglibProxyEnhance;
        // 创建增强器, 用来创建动态代理类
        Enhancer enhancer= new Enhancer();
        // 为增强加指定要代理的目标类
        enhancer.setSuperclass(this.object.getClass());
        // 设置回调: 代理类上的所有方法都会调用 callback, 而 callback 需要实现 intercept 进行拦截
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 打印方法的第一个参数
        System.out.println(args[0]);
        this.cglibProxyEnhance.queryUser();
        Object invoke = method.invoke(this.object, args);
        this.cglibProxyEnhance.addLog();
        return invoke;
    }
}

测试

package cglib;

/**
 * 测试 Cglib 代理
 */
public class CglibProxyMain {

    public static void main(String[] args) {
        // 目标类
        CglibProxyImpl obj = new CglibProxyImpl();
        // 增强类
        CglibProxyEnhance enhance = new CglibProxyEnhance();
        // Cglib 代理工厂
        CglibProxyFactory cglibProxyFactory = new CglibProxyFactory();
        // 进行代理增强
        CglibProxyImpl proxy = (CglibProxyImpl) cglibProxyFactory.proxy(obj, enhance);
        // 调用增强后的方法
        proxy.addUser("test");
    }

}

输出结果如下:

两者的区别

jdk 动态代理实现原理及特点

  • 基于反射机制, 生成一个实现代理接口的匿名类, 然后重写方法, 实现方法的增强。
  • 它生成类的速度很快, 但是运行时因为是基于反射, 调用后续的类操作会很慢。
  • 只能针对接口编程。如果目标类没有实现接口无法使用 jdk 代理。

cglib 动态代理实现原理及特点

  • cglib 是基于继承机制, 继承被代理类, 所以方法不要声明为 final, 然后重写父类方法达到增强类的作用。
  • 底层是基于 asm 第三方框架, 是将代理对象类的 class 文件加载进来, 通过修改其字节码生成子类来处理。
  • 生成类的速度慢, 但是后续执行类的操作时候很快。
  • 可以针对类和接口。

二者区别

  • 因为 jdk 是基于反射, cglib 是基于字节码, 所以性能上会有差异。
  • 老版本 cglib 的速度是 jdk 速度的 10 倍左右, 但是 cglib 启动类比 jdk 慢 8 倍左右, 但是实际上 jdk 的速度在版本升级的时候每次都提高很多性能, 而 cglib 仍止步不前。
  • 在对 jdk 动态代理与 cglib 动态代理的代码实验中看, 1W 次执行下, jdk 7、8 的动态代理性能比 cglib 要好 20% 左右。

具体应用:

  • 如果目标对象实现了接口, 默认情况下是采用 jdk 动态实现 AOP。
  • 如果目标对象没有实现接口, 必须采用 cglib 库。
  • Spring 5.x 中 AOP 默认依旧使用 jdk 动态代理。
  • SpringBoot 2.x 开始,为了解决使用 jdk 动态代理可能导致的类型转化异常而默认使用 cglib。
  • 在 SpringBoot 2.x 中,如果需要默认使用 jdk 动态代理可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,proxyTargetClass 配置已无效。

参考文章:

JDK 和 Cglib 动态代理的实现和区别

JDK动态代理和CGLIB动态代理的区别以及反射


前天遇到了小鹿,昨天是小兔子,今天是你。