Java 动态代理

1. 概念 (Concept)

Java 动态代理是一种强大的机制,它允许程序在运行时动态地创建一个代理对象,该代理对象可以包装一个真实对象,并在不修改原始代码的情况下,拦截对真实对象方法的调用。这使得我们可以在方法调用前后注入自定义的逻辑,如日志记录、性能监控、事务管理、权限检查等,是面向切面编程 (AOP) 的核心实现方式之一。

2. 核心组件 (Core Components)

Java 的动态代理主要由以下两个核心部分组成:

  • java.lang.reflect.Proxy: 这是创建动态代理实例的主类。通过其静态方法 newProxyInstance(),我们可以动态地生成一个代理类并创建其实例。
  • java.lang.reflect.InvocationHandler: 这是一个接口,我们通过实现该接口来定义代理的行为。它只有一个 invoke() 方法,所有对代理对象的方法调用都会被转发到这个方法中进行处理。

3. 工作原理 (How it Works)

动态代理的工作流程可以概括为以下几个步骤:

  1. 定义一个接口:代理对象和真实对象都需要实现这个共同的接口。
  2. 创建真实对象:这是我们希望代理的业务逻辑实现。
  3. 创建 InvocationHandler 实现:在这个实现类的 invoke 方法中,我们编写代理逻辑。invoke 方法会接收到被调用的方法 (Method) 和参数 (args),我们可以在这里调用真实对象的方法,并在其前后添加额外的操作。
  4. 创建代理对象:使用 Proxy.newProxyInstance() 方法,传入类加载器、接口数组和 InvocationHandler 实例,来生成代理对象。
  5. 通过代理调用方法:客户端代码现在通过代理对象来调用方法,所有调用都会被 InvocationHandler 拦截和处理。

4. 示意图 (Diagram)

下面是一个简化的 ASCII 示意图,展示了动态代理中各个组件之间的关系:

+-----------+       +-----------------+       +-------------------+
|  Client   | ----> |  Proxy Instance | ----> | InvocationHandler |
+-----------+       | (implements     |       | (invoke method)   |
                    |  SomeInterface) |       +-------------------+
                    +-----------------+               |
                                                      | (通过反射调用)
                                                      v
                                                +----------------+
                                                |  Target Object |
                                                | (implements    |
                                                |  SomeInterface)|
                                                +----------------+
  • Client: 客户端代码,它持有代理对象的引用。
  • Proxy Instance: 由 Proxy.newProxyInstance() 动态生成的代理对象。
  • InvocationHandler: 调用处理器,包含了代理逻辑,并持有真实对象的引用。
  • Target Object: 真实的服务对象,包含了核心业务逻辑。

5. 代码示例 (Code Example)

下面是一个完整的示例,演示了如何为一个 UserService 创建一个日志记录代理。

步骤 1: 定义接口

public interface UserService {
    void addUser(String username);
    String findUser(String username);
}

步骤 2: 创建真实对象

public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("--> [Database] Adding user: " + username);
    }
 
    @Override
    public String findUser(String username) {
        System.out.println("--> [Database] Finding user: " + username);
        return "Found " + username;
    }
}

步骤 3: 创建 InvocationHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
 
public class LoggingInvocationHandler implements InvocationHandler {
    private final Object target;
 
    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(
            String.format("==> [Log] Method '%s' called at %s", method.getName(), new Date())
        );
 
        // 调用真实对象的方法
        Object result = method.invoke(target, args);
 
        System.out.println(
            String.format("<== [Log] Method '%s' finished.", method.getName())
        );
 
        // 可以对返回结果进行处理
        if (result != null) {
            return result.toString().toUpperCase();
        }
        return null;
    }
}

步骤 4: 创建并使用代理

Proxy.newProxyInstance介绍

Proxy.newProxyInstance 是 Java 中用于创建动态代理实例的方法。它位于 java.lang.reflect.Proxy 类中,主要通过反射机制来实现代理模式。该方法需要三个参数:

  1. ClassLoader loader: 这是一个类加载器(Class Loader),用于定义由 Proxy 类生成的代理类。通常可以传递当前线程的上下文类加载器,可以通过 Thread.currentThread().getContextClassLoader() 获取。

  2. Class<?>[] interfaces: 这是一个接口数组,表示要被代理的接口列表。生成的代理对象将实现这些接口的所有方法。需要注意的是,Java 不支持直接代理具体的类,只能代理接口。

  3. InvocationHandler h: 这是一个调用处理器(Invocation Handler),它是实现了 java.lang.reflect.InvocationHandler 接口的对象。当代理对象上调用某个方法时,实际会调用这个 InvocationHandler 对象的 invoke 方法。在这个方法里,你可以编写自定义的行为逻辑,比如在目标方法执行前后添加额外的操作。

Note

Proxy.newProxyInstance()中可以通过反射拿到要代理对象的所有接口,无需手动指定

例如:

Hello proxyHello = (Hello) Proxy.newProxyInstance(
		hello.getClass().getClassLoader(),
		hello.getClass().getInterfaces(),
		handler
);
import java.lang.reflect.Proxy;
 
public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 1. 创建真实对象
        UserService realUserService = new UserServiceImpl();
 
        // 2. 创建 InvocationHandler
        InvocationHandler handler = new LoggingInvocationHandler(realUserService);
 
        // 3. 创建代理对象
        UserService proxyUserService = (UserService) Proxy.newProxyInstance(
                realUserService.getClass().getClassLoader(),
                new Class<?>[]{UserService.class}, // 必须是接口
                handler
        );
 
        // 4. 通过代理对象调用方法
        proxyUserService.addUser("Alice");
        System.out.println("--------------------");
        String user = proxyUserService.findUser("Bob");
        System.out.println("Final result from proxy: " + user);
    }
}

预期输出

==> [Log] Method 'addUser' called at [current date]
--> [Database] Adding user: Alice
<== [Log] Method 'addUser' finished.
--------------------
==> [Log] Method 'findUser' called at [current date]
--> [Database] Finding user: Bob
<== [Log] Method 'findUser' finished.
Final result from proxy: FOUND BOB

6. 优缺点 (Pros and Cons)

优点

  • 高度解耦: 代理逻辑(如日志、安全)与业务逻辑完全分离,符合单一职责原则。
  • 通用性强: 可以为任何实现了接口的类创建代理,具有很好的复用性。
  • 非常灵活: 在运行时动态生成,可以根据条件决定是否需要代理以及如何代理。

缺点

  • 性能开销: 由于使用了反射,方法调用链变长,相比直接调用会有一定的性能损失。
  • 必须实现接口: JDK 动态代理的硬性要求是目标对象必须实现一个或多个接口。对于没有实现接口的普通类,无法使用此方法(此时可以考虑使用 CGLIB 等第三方库)。