SpringBoot使用设计模式一JDK动态代理
一、前言
在日常开发中,我们经常会遇到这样的场景:需要在不修改原有业务代码的前提下,为核心业务逻辑添加额外功能,比如日志记录、权限校验、事务管理等。
如果直接在业务方法中嵌入这些非核心逻辑,会导致代码耦合度高、可读性差,且重复代码冗余。例如:
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
// 日志记录(非核心逻辑)
System.out.println("开始执行addUser方法,参数:" + username);
long startTime = System.currentTimeMillis();
// 核心业务逻辑
System.out.println("添加用户:" + username);
// 性能监控(非核心逻辑)
long costTime = System.currentTimeMillis() - startTime;
System.out.println("addUser方法执行完成,耗时:" + costTime + "ms");
}
}
这种写法会让非核心逻辑与业务逻辑深度绑定,后续修改日志格式或添加新的横切逻辑时,需要改动所有相关业务类。针对这种场景,我们可以使用JDK动态代理模式来解决。
二、JDK动态代理模式
JDK动态代理是Java原生支持的代理模式实现,属于动态代理的一种,它允许在运行时动态生成代理对象,无需手动编写代理类。
核心概念
JDK动态代理模式包含三个核心角色:
- 目标接口(Target Interface):目标对象实现的接口,
JDK动态代理必须基于接口实现; - 目标对象(Target Object):被代理的原始对象,包含核心业务逻辑;
- InvocationHandler(调用处理器):代理对象的核心逻辑处理器,负责定义代理行为,如添加额外功能、调用目标方法。
工作原理
- 客户端通过代理对象调用方法;
- 代理对象将调用转发给
InvocationHandler的invoke方法; - 在
invoke方法中,先执行额外的增强逻辑(如日志、权限校验); - 通过反射调用目标对象的核心方法;
- 执行后续增强逻辑(如结果处理、性能监控),并返回结果给客户端。
适用场景
- 需为多个类统一添加横切逻辑(日志、权限、事务、性能监控);
- 不允许修改原有业务代码,需基于现有接口扩展功能;
- 希望动态切换增强逻辑,无需重新编译代码。
优点
- 无需手动编写代理类,减少代码冗余;
- 运行时动态生成代理对象,灵活性高;
- 遵循开闭原则,扩展功能无需修改目标对象代码;
与SpringBoot、Spring框架无缝集成,是AOP的底层核心实现之一。
缺点
- 仅支持基于接口的代理,目标类必须实现至少一个接口;
- 无法代理接口中未声明的方法(如目标类的私有方法、静态方法);
- 反射调用存在轻微性能损耗(
JDK8+后已大幅优化,日常场景可忽略)。
三、实现案例
下面以Spring Boot项目为例,通过JDK动态代理为用户服务添加日志记录和性能监控功能。
3.1 定义目标接口与目标对象
首先定义业务接口和对应的实现类(核心业务逻辑):
目标接口(UserService)
public interface UserService {
/**
* 添加用户
* @param username 用户名
* @return 操作结果
*/
String addUser(String username);
/**
* 查询用户
* @param username 用户名
* @return 用户信息
*/
String getUser(String username);
}
目标对象(UserServiceImpl)
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public String addUser(String username) {
// 核心业务逻辑
return "添加用户成功:" + username;
}
@Override
public String getUser(String username) {
// 核心业务逻辑
return "查询到用户:" + username;
}
}
3.2 实现InvocationHandler(调用处理器)
创建调用处理器类,定义代理对象的增强逻辑(日志记录+性能监控):
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* JDK动态代理处理器:添加日志记录和性能监控
*/
@Slf4j
public class LogPerformanceInvocationHandler implements InvocationHandler {
// 目标对象(被代理的原始业务对象)
private final Object target;
// 构造方法注入目标对象
public LogPerformanceInvocationHandler(Object target) {
this.target = target;
}
/**
* 代理对象的核心处理方法
* @param proxy 代理对象(一般不直接使用)
* @param method 目标方法
* @param args 目标方法参数
* @return 目标方法返回值
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 前置增强:日志记录+计时开始
String methodName = target.getClass().getSimpleName() + "." + method.getName();
log.info("【{}】开始执行,参数:{}", methodName, args);
long startTime = System.currentTimeMillis();
Object result = null;
try {
// 2. 调用目标对象的核心业务方法
result = method.invoke(target, args);
// 3. 后置增强:日志记录执行结果
log.info("【{}】执行成功,返回结果:{}", methodName, result);
} catch (Exception e) {
// 4. 异常增强:记录异常信息
log.error("【{}】执行失败,异常信息:{}", methodName, e.getMessage(), e);
throw e; // 抛出异常,不影响原有业务异常处理
} finally {
// 5. 最终增强:性能监控(无论成功失败都执行)
long costTime = System.currentTimeMillis() - startTime;
log.info("【{}】执行完成,耗时:{}ms", methodName, costTime);
}
return result;
}
}
3.3 创建代理对象工厂
创建工厂类,封装代理对象的生成逻辑,方便Spring容器管理:
import org.springframework.stereotype.Component;
import java.lang.reflect.Proxy;
/**
* JDK动态代理工厂:用于生成代理对象
*/
@Component
public class JdkProxyFactory {
/**
* 生成代理对象
* @param target 目标对象(必须实现接口)
* @return 代理对象
*/
@SuppressWarnings("unchecked")
public <T> T createProxy(T target) {
// 验证目标对象是否实现接口
Class<?>[] interfaces = target.getClass().getInterfaces();
if (interfaces == null || interfaces.length == 0) {
throw new IllegalArgumentException("目标对象必须实现至少一个接口");
}
// 生成代理对象:参数1=类加载器,参数2=目标接口,参数3=调用处理器
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
interfaces,
new LogPerformanceInvocationHandler(target)
);
}
}
3.4 SpringBoot配置与使用
通过Spring配置类将代理对象注入容器,确保业务代码通过代理对象调用:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ProxyConfig {
/**
* 注入目标对象(业务实现类)
*/
@Bean
public UserService userServiceImpl() {
return new UserServiceImpl();
}
/**
* 注入代理对象(通过工厂生成)
*/
@Bean
public UserService userServiceProxy(JdkProxyFactory proxyFactory, UserService userServiceImpl) {
return proxyFactory.createProxy(userServiceImpl);
}
}
3.5 控制器层调用(客户端)
创建Controller测试代理对象的使用:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
// 注入代理对象(而非原始目标对象)
@Autowired
private UserService userServiceProxy;
@PostMapping("/add")
public String addUser(@RequestParam String username) {
return userServiceProxy.addUser(username);
}
@GetMapping("/get")
public String getUser(@RequestParam String username) {
return userServiceProxy.getUser(username);
}
}
3.6 测试结果
启动SpringBoot项目,调用接口测试:
控制台输出日志(增强逻辑已生效):
【UserServiceImpl.addUser】开始执行,参数:[张三]
【UserServiceImpl.addUser】执行成功,返回结果:添加用户成功:张三
【UserServiceImpl.addUser】执行完成,耗时:2ms
【UserServiceImpl.getUser】开始执行,参数:[李四]
【UserServiceImpl.getUser】执行成功,返回结果:查询到用户:李四
【UserServiceImpl.getUser】执行完成,耗时:1ms
四、JDK动态代理与Spring的整合进阶
在实际开发中,我们可以结合Spring的Bean生命周期和注解,进一步优化JDK动态代理的使用。
4.1 基于BeanPostProcessor自动生成代理
通过Spring的BeanPostProcessor接口,在Bean初始化时自动为指定类型的Bean生成代理对象,无需手动配置每个代理Bean:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* 自动代理处理器:为所有实现UserService接口的Bean生成代理
*/
@Component
public class UserServiceProxyProcessor implements BeanPostProcessor {
@Autowired
private JdkProxyFactory proxyFactory;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 对所有UserService接口的实现类生成代理对象
if (bean instanceof UserService && !Proxy.isProxyClass(bean.getClass())) {
return proxyFactory.createProxy(bean);
}
return bean;
}
}
此时,ProxyConfig配置类可以删除,Spring会自动为UserServiceImpl生成代理对象,Controller直接注入UserService即可使用代理功能。
4.2 结合注解实现灵活增强
定义自定义注解,标记需要增强的方法,实现更精细的代理控制:
自定义注解(@LogPerformance)
import java.lang.annotation.*;
/**
* 标记需要添加日志和性能监控的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogPerformance {
}
改造InvocationHandler
在invoke方法中判断目标方法是否标注该注解,仅对标注的方法进行增强:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断方法是否标注@LogPerformance注解
if (!method.isAnnotationPresent(LogPerformance.class)) {
// 未标注则直接调用目标方法,不增强
return method.invoke(target, args);
}
// 标注了注解则执行增强逻辑(与之前一致)
String methodName = target.getClass().getSimpleName() + "." + method.getName();
log.info("【{}】开始执行,参数:{}", methodName, args);
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = method.invoke(target, args);
log.info("【{}】执行成功,返回结果:{}", methodName, result);
} catch (Exception e) {
log.error("【{}】执行失败,异常信息:{}", methodName, e.getMessage(), e);
throw e;
} finally {
long costTime = System.currentTimeMillis() - startTime;
log.info("【{}】执行完成,耗时:{}ms", methodName, costTime);
}
return result;
}
在目标方法上添加注解
@Service
public class UserServiceImpl implements UserService {
@Override
@LogPerformance // 仅该方法会被增强
public String addUser(String username) {
return "添加用户成功:" + username;
}
@Override
// 该方法不会被增强
public String getUser(String username) {
return "查询到用户:" + username;
}
}
五、JDK动态代理的常见问题与注意事项
5.1 目标类必须实现接口
JDK动态代理基于接口生成代理对象,若目标类未实现接口,会抛出IllegalArgumentException。此时可选择CGLIB代理(基于继承实现)。
5.2 私有方法无法被代理
JDK动态代理仅能代理接口中声明的公共方法(public),私有方法(private)、保护方法(protected)无法被代理,因为这些方法无法通过反射被外部调用。
5.3 性能优化建议
- 缓存代理对象:避免频繁生成代理对象,可在工厂类中缓存已生成的代理;
- 减少反射开销:对于高频调用的方法,可通过缓存
Method对象优化反射性能; - 合理使用增强逻辑:避免在
invoke方法中添加过重的业务逻辑,影响代理效率。
5.4 与CGLIB代理的区别
| 对比维度 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 实现方式 | 基于接口(反射) | 基于继承(ASM字节码) |
| 目标类要求 | 必须实现接口 | 无需实现接口(不能是final) |
| 方法支持 | 仅支持接口中声明的方法 | 支持所有非final方法 |
| 性能 | JDK8+后性能优异 | 生成代理类较慢,调用较快 |
| 依赖 | Java原生支持,无需额外依赖 | 需引入cglib依赖 |
Spring框架默认优先使用JDK动态代理,若目标类无接口则自动切换为CGLIB代理。
六、总结
JDK动态代理模式是解决代码耦合、实现横切逻辑复用的优秀方案,尤其适用于Spring Boot项目中的日志记录、权限校验、事务管理等场景。
通过本文的案例实现,我们可以看到:
JDK动态代理无需修改原有业务代码,即可灵活扩展功能,符合开闭原则;- 与
Spring Boot的整合简单高效,可通过BeanPostProcessor、注解等方式优化使用体验; - 核心优势在于动态生成代理对象、减少代码冗余,同时保持业务逻辑的纯粹性。
在实际开发中,JDK动态代理不仅可以单独使用,更是Spring AOP、声明式事务等核心功能的底层支撑。掌握JDK动态代理的原理与使用,能帮助我们更好地理解Spring框架的设计思想,写出更优雅、可维护的代码。

浙公网安备 33010602011771号