java反射-动态代理

曾经终败给现在 2023-01-17 07:42 181阅读 0赞

转自:https://www.cnblogs.com/throwable/p/12272269.html

动态代理的简介#

Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。Java动态代理实际上通过反射技术,把代理对象和被代理对象(真实对象)的代理关系建立延迟到程序运行之后,动态创建新的代理类去完成对真实对象的代理操作(可以改变原来真实对象的方法行为),这一点成为了当前主流的AOP框架和延迟加载功能的基础。本文在查看和编写动态代理相关的代码使用的是JDK11,不过JDK动态代理相关的功能和接口已经相对稳定,不必担心JDK版本升级带来的兼容性问题,但是需要注意由于JDK9引入了模块概念,动态代理的源码也有不少的改动。下文先介绍设计模式中的代理模式,接着会分析JDK动态代理的核心类库、流程和机制,最后分析其底层源码级别实现。

设计模式中的代理模式#

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

bddf6b24f90fc048bd826966ff9cc85d.png

代理模式主要包括三种角色:

  • Subject抽象主题角色:一般定义为抽象类或者接口,是作为功能的定义,提供一系列抽象的功能方法。
  • RealSubject具体(真实)主题角色:一般称为被委托角色或者被代理角色,它是Subject的一个具体实现。
  • ProxySubject代理主题角色:一般称为委托角色或者代理角色,一般ProxySubject也实现(或者继承)Subject,接收一个具体的Subject实例RealSubject,在RealSubject处理前后做预定义或者后置操作,甚至可以直接忽略RealSubject原来的方法。

把上面的类图编写成代码如下:

  1. public interface Subject {
  2. void doSomething();
  3. }
  4. public class RealSubject implements Subject {
  5. @Override
  6. public void doSomething() {
  7. System.out.println("RealSubject doSomething...");
  8. }
  9. }
  10. public class ProxySubject implements Subject {
  11. private final Subject subject;
  12. public ProxySubject(Subject subject) {
  13. this.subject = subject;
  14. }
  15. @Override
  16. public void doSomething() {
  17. subject.doSomething();
  18. doOtherThing();
  19. }
  20. private void doOtherThing() {
  21. System.out.println("ProxySubject doOtherThing...");
  22. }
  23. }
  24. public class Client {
  25. public static void main(String[] args) throws Exception {
  26. Subject subject = new RealSubject();
  27. ProxySubject proxySubject = new ProxySubject(subject);
  28. proxySubject.doSomething();
  29. }
  30. }

运行Client#main()输出:

  1. RealSubject doSomething...
  2. ProxySubject doOtherThing...

代理模式在日常的场景中也经常碰到,比较常见的一个场景就是游戏代练,套进去上面的代码可以写个比较生动的例子:

  1. public interface Player {
  2. void playGame();
  3. }
  4. public class I implements Player {
  5. @Override
  6. public void playGame() {
  7. System.out.println("操作Throwable游戏角色打怪升级");
  8. }
  9. }
  10. public class ProxyPlayer implements Player {
  11. private final Player player;
  12. public ProxyPlayer(Player player) {
  13. this.player = player;
  14. }
  15. @Override
  16. public void playGame() {
  17. login();
  18. this.player.playGame();
  19. logout();
  20. }
  21. private void login() {
  22. System.out.println("登录Throwable游戏角色");
  23. }
  24. private void logout() {
  25. System.out.println("退出Throwable游戏角色");
  26. }
  27. }

代理模式有几个比较大的优点:

  • 职责清晰:也就是真实主题角色只需要实现具体的逻辑,不需关注代理类的职责,而代理类也只需要处理预处理和后置的逻辑,类的职责分明。
  • 高扩展性:由于职责分明,也就是真实主题角色可以随时修改实现,这样就能通过更新或者替换真实主题的实现并且不改变代理主题角色的情况下改变具体功能。
  • 高灵活性:主要体现在后面提到的动态代理。

JDK动态代理的核心API#

JDK动态代理提供外部使用的主要依赖两个类:

  • java.lang.reflect.Proxy:可以理解为代理类的工厂类(其实也是父类,见下文)。
  • java.lang.reflect.InvocationHandler:代理实例需要实现的调用处理器接口。

Proxy#

