spring-reading./spring-aop/spring-aop-jdkProxy/README.md

13 KiB
Raw Blame History

JDK动态代理

一、基本信息

✒️ 作者 - Lex 📝 博客 - 掘金 📚 源码地址 - github

二、知识储备

  1. 反射

    • JDK 动态代理是基于 Java 的反射机制实现的。你需要了解如何在运行时获取类的信息、调用类的方法以及创建对象等。这是因为动态代理是在运行时生成代理类的字节码,并通过反射来实现对方法的调用。
  2. 设计模式

    • 尤其是代理模式。理解代理模式是非常重要的,因为动态代理实际上是代理模式的一种实现。你需要理解代理模式的结构以及它的应用场景。
  3. InvocationHandler

    • 这是 JDK 动态代理中的核心接口,它用于处理代理对象的方法调用。我们需要了解如何实现 InvocationHandler 接口以及如何在其 invoke 方法中编写代码来处理代理方法的调用。
  4. 类加载器

    • 了解类加载器的工作原理对于理解动态代理的生成过程是很有帮助的。动态代理通过类加载器加载动态生成的代理类,因此我们需要了解类加载器的概念和工作原理。

三、基本描述

JDK动态代理是一种在运行时生成代理类的机制它基于接口实现通过在代理类中重定向方法调用到实际对象并提供了InvocationHandler接口来实现代理对象方法调用的处理逻辑适用于AOP、远程方法调用等场景能够在不修改原始类的情况下实现横切关注点的统一管理提供更灵活和可维护的代码结构。

四、主要功能

  1. 代理对象生成

    • 在运行时生成代理对象,无需提前编写代理类的代码。
  2. 接口实现

    • 动态代理是基于接口的,可以为接口生成代理对象,而不是具体的类。
  3. 方法重定向

    • 代理对象可以重定向方法调用到实际对象,允许在方法调用前后执行一些额外逻辑。
  4. 横切关注点的统一管理

    • 通过代理对象,在方法调用前后执行统一的逻辑,如日志记录、权限验证、事务管理等,实现了横切关注点的统一管理。
  5. AOP的实现

    • 动态代理是AOP面向切面编程的基础之一可以通过动态代理实现切面的横切关注点将应用程序核心业务逻辑与横切关注点分离开来提高了代码的可维护性和灵活性。

五、接口源码

在Java JDK 中的动态代理机制中,Proxy 类提供了静态方法 newProxyInstance,用于动态生成代理对象。通过传入类加载器、接口数组和 InvocationHandler 对象,该方法在运行时生成一个代理对象。内部实现使用了反射机制,通过调用代理类的构造函数,将 InvocationHandler 对象传递给代理对象。

public class Proxy implements java.io.Serializable {

    protected InvocationHandler h;

    private Proxy() {
    }

    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    
    // ... [代码部分省略以简化]
    
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }
    
    // ... [代码部分省略以简化]
    
    private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
                                           Constructor<?> cons,
                                           InvocationHandler h) {
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (caller != null) {
                checkNewProxyPermission(caller, cons.getDeclaringClass());
            }

            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException | InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        }
    }
    
    // ... [代码部分省略以简化]
}

六、最佳实践

使用 JDK 动态代理的基本流程。首先,创建目标对象 MyService 的实例,然后获取目标对象的类信息。接着,通过调用 Proxy.newProxyInstance 方法创建代理对象,传入目标对象的类加载器、实现的接口以及调用处理器。最后,通过代理对象调用方法,实际上会调用 MyInvocationHandler 中的 invoke 方法来处理方法调用,并在方法执行前后添加额外的逻辑。

public class JdkProxyDemo {

    public static void main(String[] args) {
        // 创建目标对象
        MyService target = new MyServiceImpl();
        // 获取目标对象的类对象
        Class clz = target.getClass();
        // 创建代理对象,并指定目标对象的类加载器、实现的接口以及调用处理器
        MyService proxyObject = (MyService) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new MyInvocationHandler(target));
        // 打印代理对象的类信息
        System.out.println("ProxyObject = " + proxyObject.getClass());
        // 通过代理对象调用方法,实际上会调用 MyInvocationHandler 中的 invoke 方法
        proxyObject.doSomething();
    }
}

