Java 动态代理
1. 概念 (Concept)
Java 动态代理是一种强大的机制,它允许程序在运行时动态地创建一个代理对象,该代理对象可以包装一个真实对象,并在不修改原始代码的情况下,拦截对真实对象方法的调用。这使得我们可以在方法调用前后注入自定义的逻辑,如日志记录、性能监控、事务管理、权限检查等,是面向切面编程 (AOP) 的核心实现方式之一。
2. 核心组件 (Core Components)
Java 的动态代理主要由以下两个核心部分组成:
java.lang.reflect.Proxy: 这是创建动态代理实例的主类。通过其静态方法newProxyInstance(),我们可以动态地生成一个代理类并创建其实例。java.lang.reflect.InvocationHandler: 这是一个接口,我们通过实现该接口来定义代理的行为。它只有一个invoke()方法,所有对代理对象的方法调用都会被转发到这个方法中进行处理。
3. 工作原理 (How it Works)
动态代理的工作流程可以概括为以下几个步骤:
- 定义一个接口:代理对象和真实对象都需要实现这个共同的接口。
- 创建真实对象:这是我们希望代理的业务逻辑实现。
- 创建
InvocationHandler实现:在这个实现类的invoke方法中,我们编写代理逻辑。invoke方法会接收到被调用的方法 (Method) 和参数 (args),我们可以在这里调用真实对象的方法,并在其前后添加额外的操作。 - 创建代理对象:使用
Proxy.newProxyInstance()方法,传入类加载器、接口数组和InvocationHandler实例,来生成代理对象。 - 通过代理调用方法:客户端代码现在通过代理对象来调用方法,所有调用都会被
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类中,主要通过反射机制来实现代理模式。该方法需要三个参数:
ClassLoader loader: 这是一个类加载器(Class Loader),用于定义由 Proxy 类生成的代理类。通常可以传递当前线程的上下文类加载器,可以通过Thread.currentThread().getContextClassLoader()获取。
Class<?>[] interfaces: 这是一个接口数组,表示要被代理的接口列表。生成的代理对象将实现这些接口的所有方法。需要注意的是,Java 不支持直接代理具体的类,只能代理接口。
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 等第三方库)。