java.lang.reflect.Proxy是JDK动态代理的核心类,它的核心功能是提供静态方法来为一组接口动态地生成代理类并且返回代理实例对象,类似于代理类实例的工厂类。java.lang.reflect.Proxy主要提供四个public静态方法:

  1. // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
  2. public static InvocationHandler getInvocationHandler(Object proxy)
  3. // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
  4. public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces)
  5. // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
  6. public static boolean isProxyClass(Class<?> cl)
  7. // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
  8. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • InvocationHandler getInvocationHandler(Object proxy):通过制定的代理类实例查找它关联的调用处理器实例。
  • Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces):用于获取关联于指定类装载器和一组接口的动态代理类的类对象,也就是获取$ProxyXXX的类型,此方法在JDK9以后标记为过期,原因是:在命名模块中生成的代理类是封闭的,模块外的代码无法访问这些类(违反模块规则调用了会抛异常)。
  • boolean isProxyClass(Class<?> cl):用于判断指定类是否是一个动态代理类。
  • Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):这个是JDK动态代理最核心的方法,用于为指定类装载器、一组接口及调用处理器生成动态代理类实例,也就是生成$ProxyXXX的实例。此方法需要指定类加载器java.lang.ClassLoader,Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是在运行时动态生成的而非预存在于任何一个.class文件中。interfaces是Class数组,也就是需要使用InvocationHandler进行代理访问的接口类型数组,这里的h参数就是调用处理器的实例。

InvocationHandler#

java.lang.reflect.InvocationHandler是调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

  1. public interface InvocationHandler {
  2. Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  3. }

参数说明:

  • proxy:Object类型,此参数即是代理类实例,也就是$ProxyXXX的实例。
  • method:java.lang.reflect.Method类型,被调用的方法的实例。
  • args:Object[]类型,被调用方法的参数数组。

实现java.lang.reflect.InvocationHandler接口,通过实现invoke方法即可添加代理访问的逻辑,在这个逻辑代码块中除了可以调用委托类的方法,还可以织入额外的自定义逻辑,AOP就是这样实现的。

JDK动态代理的流程#

JDK动态代理的使用流程如下:

  • 1、通过实现java.lang.reflect.InvocationHandler接口创建自定义的调用处理器。
  • 2、通过为java.lang.reflect.Proxy类指定ClassLoader对象和一组interface来创建动态代理类。
  • 3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型。
  • 4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

伪代码如下:

  1. // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
  2. // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
  3. InvocationHandler handler = new InvocationHandlerImpl(..);
  4. // 通过Proxy为包括Interface接口在内的一组接口动态创建代理类的类对象
  5. Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
  6. // 通过反射从生成的类对象获得构造函数对象
  7. Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
  8. // 通过构造函数对象创建动态代理类实例
  9. Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

上面的过程比较复杂,可以进行精简。简化后的伪代码如下:

  1. // InvocationHandlerImpl实现了InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
  2. InvocationHandler handler = new InvocationHandlerImpl(..);
  3. // 通过Proxy直接创建动态代理类实例
  4. Interface proxy = (Interface) Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);

JDK动态代理的机制#

首先是JDK动态代理生成的代理类本身的特点:

  • 1、包(或者JDK9引入的模块):如果所代理的接口都是public的,那么它将被定义在包com.sun.proxy;如果所代理的接口中有非public的接口(因为接口不能被定义为protect或private,所以除public之外就是默认的package访问级别,修饰符为default),那么它将被定义在该接口所在包(假设代理了throwable.club包中的某非public接口A,那么新生成的代理类所在的包就是throwable.club),值得注意的是,如果接口数组中存在非public的接口,那么它们必须在同一个包路径下,否则会抛异常。这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问。
  • 2、类修饰符:该代理类具有final和public修饰符,意味着它可以被所有的类访问,但是不能被再度继承
  • 3、类名:代理类名称格式是$ProxyN,其中N是一个逐一递增的阿拉伯数字,代表java.lang.reflect.Proxy类第N次生成的动态代理类,值得注意的一点是,并不是每次调用Proxy的静态方法创建动态代理类都会使得N值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会从缓存中获取先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
  • 4、类继承关系:代理类的继承关系图如下:

8ad077c7babf70ddbb83eed48a38b526.png

由图可知,java.lang.reflect.Proxy类是代理类的父类,这个规则适用于所有由java.lang.reflect.Proxy创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

代理类实例的特点#

每个代理类实例都会关联一个调用处理器对象,可以通过java.lang.reflect.Proxy提供的静态方法getInvocationHandler()去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类java.lang.Object中有三个方法也同样会被分派到调用处理器的invoke方法执行,它们是hashCodeequalstoString,可能的原因有:

  • 一、因为这些方法为public且非final类型,能够被代理类覆盖。
  • 二、因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

