文章目录
-
- 前言
- 使用场景
-
- 拦截器(Interceptor)模式
- 声明式接口实现
- 数据访问层(DAO)代理
- 缓存代理
- 远程服务代理
- [模拟对象(Mock Object)生成](#模拟对象(Mock Object)生成)
- 实战演示
- 写在最后
前言
Java JDK动态代理则一种强大的设计模式,它允许在运行时创建一个实现了指定接口的新类(代理类),并可以控制对原有目标对象方法的调用。在之前的博文中有讲过Cglib动态代理,但是它适用于没有实现接口的情况,并且由于需要自己生成被代理对象子类完成代理,故在非接口代理下性能上还是与JDK有一定的差距。如果我们代理对象有实现接口,可以优先选择JDK动态代理。
使用场景
拦截器(Interceptor)模式
在面向切面编程(AOP, Aspect-Oriented Programming)中,动态代理可以用来实现方法级别的拦截功能。例如,在Web服务、微服务架构中,通过创建一个实现了InvocationHandler接口的拦截器类,可以对服务接口的方法调用进行增强,如添加预处理逻辑(如权限校验、日志记录、性能监控等)、后处理逻辑(如结果封装、异常处理、事务管理等)。这样可以将与核心业务无关但又与多个业务方法紧密相关的横切关注点(cross-cutting concerns)集中管理和维护,提高代码的可重用性和模块化程度。
声明式接口实现
当需要为一组具有相同签名的方法提供统一的默认行为或额外行为时,可以通过动态代理为这些方法创建一个通用的实现类。例如,在某些框架中,用户只需定义接口,框架会自动利用JDK动态代理生成实现类,用户无需手动编写每个接口方法的具体实现。这种机制常见于服务端框架(如RPC框架)中,用于简化服务端接口的实现,或者在客户端作为服务端接口的本地代理,封装远程调用逻辑。
数据访问层(DAO)代理
在数据访问层,动态代理可以用来实现数据库操作的透明化,如连接池管理、SQL语句监控、事务控制等。当客户端调用DAO方法时,实际执行的是代理对象的方法,代理对象在调用真实DAO之前或之后插入必要的数据访问支持逻辑,如获取连接、开启事务、关闭连接、回滚事务等。这种设计使得业务代码无需关心底层数据访问细节,提高了代码的整洁度和可维护性。
缓存代理
对于计算密集型或IO密集型的操作,动态代理可以用来实现方法结果的缓存。代理对象在调用真实方法前先检查缓存是否存在对应的结果,如果存在则直接返回缓存值,否则调用真实方法获取结果并将其存入缓存,供后续调用使用。这种策略在处理重复查询、减少数据库压力等方面非常有效,常见于数据缓存、模板引擎缓存等场景。
远程服务代理
在分布式系统中,动态代理可以用来封装远程服务调用的细节,如网络通信、序列化反序列化、错误处理等。客户端通过代理对象调用远程服务就像调用本地方法一样,无需关心底层通信协议和远程交互过程。RMI(Remote Method Invocation)等分布式技术框架就大量使用了JDK动态代理来实现客户端对远程服务的透明访问。
模拟对象(Mock Object)生成
在单元测试中,为了隔离待测试组件与其他依赖组件的交互,可以使用动态代理生成模拟对象替代真实的依赖对象。模拟对象根据测试需求实现特定的行为(如返回预期结果、触发特定异常、验证方法调用次数等),帮助开发者专注于测试目标组件的行为,而无需启动或配置实际依赖环境。
实战演示
本次演示就代理实现了接口的普通类,并在代理调用方法中增加一些日志信息即可。对于其他的JDBC处理或者缓存处理也是一样的道理,直接在代理调用方法中增加业务逻辑就行。
定义服务接口
首先,我们需要有一个公共接口,所有需要被代理的对象都必须实现这个接口。例如,假设有一个CalculatorService接口:
java
/**
* CalculatorService
* @author senfel
* @version 1.0
* @date 2024/4/19 15:33
*/
public interface CalculatorService {
/**
* add
* @param a
* @param b
* @author senfel
* @date 2024/4/19 15:50
* @return int
*/
int add(int a, int b);
}
实现服务接口
创建一个实现了该接口的类作为实际的服务提供者(即目标对象CalculatorServiceImpl ):
java
/**
* CalculatorServiceImpl
* @author senfel
* @version 1.0
* @date 2024/4/19 15:34
*/
public class CalculatorServiceImpl implements CalculatorService {
/**
* add
* @param a
* @param b
* @author senfel
* @date 2024/4/19 15:50
* @return int
*/
@Override
public int add(int a, int b) {
return 0;
}
}
创建InvocationHandler
实现java.lang.reflect.InvocationHandler接口,这个接口只有一个方法invoke()。
在invoke()方法中,编写代理逻辑,如前置处理、方法调用、后置处理等:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* logInvocationHandler
* @author senfel
* @version 1.0
* @date 2024/4/19 15:36
*/
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.err.println("Calling method: " + method.getName());
// 方法调用前的日志记录或其他操作
long start = System.currentTimeMillis();
// 调用目标方法
Object result = method.invoke(target, args);
// 方法调用后的日志记录或其他操作
long duration = System.currentTimeMillis() - start;
System.err.printf("Method %s executed in %d ms%n", method.getName(), duration);
return result;
}
}
创建代理对象
使用java.lang.reflect.Proxy类的newProxyInstance()静态方法来创建代理对象。
该方法接受三个参数:
ClassLoader loader: 用于加载代理类的类加载器,通常传递目标对象的类加载器。
Class<?>[] interfaces: 目标对象所实现的所有接口的数组。
InvocationHandler handler: 实现了InvocationHandler接口的对象,用于处理代理方法调用。
创建SimpleCalculator对象的代理,并通过代理对象调用方法:
java
/**
* JDKProxyTest
* @author senfel
* @version 1.0
* @date 2024/4/19 15:39
*/
@SpringBootTest
public class JDKProxyTest {
@Test
public void test() {
CalculatorService proxy = (CalculatorService)Proxy.newProxyInstance(
CalculatorServiceImpl.class.getClassLoader(),
new Class<?>[]{CalculatorService.class},
new LogInvocationHandler(new CalculatorServiceImpl()));
int add = proxy.add(1, 2);
System.err.println(add);
}
}
验证JDK动态代理
直接执行JDKProxyTest中的test()方法:
写在最后
这就是一个完整的Java JDK动态代理实例,展示了如何利用JDK提供的动态代理机制为已有接口实现添加额外行为(如日志记录、权限检查、缓存等)。
在这个例子中,LogInvocationHandler添加了方法调用的前后日志记录功能。当通过代理对象调用add()方法时,实际上会触发invoke()方法,在执行目标方法之前打印方法调用信息,执行之后记录方法执行耗时。