实现了 InvocationHandler 接口,定义了一个名为 MyInvocationHandler 的类。该类包含一个私有属性 target,用于存储目标对象。构造函数接收一个目标对象作为参数,并将其存储在 target 属性中。在 invoke 方法中,会在目标方法执行前打印 "Before method execution",然后通过反射调用目标对象的方法,并获取方法的返回结果。最后,在目标方法执行后打印 "After method execution",并返回方法的结果。

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("Before method execution");
        Object result = method.invoke(target, args);
        System.out.println("After method execution");
        return result;
    }
}

定义了一个接口 MyService,其中包含一个抽象方法 doSomething()。然后,定义了一个实现了 MyService 接口的类 MyServiceImpl,并实现了 doSomething() 方法。

public interface MyService {
    void doSomething();
}

public class MyServiceImpl implements MyService {

    @Override
    public void doSomething() {
        System.out.println("hello world");
    }
}

运行结果,成功使用了 JDK 动态代理。首先打印了代理对象的类信息,确认了代理对象确实是由 $Proxy0 类生成的。接着,打印了 "Before method execution",表示方法执行前的逻辑已经被执行。然后调用了目标对象的 doSomething() 方法,输出了 "hello world"。最后打印了 "After method execution",表示方法执行后的逻辑也被执行了。这表明 JDK 动态代理成功地代理了目标对象的方法,并在方法执行前后执行了额外的逻辑。

ProxyObject = class com.sun.proxy.$Proxy0
Before method execution
hello world
After method execution

七、源码分析

com.sun.proxy.$Proxy0 类是通过 Arthas 运行时编译生成的。Arthas 是一个强大的 Java 诊断工具,可以在运行时进行类的动态修改和增强,生成代理类以便进行调试和分析。

这段代码是由JDK动态代理生成的代理类位于com.sun.proxy包下,命名为$Proxy0。它继承自Proxy类并实现了MyService接口,包含了目标接口中的方法实现,同时通过InvocationHandler对象委托处理方法调用。在静态代码块中获取了目标接口和Object类中的方法,并提供了方法的具体实现。

package com.sun.proxy;

import com.xcs.spring.MyService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements MyService {
    
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    public final void doSomething() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
    
    // ... [toString代码部分省略以简化]
    // ... [hashCode代码部分省略以简化]
    // ... [equals代码部分省略以简化]

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.xcs.spring.MyService").getMethod("doSomething", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }
}

八、与其他组件的关系

  1. 接口实现关系

    • JDK动态代理通常基于接口实现。在使用动态代理时需要传入一个接口类型作为参数代理对象会实现这个接口并且在运行时生成的代理类会实现该接口定义的所有方法。因此与其他组件接口的关系是通过实现这些接口来实现的。
  2. 代理类关系

    • JDK动态代理生成的代理类是在运行时动态生成的。这些代理类继承自Proxy类,并且实现了传入的接口。因此,代理类与其他组件接口的关系体现在代理类实现了这些接口,从而拥有了对应接口定义的方法。
  3. 调用处理器关系

    • 在使用JDK动态代理时需要提供一个调用处理器InvocationHandler)来处理方法调用。该调用处理器可以是自定义的类,它与其他组件接口或类的关系取决于在其invoke方法中对方法调用的处理逻辑。

九、常见问题

  1. 代理对象的类型限制

    • 由于 JDK 动态代理是基于接口实现的,因此只能代理实现了接口的类。如果目标对象没有实现接口,就无法使用 JDK 动态代理。
  2. 无法代理 final 类和方法

    • JDK 动态代理无法代理 final 类和 final 方法。因为 final 类无法被继承final 方法无法被重写,而动态代理需要生成代理类并继承目标类或实现目标接口。
  3. 无法代理静态方法

    • JDK 动态代理无法代理静态方法,因为动态代理是基于实例的,而静态方法是属于类的。
  4. 性能开销

    • 与静态代理相比JDK 动态代理的性能开销较大。动态代理在运行时生成代理类的字节码,并通过反射来调用方法,相比静态代理的直接方法调用,会增加额外的开销。
  5. 泛型的处理

    • 如果目标方法涉及泛型参数,代理对象可能无法正确处理。因为在泛型擦除后,代理对象无法获取到准确的泛型信息。
  6. 调用堆栈的可读性

    • 由于动态代理的调用会经过 InvocationHandler,可能会增加调用堆栈的深度,降低代码的可读性和调试的便利性。