被代理的一组接口的特点#

首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非public的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过65535,这是JVM设定的限制,这一点在代理类生成的时候也做了判断。

异常处理#

从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于Throwable接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛Throwable异常。那么如果在invoke方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Jdk动态代理类已经为我们设计好了解决方法:它将会抛出UndeclaredThrowableException 异常。这个异常是一个RuntimeException类型,所以不会引起编译错误。通过该异常的getCause方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

JDK动态代理源码分析#

因为JDK动态代理核心逻辑都在java.lang.reflect.Proxy类中,下面简单分析一下这个类的源码。先看Proxy类中的几个重要的静态变量:

  1. // 接口组中接口都为为public时候代理类创建的包路径:com.sun.proxy
  2. private static final String PROXY_PACKAGE_PREFIX = ReflectUtil.PROXY_PACKAGE;
  3. // 代理类的构造方法参数类型数组,可见代理类的构造参数只有InvocationHandler类型
  4. private static final Class<?>[] constructorParams = { InvocationHandler.class };
  5. // 缓存了所有已经调用过setAccessible(true)的代理类的构造(Constructor)实例
  6. private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();

这里注意到ClassLoaderValue,下文会调用到它的一个很复杂的调用链:

  1. //intf是Class<?>类型
  2. //loader是类加载器实例
  3. return proxyCache.sub(intf).computeIfAbsent(
  4. loader,
  5. (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
  6. );
  7. public V computeIfAbsent(ClassLoader cl,
  8. BiFunction<? super ClassLoader,? super CLV,? extends V> mappingFunction)
  9. throws IllegalStateException {

上面的computeIfAbsent中使用了函数式接口和Lambda表达式,如果Lambda表达式玩的比较熟练看起来应该没问题,它的功能可以解读为:通过接口类型和类加载器实例计算通过接口类型和类加载器实例构建ProxyBuilder实例并且调用ProxyBuilder#build()得到的结果,如果结果已经存在则直接返回缓存。其实computeIfAbsentMap接口中也定义了同样的方法,功能是相似的。

接着看Proxy的构造函数:

  1. protected InvocationHandler h;
  2. private Proxy() {
  3. }
  4. protected Proxy(InvocationHandler h) {
  5. Objects.requireNonNull(h);
  6. this.h = h;
  7. }

到此可以明确一点,既然所有动态代理类都是java.lang.reflect.Proxy的子类,那么它们一定具备一个包含InvocationHandler参数的构造器。接着查看``方法的源码:

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler h) {
  4. // 空判断
  5. Objects.requireNonNull(h);
  6. // 当前调用类获取
  7. final Class<?> caller = System.getSecurityManager() == null
  8. ? null
  9. : Reflection.getCallerClass();
  10. // 获取代理类的构造器实例
  11. Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
  12. // 生成代理实例
  13. return newProxyInstance(caller, cons, h);
  14. }

先看getProxyConstructor方法:

  1. private static Constructor<?> getProxyConstructor(Class<?> caller,
  2. ClassLoader loader,
  3. Class<?>... interfaces){
  4. // 这里需要区分代理接口数组中只有单个接口和多个接口的逻辑
  5. // 而基本的逻辑都是先校验当前调用类的权限,后续获取Constructor实例委托到ProxyBuilder
  6. if (interfaces.length == 1) {
  7. Class<?> intf = interfaces[0];
  8. if (caller != null) {
  9. checkProxyAccess(caller, loader, intf);
  10. }
  11. return proxyCache.sub(intf).computeIfAbsent(
  12. loader,
  13. (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
  14. );
  15. } else {
  16. // 接口克隆
  17. final Class<?>[] intfsArray = interfaces.clone();
  18. if (caller != null) {
  19. checkProxyAccess(caller, loader, intfsArray);
  20. }
  21. final List<Class<?>> intfs = Arrays.asList(intfsArray);
  22. return proxyCache.sub(intfs).computeIfAbsent(
  23. loader,
  24. (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
  25. );
  26. }
  27. }

可以明确,核心的逻辑都交给了Proxy的内部类ProxyBuilder完成,先看ProxyBuilder的静态成员变量:

  1. // Unsafe实例
  2. private static final Unsafe UNSAFE = Unsafe.getUnsafe();
  3. // 代理类的简单类名的前置字符串
  4. private static final String proxyClassNamePrefix = "$Proxy";
  5. // 用于生成下一个代理类的数字计数器,记住它是静态的
  6. private static final AtomicLong nextUniqueNumber = new AtomicLong();
  7. // 记录了已经生成的代理类-Boolean的映射,已经生成过对应代理类则记录为true
  8. private static final ClassLoaderValue<Boolean> reverseProxyCache = new ClassLoaderValue<>();
  9. // 单个代理接口的情况,其实也是把接口转换为List
  10. ProxyBuilder(ClassLoader loader, Class<?> intf) {
  11. this(loader, Collections.singletonList(intf));
  12. }
  13. // 多个代理接口的情况
  14. ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
  15. // 通过JVM参数强制关闭动态代理功能则抛出异常
  16. if (!VM.isModuleSystemInited()) {
  17. throw new InternalError("Proxy is not supported until "
  18. + "module system is fully initialized");
  19. }
  20. // 代理接口数量不能超过65535,也就是最多代理65535个接口
  21. if (interfaces.size() > 65535) {
  22. throw new IllegalArgumentException("interface limit exceeded: "
  23. + interfaces.size());
  24. }
  25. // 收集接口数组中所有接口的非静态方法的返回值类型、共享(shared)参数类型和共享(shared)异常类型,注释说是收集代理接口的方法签名
  26. Set<Class<?>> refTypes = referencedTypes(loader, interfaces);
  27. // 确保上一步得到的代理接口方法签名的类型都是"可见(其实就是类型都存在)"的,通过遍历调用Class.forName(type.getName(), false, ld)去判断
  28. validateProxyInterfaces(loader, interfaces, refTypes);
  29. this.interfaces = interfaces;
  30. // 获取代理类最终生成的模块,规则如下:
  31. // 1、所有代理接口的修饰符都为public,接口所在模块都能公开访问,则返回unnamed模块
  32. // 2、如果有任意的代理接口是包私有,则返回该包所在的模块 、
  33. // 3、所有代理接口的修饰符都为public,有任意至少一个接口所在模块不能公开访问,则返回该不能公开访问的模块,
  34. this.module = mapToModule(loader, interfaces, refTypes);
  35. assert getLoader(module) == loader;
  36. }

一个构造器处理的逻辑也是相对复杂,主要是因为引入模块管理的概念,接着看ProxyBuilder#build()的源码:

  1. Constructor<?> build() {
  2. // 定义代理类,实际上是动态生成代理类字节码和缓存它的类型的过程
  3. Class<?> proxyClass = defineProxyClass(module, interfaces);
  4. final Constructor<?> cons;
  5. try {
  6. // 返回代理类的构造
  7. cons = proxyClass.getConstructor(constructorParams);
  8. } catch (NoSuchMethodException e) {
  9. throw new InternalError(e.toString(), e);
  10. }
  11. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  12. public Void run() {
  13. cons.setAccessible(true);
  14. return null;
  15. }
  16. });
  17. return cons;
  18. }

