JDK 动态代理概述

JDK 动态代理是 Java 提供的一种机制,它允许你在运行时创建一个代理对象,代理对象可以通过实现一个或多个接口来对原对象进行代理。在代理对象的方法调用时,动态代理会将请求转发给代理类(通常是一个 InvocationHandler 实现类)。它主要应用于 面向切面编程(AOP) 和 装饰者模式 等场景。

为什么使用 JDK 动态代理?

  1. 代码解耦:通过动态代理,可以将功能的实现与业务逻辑解耦,从而增加代码的可维护性和复用性。
  2. 增强功能:可以在不修改原有类的情况下,给类的方法添加功能,比如事务控制、日志记录、权限校验等。

JDK 动态代理的工作原理

JDK 动态代理机制依赖于 Java 的反射机制,它基于以下几点:

  1. 接口:JDK 动态代理只支持接口代理,也就是说,被代理的类必须实现接口。
  2. Proxy 类:Java 提供的 Proxy 类用于创建动态代理对象。
  3. InvocationHandler 接口InvocationHandler 接口用于定义代理类如何处理方法调用,它有一个 invoke() 方法。

1. 创建一个简单的 JDK 动态代理

假设我们有一个接口和它的实现类:

public interface HelloWorld {
    void sayHello(String name);
}

public class HelloWorldImpl implements HelloWorld {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

我们希望对 sayHello 方法进行代理,在调用该方法时,打印一些日志。

步骤:

  1. 定义 InvocationHandler 实现类InvocationHandler 接口中的 invoke 方法在每次调用代理对象的方法时都会被执行。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class HelloWorldInvocationHandler implements InvocationHandler {
    private Object target;

    public HelloWorldInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用方法之前打印日志
        System.out.println("Before method: " + method.getName());
        
        // 调用原始对象的方法
        Object result = method.invoke(target, args);
        
        // 在调用方法之后打印日志
        System.out.println("After method: " + method.getName());
        
        return result;
    }
}
  1. 创建代理对象:通过 Proxy.newProxyInstance() 创建一个动态代理对象,并指定它所实现的接口和 InvocationHandler
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // 1. 创建原始对象
        HelloWorld helloWorld = new HelloWorldImpl();

        // 2. 创建 InvocationHandler 对象
        HelloWorldInvocationHandler handler = new HelloWorldInvocationHandler(helloWorld);

        // 3. 创建代理对象
        HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
                HelloWorld.class.getClassLoader(),
                new Class<?>[]{HelloWorld.class},
                handler
        );

        // 4. 调用代理对象的方法
        proxy.sayHello("Alice");
    }
}

运行结果:

Before method: sayHello
Hello, Alice
After method: sayHello

2. 代码分析:

  1. Proxy.newProxyInstance()
    • 第一个参数是类加载器(通常是 getClassLoader())。
    • 第二个参数是代理对象需要实现的接口数组。在这个例子中,它只实现了 HelloWorld 接口。
    • 第三个参数是实现了 InvocationHandler 的对象,在这个例子中是 HelloWorldInvocationHandler
  2. InvocationHandler.invoke()
    • 这个方法会在调用代理对象的方法时被自动调用。在 invoke() 方法中,先可以做一些前置处理(如打印日志),然后通过 method.invoke(target, args) 调用原始对象的方法,最后可以做一些后置处理。
  3. 动态代理的实现
    • JDK 动态代理的核心思想是通过反射机制,在运行时生成一个代理对象,并将方法调用转发给 InvocationHandlerProxy.newProxyInstance() 实际上会根据指定的接口创建一个代理类,这个代理类实现了指定的接口,并重写接口的方法,在方法中调用 InvocationHandler 的 invoke() 方法。

3. JDK 动态代理的限制

  1. 只能代理接口:JDK 动态代理只能用于代理接口,不能直接代理类。如果需要代理类,可以使用 CGLIB 等第三方库。
  2. 性能开销:因为每次方法调用都需要经过 InvocationHandler 的处理,所以性能上会比直接调用原始对象的方法稍微差一些。

4. 应用场景

  • AOP(面向切面编程):使用动态代理可以在不修改源代码的情况下,添加横切关注点(如日志、事务、权限控制等)。
  • 装饰模式:可以使用动态代理在运行时动态地为一个对象增加新功能。
  • 远程调用:动态代理可以用于远程方法调用(如 RMI、WebService、Dubbo 等框架中就使用了动态代理来调用远程服务)。

总结

JDK 动态代理通过反射机制在运行时动态生成代理对象,并将方法调用转发给指定的 InvocationHandler 实现类,允许在不修改源代码的情况下,对对象的方法进行增强。它适用于面向切面编程(AOP)等场景,但仅支持接口代理。如果需要更复杂的代理需求,可以考虑使用其他库,如 CGLIB。