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(调用处理器):代理对象的核心逻辑处理器,负责定义代理行为,如添加额外功能、调用目标方法。

工作原理

  1. 客户端通过代理对象调用方法;
  2. 代理对象将调用转发给InvocationHandlerinvoke方法;
  3. invoke方法中,先执行额外的增强逻辑(如日志、权限校验);
  4. 通过反射调用目标对象的核心方法;
  5. 执行后续增强逻辑(如结果处理、性能监控),并返回结果给客户端。

适用场景

  1. 需为多个类统一添加横切逻辑(日志、权限、事务、性能监控);
  2. 不允许修改原有业务代码,需基于现有接口扩展功能;
  3. 希望动态切换增强逻辑,无需重新编译代码。

优点

  1. 无需手动编写代理类,减少代码冗余;
  2. 运行时动态生成代理对象,灵活性高;
  3. 遵循开闭原则,扩展功能无需修改目标对象代码;

SpringBootSpring框架无缝集成,是AOP的底层核心实现之一。

缺点

  1. 仅支持基于接口的代理,目标类必须实现至少一个接口;
  2. 无法代理接口中未声明的方法(如目标类的私有方法、静态方法);
  3. 反射调用存在轻微性能损耗(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的整合进阶

在实际开发中,我们可以结合SpringBean生命周期和注解,进一步优化JDK动态代理的使用。

4.1 基于BeanPostProcessor自动生成代理

通过SpringBeanPostProcessor接口,在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 性能优化建议

  1. 缓存代理对象:避免频繁生成代理对象,可在工厂类中缓存已生成的代理;
  2. 减少反射开销:对于高频调用的方法,可通过缓存Method对象优化反射性能;
  3. 合理使用增强逻辑:避免在invoke方法中添加过重的业务逻辑,影响代理效率。

5.4 与CGLIB代理的区别

对比维度 JDK动态代理 CGLIB代理
实现方式 基于接口(反射) 基于继承(ASM字节码)
目标类要求 必须实现接口 无需实现接口(不能是final)
方法支持 仅支持接口中声明的方法 支持所有非final方法
性能 JDK8+后性能优异 生成代理类较慢,调用较快
依赖 Java原生支持,无需额外依赖 需引入cglib依赖

Spring框架默认优先使用JDK动态代理,若目标类无接口则自动切换为CGLIB代理。

六、总结

JDK动态代理模式是解决代码耦合、实现横切逻辑复用的优秀方案,尤其适用于Spring Boot项目中的日志记录、权限校验、事务管理等场景。

通过本文的案例实现,我们可以看到:

  1. JDK动态代理无需修改原有业务代码,即可灵活扩展功能,符合开闭原则;
  2. Spring Boot的整合简单高效,可通过BeanPostProcessor、注解等方式优化使用体验;
  3. 核心优势在于动态生成代理对象、减少代码冗余,同时保持业务逻辑的纯粹性。

在实际开发中,JDK动态代理不仅可以单独使用,更是Spring AOP、声明式事务等核心功能的底层支撑。掌握JDK动态代理的原理与使用,能帮助我们更好地理解Spring框架的设计思想,写出更优雅、可维护的代码。

posted @ 2025-12-17 22:24  夏尔_717  阅读(2)  评论(0)    收藏  举报