JDK与CGLIB动态代理

195 阅读6分钟

JDK动态代理

1.什么是动态代理?

代理模式可以分为静态代理和动态代理。

  • 静态代理:代理类是在编译时就生成了实际的 class 文件。每个目标类都需要一个对应的代理类。如果接口增加方法,目标类和代理类都要修改,比较麻烦,不够灵活。
  • 动态代理:代理类是在程序运行时动态生成的,而不是预先写好的。我们不需要为每个目标类都手动编写一个代理类。它更加灵活,尤其适用于需要为很多类或接口统一添加某些功能(如日志、事务、权限控制等)的场景。
JDK静态代理

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

下面通过代码展示!

1.定义发送短信的接口

public interface SmsService {
    String send(String message);
}

2.实现发送短信的接口

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3.创建代理类并同样实现发送短信的接口

public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}

4.实际使用

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

运行上述代码之后,控制台打印出:

before method send()
send message:java
after method send()

可以输出结果看出,我们已经增加了 SmsServiceImplsend()方法。

JDK动态代理

JDK 动态代理是 Java 标准库(java.lang.reflect包)提供的一种实现动态代理的方式。它的核心特点和要求是:

  1. 基于接口
    • JDK 动态代理必须要求目标对象(被代理的对象)实现了一个或多个接口。它生成的代理类也会实现这些接口。它无法直接代理没有实现接口的类。
  2. 核心组件
    • java.lang.reflect.Proxy 类:这是创建动态代理实例的主要工厂类。最重要的方法是 Proxy.newProxyInstance()
    • java.lang.reflect.InvocationHandler 接口:这是一个调用处理器接口。所有对代理对象的方法调用,都会被转发到 InvocationHandler 实现类的 invoke() 方法中。你需要自己实现这个接口,并在 invoke 方法里定义你希望代理对象执行的逻辑(比如在调用目标方法前后添加一些操作)。

如何使用 JDK 动态代理?

