JDK动态代理 VS Cglib动态代理

165 阅读6分钟

前言

提到了动态代理就不得不提到静态代理和动态代理的区别了。动态代理和静态代理是两种常见的代理模式,在软件开发中经常被用来实现对象间的间接访问。它们之间的主要区别在于代理对象的生成时机和方式。

静态代理

静态代理是在编译时就已经确定了代理对象的类型,即在编码阶段就确定了代理对象。在静态代理中,代理类通常要实现与被代理类相同的接口或继承相同的父类,并且在编译期间就已经明确了被代理对象。

优点:

  1. 结构清晰,可读性强。
  2. 对被代理类进行功能增强,符合单一职责原则。

缺点:

  1. 静态代理需要为每个被代理类创建一个代理类,代码重复且不易维护。
  2. 如果被代理类接口或者实现类发生改变,代理类也需要相应修改。

动态代理

动态代理是在运行时生成代理对象,无需事先编写代理类。Java语言提供了 java.lang.reflect 包来实现动态代理,通过 Proxy 类和 InvocationHandler 接口可以动态地创建代理类并处理代理对象的方法调用。

优点:

  1. 不需要为每个被代理类编写单独的代理类,减少了代码量,提高了代码的可维护性。
  2. 可以在运行时动态地增强被代理类的功能,更灵活。

缺点:

  1. 动态代理相对于静态代理的实现机制更加复杂,性能可能稍逊一筹。
  2. 无法代理类中的私有方法。

总结

  • 静态代理和动态代理都是为了实现对象间的间接访问,通过代理对象来控制对目标对象的访问。
  • 静态代理在编译时就已经确定了代理对象,而动态代理是在运行时生成代理对象。
  • 静态代理需要为每个被代理类创建一个代理类,而动态代理可以动态地为多个类创建代理对象。
  • 动态代理相对于静态代理更加灵活,但实现机制相对复杂

JDK动态代理 VS Cglib动态代理

JDK动态代理

JDK动态代理是Java标准库提供的一种动态代理方式,它是基于接口的代理。在使用JDK动态代理时,被代理的类必须实现一个接口,代理对象则是基于这个接口来动态生成的。

工作原理:

  1. 使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来创建动态代理类。
  2. 在运行时生成代理类,该代理类实现了被代理接口,并且通过 InvocationHandler 接口将方法调用分派到实际的处理器上。

优点:

  1. 基于接口,对于没有实现接口的类无法使用。
  2. JDK自带,无需引入额外的依赖。

缺点:

  1. 只能代理实现了接口的类,对于没有实现接口的类无法代理。
  2. 创建代理对象时,需要提供接口,对于没有接口的类无法使用。

代码实现

interface UserManager {
    void addUser(String username);
    void deleteUser(String username);
}

// 实现接口的类
class UserManagerImpl implements UserManager {
    public void addUser(String username) {
        System.out.println("Adding user: " + username);
    }

    public void deleteUser(String username) {
        System.out.println("Deleting user: " + username);
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法执行前进行额外的操作
        System.out.println("增强~方法执行之前: " + method.getName());
        // 调用原始对象的方法
        Object result = method.invoke(target, args);
        // 在方法执行后进行额外的操作
        System.out.println("增强~方法执行之后: " + method.getName());
        return result;
    }
}
//测试类
public class ProxyExample {
    public static void main(String[] args) {
        // 创建目标对象
        UserManager userManager = new UserManagerImpl();
        // 创建 InvocationHandler 实例
        InvocationHandler handler = new MyInvocationHandler(userManager);
        // 使用 Proxy 类的静态方法创建代理对象
        UserManager proxy = (UserManager) Proxy.newProxyInstance(
                userManager.getClass().getClassLoader(),
                userManager.getClass().getInterfaces(),
                handler);

        // 通过代理对象调用方法
        proxy.addUser("Alice");
        proxy.deleteUser("Bob");
    }
}

运行结果

image.png

代码分析: 首先定义了一个接口 UserManager,以及它的实现类 UserManagerImpl。然后,我们定义了一个实现了 InvocationHandler 接口的 MyInvocationHandler 类,用来实现代理逻辑。最后,在 ProxyExample 类中,我们使用 Proxy 类的 newProxyInstance 方法创建了一个代理对象,并通过该代理对象调用了接口中的方法。在代理对象的方法调用前后,会执行额外的逻辑,这是代理的一种典型应用场景。

CGLIB动态代理

CGLIB是一个强大的,高性能的代码生成库,它的主要特点是可以在运行时动态生成字节码,而不需要在编译时就需要接口。CGLIB动态代理可以代理没有实现接口的类。

工作原理:

  1. 使用ASM框架直接操作字节码,生成被代理类的子类作为代理类。
  2. 在子类中覆盖父类的方法,将方法的调用委派给相关的拦截器进行处理。

优点:

  1. 可以代理没有实现接口的类。
  2. 在运行时生成代理类,无需接口,更加灵活。

缺点:

  1. CGLIB动态代理的性能一般情况下略低于JDK动态代理,因为它是通过生成子类的方式实现代理,而JDK动态代理是基于接口的。
  2. 生成的代理类可能会比较复杂,对调试不够友好。

代码实现:

被代理接口
class TargetClass {
    public void doSomething() {
        System.out.println("hello world");
    }
}

// 自定义的方法拦截器,用于在目标方法执行前后添加额外逻辑
class CustomInterceptor implements MethodInterceptor {

    // 实现 intercept 方法,在该方法中编写额外逻辑
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在目标方法执行前添加逻辑
        System.out.println("目标方法执行之前~");

        // 调用目标方法
        Object result = proxy.invokeSuper(obj, args);

        // 在目标方法执行后添加逻辑
        System.out.println("目标方法执行之后~");

        return result;
    }
}

public class CglibProxyExample {
    public static void main(String[] args) {
        // 创建 Enhancer 对象,用于生成代理类
        Enhancer enhancer = new Enhancer();
        // 设置目标类为父类
        enhancer.setSuperclass(TargetClass.class);
        // 设置回调函数为自定义的方法拦截器
        enhancer.setCallback(new CustomInterceptor());

        // 通过 Enhancer 创建代理对象
        TargetClass proxy = (TargetClass) enhancer.create();

        // 调用代理对象的方法
        proxy.doSomething();
    }
}

image.png

代码分析:

  • TargetClass 是被代理的目标类,其中有一个简单的方法 doSomething()
  • CustomInterceptor 是自定义的方法拦截器,实现了 Cglib 的 MethodInterceptor 接口。在其 intercept 方法中,我们可以在目标方法执行前后添加额外的逻辑。
  • CglibProxyExample 是主程序,展示了如何使用 Cglib 动态代理。在主程序中,我们使用 Enhancer 类创建代理对象,并将目标类和自定义的方法拦截器传递给 Enhancer 对象。最后通过 enhancer.create() 方法生成代理对象。
  • 当调用代理对象的 doSomething() 方法时,实际上会调用 CustomInterceptor 中的 intercept 方法,在该方法中添加了额外的逻辑,并最终调用了目标类的 doSomething() 方法。

总结:

  • JDK动态代理基于接口,而CGLIB动态代理基于继承。
  • JDK动态代理适用于需要代理接口的情况,而CGLIB动态代理适用于需要代理没有实现接口的类的情况。
  • JDK动态代理在性能上略优于CGLIB动态代理,但对于特定情况下需要代理类而无法使用接口的情况,CGLIB提供了更灵活的解决方案。