JDK 动态代理

概述

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy类是核心。
Proxy类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler h)
  4. throws IllegalArgumentException
  5. {
  6. ......
  7. }

这个方法一共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces : 被代理类实现的一些接口;
  3. h : 实现了 InvocationHandler接口的对象;

要实现动态代理的话,还必须需要实现InvocationHandler来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler接口类的 invoke方法来调用。

  1. public interface InvocationHandler {
  2. /**
  3. * 当你使用代理对象调用方法的时候实际会调用到这个方法
  4. */
  5. public Object invoke(Object proxy, Method method, Object[] args)
  6. throws Throwable;
  7. }

invoke() 方法有下面三个参数:

  1. proxy :动态生成的代理类
  2. method : 与代理类对象调用的方法相对应
  3. args : 当前 method 方法的参数

也就是说:你通过Proxy类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现
InvocationHandler接口的类的invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

JDK 动态代理类使用步骤

  1. 定义一个接口及其实现类;
  2. 自定义 InvocationHandler并重写invoke方法,在 invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;

    代码示例

    定义发送短信的接口

    1. public interface SmsService {
    2. String send(String message);
    3. }

    实现发送短信的接口

    1. public class SmsServiceImpl implements SmsService {
    2. public String send(String message) {
    3. System.out.println("send message:" + message);
    4. return message;
    5. }
    6. }

    定义一个 JDK 动态代理类

    ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;

public class DebugInvocationHandler implements InvocationHandler { /**

  1. * 代理类中的真实对象
  2. */
  3. private final Object target;
  4. public DebugInvocationHandler(Object target) {
  5. this.target = target;
  6. }
  7. public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
  8. //调用方法之前,我们可以添加自己的操作
  9. System.out.println("before method " + method.getName());
  10. Object result = method.invoke(target, args);
  11. //调用方法之后,我们同样可以添加自己的操作
  12. System.out.println("after method " + method.getName());
  13. return result;
  14. }

}

  1. `invoke()` 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是` invoke()` 方法,然后 `invoke() `方法代替我们去调用了被代理对象的原生方法。
  2. <a name="ny6cu"></a>
  3. #### 获取代理对象的工厂类
  4. ```java
  5. public class JdkProxyFactory {
  6. public static Object getProxy(Object target) {
  7. return Proxy.newProxyInstance(
  8. target.getClass().getClassLoader(), // 目标类的类加载
  9. target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
  10. new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
  11. );
  12. }
  13. }

getProxy() :主要通过Proxy.newProxyInstance()方法获取某个类的代理对象

实际使用

  1. SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
  2. smsService.send("java");

运行结果

before method send send message:java after method send

CGLIB 动态代理

概述

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。

为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

你需要自定义 MethodInterceptor并重写 intercept方法,intercept用于拦截增强被代理类的方法。

  1. public interface MethodInterceptor
  2. extends Callback{
  3. // 拦截被代理类中的方法
  4. public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
  5. MethodProxy proxy) throws Throwable;
  6. }
  1. obj :被代理的对象(需要增强的对象)
  2. method :被拦截的方法(需要增强的方法)
  3. args :方法入参
  4. proxy :用于调用原始方法

你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor中的 intercept方法。

CGLIB 动态代理类使用步骤

  1. 定义一个类;
  2. 自定义 MethodInterceptor并重写 intercept方法,intercept用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke方法类似;
  3. 通过 Enhancer类的 create()创建代理类;

    代码示例

    不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
    1. <dependency>
    2. <groupId>cglib</groupId>
    3. <artifactId>cglib</artifactId>
    4. <version>3.3.0</version>
    5. </dependency>

    实现一个使用阿里云发送短信的类

    ```java package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService { public String send(String message) { System.out.println(“send message:” + message); return message; } }

  1. <a name="yPbnU"></a>
  2. #### 自定义 `MethodInterceptor`(方法拦截器)
  3. ```java
  4. import net.sf.cglib.proxy.MethodInterceptor;
  5. import net.sf.cglib.proxy.MethodProxy;
  6. import java.lang.reflect.Method;
  7. /**
  8. * 自定义MethodInterceptor
  9. */
  10. public class DebugMethodInterceptor implements MethodInterceptor {
  11. /**
  12. * @param o 代理对象(增强的对象)
  13. * @param method 被拦截的方法(需要增强的方法)
  14. * @param args 方法入参
  15. * @param methodProxy 用于调用原始方法
  16. */
  17. @Override
  18. public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  19. //调用方法之前,我们可以添加自己的操作
  20. System.out.println("before method " + method.getName());
  21. Object object = methodProxy.invokeSuper(o, args);
  22. //调用方法之后,我们同样可以添加自己的操作
  23. System.out.println("after method " + method.getName());
  24. return object;
  25. }
  26. }

获取代理类

  1. import net.sf.cglib.proxy.Enhancer;
  2. public class CglibProxyFactory {
  3. public static Object getProxy(Class<?> clazz) {
  4. // 创建动态代理增强类
  5. Enhancer enhancer = new Enhancer();
  6. // 设置类加载器
  7. enhancer.setClassLoader(clazz.getClassLoader());
  8. // 设置被代理类
  9. enhancer.setSuperclass(clazz);
  10. // 设置方法拦截器
  11. enhancer.setCallback(new DebugMethodInterceptor());
  12. // 创建代理类
  13. return enhancer.create();
  14. }
  15. }

实际使用

  1. AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
  2. aliSmsService.send("java");

运行结果

before method send send message:java after method send

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

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