主要步骤如下:

  1. 定义接口:创建一个或多个接口,定义需要被代理的方法。

    Java

    // 1. 定义一个接口
    public interface UserService {
        void addUser(String username);
        String findUser(int userId);
    }
    
  2. 创建目标对象(被代理对象):实现这些接口。

    Java

    // 2. 创建目标对象类,实现接口
    public class UserServiceImpl implements UserService {
        @Override
        public void addUser(String username) {
            System.out.println("--- 正在执行数据库操作:添加用户 " + username + " ---");
        }
    
        @Override
        public String findUser(int userId) {
            System.out.println("--- 正在执行数据库操作:查找用户 ID " + userId + " ---");
            return "用户" + userId;
        }
    }
    
  3. 创建 InvocationHandler 实现类:这是代理逻辑的核心。

    • 它通常持有一个目标对象的引用。

    • 实现

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

      方法。

      • proxy: 生成的代理对象实例(一般很少直接使用它)。
      • method: 当前被调用的方法(例如 addUserfindUserMethod 对象)。
      • args: 调用该方法时传递的参数。
    • invoke
      

      方法内部:

      • 你可以在调用目标方法之前执行一些操作(例如打印日志、权限检查)。
      • 通过反射调用目标对象的实际方法:method.invoke(targetObject, args)
      • 你可以在调用目标方法之后执行一些操作(例如记录结束日志、处理返回值、提交事务)。
      • 返回目标方法执行的结果。

    Java

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    // 3. 创建 InvocationHandler 实现类
    public class MyInvocationHandler implements InvocationHandler {
    
        private Object target; // 目标对象(被代理的对象)
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        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() + "] 执行完毕...");
            System.out.println("<<< 【代理】返回结果:" + result); // 注意:如果方法是 void,这里 result 是 null
    
            return result; // 返回目标方法的执行结果
        }
    }
    
  4. 使用 Proxy.newProxyInstance() 创建代理对象

    • loader: 目标对象的类加载器 (target.getClass().getClassLoader())。
    • interfaces: 目标对象实现的接口数组 (target.getClass().getInterfaces())。
    • h: 你创建的 InvocationHandler 实例。

    Java

    import java.lang.reflect.Proxy;
    
    public class ProxyDemo {
        public static void main(String[] args) {
            // 1. 创建目标对象
            UserService target = new UserServiceImpl();
    
            // 2. 创建 InvocationHandler
            InvocationHandler handler = new MyInvocationHandler(target);
    
            // 3. 使用 Proxy.newProxyInstance 创建代理对象
            //    注意:需要强制类型转换为接口类型
            UserService proxyUserService = (UserService) Proxy.newProxyInstance(
                    UserService.class.getClassLoader(),      // 接口的类加载器
                    new Class<?>[]{UserService.class},       //接口信息 手动构造数组替换target.getClass().getInterfaces()
                    handler                                  // InvocationHandler 实例
            );
    
            // 4. 通过代理对象调用方法
            System.out.println("----- 调用 addUser -----");
            proxyUserService.addUser("Alice");
    
            System.out.println("\n----- 调用 findUser -----");
            String user = proxyUserService.findUser(101);
            System.out.println("主程序收到结果: " + user);
    
            // 检查代理对象的类型信息
            System.out.println("\n代理对象的类名: " + proxyUserService.getClass().getName()); // 通常是 com.sun.proxy.$Proxy0
            System.out.println("代理对象是否是 UserService 的实例: " + (proxyUserService instanceof UserService)); // true
        }
    }
    

    以上为什么要手动构造数组

    1. 接口的getInterfaces()可能返回空数组 当目标接口本身没有父接口时,IPerson.class.getInterfaces()返回空数组,而动态代理要求接口数组必须包含IPerson本身才能生成正确的代理类。
    2. 明确代理目标 手动构造数组(如new Class<?>[] {IPerson.class})直接指定代理类需要实现的接口,避免依赖接口的继承关系,确保代码的明确性和可靠性。

    默认情况下的类加载器行为

    1. 同一类加载器委托模型 在标准Java应用中,如果UserService接口和UserServiceImpl类位于同一个类路径(例如同一个JAR包或目录),且未被特殊类加载器隔离,两者通常由同一个类加载器加载。这是因为类加载器遵循“双亲委派”机制:当加载UserServiceImpl类时,其实现的接口UserService会由同一个类加载器优先加载(除非显式打破委派规则)。
    2. 接口与实现类的依赖关系 当UserServiceImpl类实现UserService接口时,JVM在加载UserServiceImpl类时需先加载UserService接口。若两者未被隔离,类加载器会从同一路径加载它们,因此两者的类加载器通常一致。
    3. JDK动态代理生成的代理类需要实现目标接口(如UserService),因此类加载器必须能够加载这些接口。若接口的类加载器与实现类的类加载器不同,应优先选择接口的类加载器作为参数

    运行结果

    ----- 调用 addUser -----
    >>> 【代理】方法 [addUser] 开始执行...
    --- 正在执行数据库操作:添加用户 Alice ---
    <<< 【代理】方法 [addUser] 执行完毕...
    <<< 【代理】返回结果:null
       
    ----- 调用 findUser -----
    >>> 【代理】方法 [findUser] 开始执行...
    --- 正在执行数据库操作:查找用户 ID 101 ---
    <<< 【代理】方法 [findUser] 执行完毕...
    <<< 【代理】返回结果:用户101
    主程序收到结果: 用户101
       
    代理对象的类名: com.sun.proxy.$Proxy0
    代理对象是否是 UserService 的实例: true
    

CGLIB动态代理

什么是CGLIB动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理(创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调用者本身的效果;但也因为此,对于final类或方法,是无法继承的)。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

应用示例
class Person {
    private String sex;

    public void setSex(String sex) {
        System.out.println("setSex():" + sex);
        this.sex = sex;
    }

    public String getSex(){
        System.out.println("getSex():"+ this.sex);
        return sex;
    }
}

/**
 * 自定义 MethodInterceptor(方法拦截器)
 */
class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("after");
        return result;
    }
}

/**
 * Cglib代理工厂
 */
class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz, MethodInterceptor methodInterceptor) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(methodInterceptor);
        // 创建代理类
        return enhancer.create();
    }
}

public class ZhengheServiceCrmApplicationTests {
    public static void main(String[] args) throws Exception {
        MyMethodInterceptor interceptor = new MyMethodInterceptor();
        Person proxy = (Person) CglibProxyFactory.getProxy(Person.class, interceptor);
        proxy.setSex("男");
    }
}
运行结果
before
setSex():男
after

JDK 动态代理和CGLIB动态代理对比

  1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。