[关闭]
@TryLoveCatch 2022-04-19T10:13:31.000000Z 字数 5654 阅读 707

Java知识体系之动态代理

Java知识体系


代理模式

代理模式,简单来说,就是我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

举个例子:你喜欢小芳,你让小红去帮忙带话,这个小红就是代理对象,代理你去问话。

代理模式有静态代理和动态代理两种实现方式。

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。

从 JVM 层面来说,静态代理在编译期就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

例子

静态代理实现步骤:

  1. // 定义发送短信的接口
  2. public interface SmsService {
  3. String send(String message);
  4. }
  5. // 实现发送短信的接口
  6. public class SmsServiceImpl implements SmsService {
  7. public String send(String message) {
  8. System.out.println("send message:" + message);
  9. return message;
  10. }
  11. }
  12. // 创建代理类
  13. public class SmsProxy implements SmsService {
  14. private final SmsService smsService;
  15. public SmsProxy(SmsService smsService) {
  16. this.smsService = smsService;
  17. }
  18. @Override
  19. public String send(String message) {
  20. //调用方法之前,我们可以添加自己的操作
  21. System.out.println("before method send()");
  22. smsService.send(message);
  23. //调用方法之后,我们同样可以添加自己的操作
  24. System.out.println("after method send()");
  25. return null;
  26. }
  27. }
  28. // 实际使用
  29. public class Main {
  30. public static void main(String[] args) {
  31. SmsService smsService = new SmsServiceImpl();
  32. SmsProxy smsProxy = new SmsProxy(smsService);
  33. smsProxy.send("java");
  34. }
  35. }

动态代理

动态代理更加灵活,我们不需要针对每个目标类都单独创建一个代理类,而是通过某种方式自动生成代理类。

从 JVM 角度来说,动态代理是在运行期动态生成类字节码,并加载到 JVM 中的。

动态代理的实现方式一般来说有两种:JDK 动态代理和Cglib 动态代理。

JDK动态代理

在 Java 动态代理机制中 InvocationHandler 接口Proxy 类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

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

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

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

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

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

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

例子

JDK 动态代理类使用步骤

  1. // 定义发送短信的接口
  2. public interface SmsService {
  3. String send(String message);
  4. }
  5. // 实现发送短信的接口
  6. public class SmsServiceImpl implements SmsService {
  7. public String send(String message) {
  8. System.out.println("send message:" + message);
  9. return message;
  10. }
  11. }
  12. // JDK 动态代理类
  13. public class DebugInvocationHandler implements InvocationHandler {
  14. /**
  15. * 代理类中的真实对象
  16. */
  17. private final Object target;
  18. public DebugInvocationHandler(Object target) {
  19. this.target = target;
  20. }
  21. public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
  22. //调用方法之前,我们可以添加自己的操作
  23. System.out.println("before method " + method.getName());
  24. Object result = method.invoke(target, args);
  25. //调用方法之后,我们同样可以添加自己的操作
  26. System.out.println("after method " + method.getName());
  27. return result;
  28. }
  29. }
  30. // 获取代理对象的工厂类
  31. public class JdkProxyFactory {
  32. public static Object getProxy(Object target) {
  33. return Proxy.newProxyInstance(
  34. target.getClass().getClassLoader(), // 目标类的类加载
  35. target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
  36. new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
  37. );
  38. }
  39. }
  40. // 实际使用
  41. public class Main {
  42. public static void main(String[] args) {
  43. SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
  44. smsService.send("java");
  45. }
  46. }

Cglib动态代理

JDK 动态代理有一个最致命的问题是针对接口。

为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。
CGLIB 通过继承方式实现代理。

JDK动态代理 VS Cglib动态代理

静态代理 VS 动态代理

JDK动态代理原理

  1. SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());

我们打印一下smsService的实际类名、所实现的接口和父类的名称,得到结果如下:

  1. 类名:com.sun.proxy.$Proxy11
  2. 父类:java.lang.reflect.Proxy
  3. 实现接口:SmsService

因此,生成的代理类有如下3个特点:

1.继承了Proxy类
2.实现了我们传入的接口
3.以$Proxy+随机数字的命名

接着我们还是需要略微查看一下newProxyInstance方法的源码,只需要关心下面几行核心代码,如下:

  1. // 代理类的构造方法参数类型数组,可见代理类的构造参数只有InvocationHandler类型
  2. private static final Class<?>[] constructorParams = { InvocationHandler.class };
  3. public static Object newProxyInstance(ClassLoader loader,
  4. Class<?>[] interfaces,
  5. InvocationHandler h)
  6. throws IllegalArgumentException
  7. {
  8. ...
  9. /**
  10. * 根据我们传进来的接口创建一个类
  11. */
  12. Class<?> cl = getProxyClass0(loader, intfs);
  13. ...
  14. /**
  15. * 找到类的构造方法,该构造方法获取一个InvocationHandler类型的参数
  16. */
  17. final Constructor<?> cons = cl.getConstructor(constructorParams);
  18. ...
  19. /**
  20. * 通过构造方法生成代理类的实例
  21. */
  22. return cons.newInstance(new Object[]{h});
  23. }

因此总结一下动态代理对象创建的过程

1.根据我们传入的接口动态地创建一个Class
2.获取类的构造函数,且该构造函数有唯一的InvocationHandler类型参数
3.将InvocationHandler作为参数传入构造函数,实例化代理对象的实例,并将其返回

当然,这里最核心的方法自然是类的创建,简而言之,就是在运行时,一个字节一个字节地构造一个字节数组,而这个字节数组正是一个.class字节码,然后通过一个native方法,将其转化为我们运行时的Class类。

再通俗一些来说:平时我们使用的类都是预先编译好的.class文件,而动态代理则是直接在运行时通过组装一个byte数组的方式创建一个.class文件,这样应该就是比较好理解了吧。

最后,我们来看一下这个生成出来的代理类究竟长啥样,正符合我们之前总结出的代理对象的3个特点。

  1. // 注意类是final的
  2. public final class $Proxy11 extends Proxy implements SmsService {
  3. private static Method m3;
  4. // 构造函数,参数为InvocationHandler
  5. public $Proxy11(InvocationHandler var1) throws {
  6. super(var1);
  7. }
  8. // 方法是final
  9. public final String send(String var1) {
  10. // 调用InvocationHandler#invoke
  11. return super.h.invoke(this, m3, new Object[]{var1});
  12. }
  13. static {
  14. try {
  15. m3 = Class.forName("cn.xxx.SmsService").getMethod("send", String.TYPE);
  16. } catch (NoSuchMethodException var2) {
  17. throw new NoSuchMethodError(var2.getMessage());
  18. } catch (ClassNotFoundException var3) {
  19. throw new NoClassDefFoundError(var3.getMessage());
  20. }
  21. }
  22. }

参考

https://javaguide.cn/java/basis/proxy.html#_1-%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F
https://www.cnblogs.com/tera/p/13911819.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注