最后到逻辑最复杂的代理类的生成过程ProxyBuilder#defineProxyClass()

  1. private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
  2. String proxyPkg = null; // package to define proxy class in
  3. int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
  4. // 这里就是定义代理类包路径的逻辑,规则如下:
  5. // 1、代理接口数组所有接口都是public修饰,则代理类包路径为com.sun.proxy
  6. // 2、代理接口数组有任意接口是包私有的,则代理类包路径为该私有包的路径
  7. for (Class<?> intf : interfaces) {
  8. int flags = intf.getModifiers();
  9. if (!Modifier.isPublic(flags)) {
  10. accessFlags = Modifier.FINAL; // non-public, final
  11. String pkg = intf.getPackageName();
  12. if (proxyPkg == null) {
  13. proxyPkg = pkg;
  14. } else if (!pkg.equals(proxyPkg)) {
  15. throw new IllegalArgumentException(
  16. "non-public interfaces from different packages");
  17. }
  18. }
  19. }
  20. // 下面几个if都是包路径的合法性判断
  21. if (proxyPkg == null) {
  22. // all proxy interfaces are public
  23. proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
  24. : PROXY_PACKAGE_PREFIX;
  25. } else if (proxyPkg.isEmpty() && m.isNamed()) {
  26. throw new IllegalArgumentException(
  27. "Unnamed package cannot be added to " + m);
  28. }
  29. if (m.isNamed()) {
  30. if (!m.getDescriptor().packages().contains(proxyPkg)) {
  31. throw new InternalError(proxyPkg + " not exist in " + m.getName());
  32. }
  33. }
  34. // 计数器加1返回新的计数值
  35. long num = nextUniqueNumber.getAndIncrement();
  36. // 生成代理类全类名,一个常见的格式是:com.sun.proxy.$Proxy1
  37. String proxyName = proxyPkg.isEmpty()
  38. ? proxyClassNamePrefix + num
  39. : proxyPkg + "." + proxyClassNamePrefix + num;
  40. ClassLoader loader = getLoader(m);
  41. trace(proxyName, m, loader, interfaces);
  42. // 动态生成代理类字节码字节数组
  43. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  44. proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
  45. try {
  46. // 通过Unsafe定义代理类-这里是通过字节码定义新的类
  47. Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
  48. 0, proxyClassFile.length,
  49. loader, null);
  50. // 缓存代理类已经生成过的标记
  51. reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
  52. return pc;
  53. } catch (ClassFormatError e) {
  54. /*
  55. * A ClassFormatError here means that (barring bugs in the
  56. * proxy class generation code) there was some other
  57. * invalid aspect of the arguments supplied to the proxy
  58. * class creation (such as virtual machine limitations
  59. * exceeded).
  60. */
  61. throw new IllegalArgumentException(e.toString());
  62. }
  63. }

到这一步为止,代理类的生成过程已经大致分析完毕,ProxyGenerator中涉及到大量字节码操作,这里就不深入分析了。那么回到最前面的方法,得到代理类和它的构造实例,接着就可以生成代理实例:

  1. private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
  2. Constructor<?> cons,
  3. InvocationHandler h) {
  4. try {
  5. if (caller != null) {
  6. checkNewProxyPermission(caller, cons.getDeclaringClass());
  7. }
  8. // 这里简单反射调用Constructor#newInstance(h)
  9. return cons.newInstance(new Object[]{h});
  10. } catch (IllegalAccessException | InstantiationException e) {
  11. throw new InternalError(e.toString(), e);
  12. } catch (InvocationTargetException e) {
  13. Throwable t = e.getCause();
  14. if (t instanceof RuntimeException) {
  15. throw (RuntimeException) t;
  16. } else {
  17. throw new InternalError(t.toString(), t);
  18. }
  19. }
  20. }

小结一下:

  • 接口数组中所有接口元素的类修饰符最好一致为public。如果接口数组中存在非default修饰的接口元素,那么接口数组中的所有接口类都要放在同一个包下,并且都要使用default修饰。
  • 很少情况下我们修改接口的修饰符,默认为public,那么所有代理类的包路径都是com.sun.proxy,全类名是:com.sun.proxy.$ProxyN
  • 代理接口数量不能超过65535。

JDK动态代理类的源代码#

前面已经分析完了代理类的生成过程,这里举个简单的使用例子,并且观察生成的动态代理类的源代码。

使用例子:

  1. package com.cayden.collect.proxy;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. /**
  6. * Created by cuiran on 16/6/20.
  7. */
  8. public class MyProxy {
  9. public interface IHello{
  10. void sayHello();
  11. }
  12. static class Hello implements IHello{
  13. @Override
  14. public void sayHello() {
  15. System.out.println("Hello World");
  16. }
  17. }
  18. static class MyInvocationHandler implements InvocationHandler{
  19. private Object target;//目标对象
  20. public MyInvocationHandler(Object target){
  21. this.target=target;
  22. }
  23. public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
  24. System.out.println("before");
  25. Object rs=method.invoke(target,args);
  26. System.out.println("after");
  27. return rs;
  28. }
  29. }
  30. public static void main(String [] args){
  31. // System.out.println("test");
  32. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
  33. IHello iHello=(IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(),//加载接口的类加载器
  34. new Class[]{IHello.class},
  35. new MyInvocationHandler(new Hello()));
  36. iHello.sayHello();
  37. }
  38. }

调用后输出:

  1. Before say hello...
  2. throwable say hello!
  3. After say hello...

可以看到,我们在被代理类DefaultSimple实例的方法调用前后织入了自定义的逻辑,这就是通过JDK动态代理实现AOP的底层原理。在JDK8中可以直接使用sun.misc.ProxyGenerator去输出代理类的class文件,但是JDK11中这个代理类生成器已经变成java.lang.reflect.ProxyGenerator,并且这个类是包私有的,我们无法使用,但是它提供了jdk.proxy.ProxyGenerator.saveGeneratedFiles这个VM参数让我们可以保存代理类的class文件:

  1. # JVM参数
  2. -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

配置好VM参数后,再次调用mian方法就能看到在项目的顶层包路径下看到对应的类com.sun.proxy.$Proxy0,目前从java.lang.reflect.ProxyGenerator源码看无法控制代理类文件的输出路径,生成的代理类内容如下:

  1. public final class $Proxy0 extends Proxy implements Simple {
  2. private static Method m1;
  3. private static Method m3;
  4. private static Method m2;
  5. private static Method m0;
  6. public $Proxy0(InvocationHandler var1) throws {
  7. super(var1);
  8. }
  9. public final boolean equals(Object var1) throws {
  10. try {
  11. return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
  12. } catch (RuntimeException | Error var3) {
  13. throw var3;
  14. } catch (Throwable var4) {
  15. throw new UndeclaredThrowableException(var4);
  16. }
  17. }
  18. public final void sayHello(String var1) throws {
  19. try {
  20. super.h.invoke(this, m3, new Object[]{var1});
  21. } catch (RuntimeException | Error var3) {
  22. throw var3;
  23. } catch (Throwable var4) {
  24. throw new UndeclaredThrowableException(var4);
  25. }
  26. }
  27. public final String toString() throws {
  28. try {
  29. return (String)super.h.invoke(this, m2, (Object[])null);
  30. } catch (RuntimeException | Error var2) {
  31. throw var2;
  32. } catch (Throwable var3) {
  33. throw new UndeclaredThrowableException(var3);
  34. }
  35. }
  36. public final int hashCode() throws {
  37. try {
  38. return (Integer)super.h.invoke(this, m0, (Object[])null);
  39. } catch (RuntimeException | Error var2) {
  40. throw var2;
  41. } catch (Throwable var3) {
  42. throw new UndeclaredThrowableException(var3);
  43. }
  44. }
  45. static {
  46. try {
  47. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  48. m3 = Class.forName("club.throwable.jdk.sample.reflection.proxy.Simple").getMethod("sayHello", Class.forName("java.lang.String"));
  49. m2 = Class.forName("java.lang.Object").getMethod("toString");
  50. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  51. } catch (NoSuchMethodException var2) {
  52. throw new NoSuchMethodError(var2.getMessage());
  53. } catch (ClassNotFoundException var3) {
  54. throw new NoClassDefFoundError(var3.getMessage());
  55. }
  56. }
  57. }

代理类的代码比较简单,有如下几个特点:

  • 1、代理类继承于java.lang.reflect.Proxy,实现了接口数组中的接口元素类,构造函数只有一个InvocationHandler类型的参数。
  • 2、接口中的所有被代理方法包括equalstoStringhashCode都建立了一个对应的Method私有静态实例,在最后面的静态代码块中实例化。
  • 3、所有代理方法都是用public final修饰,也就是代理类中的代理方法是不能覆盖的。
  • 4、所有代理方法都是通过InvocationHandler实例的invoke方法进行调用的,记得第一个参数是代理类实例本身,如果用了在InvocationHandler#invoke()方法实现过程中使用了这个参数有可能造成死循环。

小结

诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的单继承机制注定了这些动态代理类们无法实现对class的动态代理(所以只能代理接口,实际上是基于反射对方法级别的逻辑进行编织)。有很多条理由,可以否定对class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。但是,不完美并不等于不伟大,伟大是一种本质,JDK动态代理就是佐例。

发表评论

表情:
评论列表 (有 0 条评论,181人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java反射动态代理

          与[javax.lang.model][] 不同的是,通过反射API可以获取程序在运行时刻的内部结构。反射API中提供的动态代理也是非常强大的功能,可以原生实现[A

    相关 反射_应用:Java动态代理

    动态代理特别重要,Java的框架都爱这玩意儿!!! 动态代理特别重要,Java的框架都爱这玩意儿!!! 动态代理特别重要,Java的框架都爱这玩意儿!!!

    相关 Java反射动态代理

    反射和动态代理放有一定的相关性,但单纯的说动态代理是由反射机制实现的,其实是不够全面不准确的,动态代理是一种功能行为,而它的实现方法有很多。要怎么理解以上这句话,请看下文。