Spring代理与动态代理

Spring代理机制:BeanPostProcessor的"魔法"是如何炼成的?

Spring的代理机制就像一个"魔术师",它能让普通的Bean突然拥有超能力(比如自动加事务、日志等)。但它的"魔术道具"其实是两个核心扩展点:BeanPostProcessorBeanFactoryPostProcessor。今天我们就用"厨房做菜"的比喻,彻底搞懂它们是如何配合实现代理的!


1. Spring代理的本质:给Bean"套壳"

想象你有一个普通的土豆(Bean),但你想让它变成薯条(增强后的Bean)。Spring的代理机制就像给土豆"裹面粉"------套一层壳(代理对象),让土豆变成薯条。这个"裹面粉"的过程,就是动态代理

Spring支持两种"裹面粉"方式:

  • JDK动态代理:要求土豆必须实现接口(比如土豆必须声明自己是"可炸的")。
  • CGLIB代理:直接给土豆套壳,不要求接口。

1.1 JDK动态代理:只认接口的"强迫症患者"

场景:

假设你有一个UserService接口和它的实现类,你想给它的方法加日志。

代码示例:

typescript 复制代码
// 1. 接口(必须!)
public interface UserService {
    void addUser();
}

// 2. 实现类
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }
}

// 3. 调用处理器(InvocationHandler)
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.out.println("== JDK代理:方法执行前 ==");
        Object result = method.invoke(target, args);
        System.out.println("== JDK代理:方法执行后 ==");
        return result;
    }
}

// 4. 生成代理对象
public class JdkProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), // 必须要有接口!
                new LogInvocationHandler(target)
        );
        proxy.addUser();
    }
}

运行结果:

复制代码
== JDK代理:方法执行前 ==
添加用户
== JDK代理:方法执行后 ==

1.2. CGLIB动态代理:不挑食的"万能套壳王"

场景:

假设你有一个没有实现接口的OrderService类,你想直接给它加日志。

代码示例:

typescript 复制代码
// 1. 目标类(不需要接口!)
public class OrderService {
    public void createOrder() {
        System.out.println("创建订单");
    }
}

// 2. 方法拦截器(MethodInterceptor)
public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("== CGLIB代理:方法执行前 ==");
        Object result = proxy.invokeSuper(obj, args); // 调用父类方法
        System.out.println("== CGLIB代理:方法执行后 ==");
        return result;
    }
}

// 3. 生成代理对象
public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class); // 继承目标类
        enhancer.setCallback(new LogMethodInterceptor());
        OrderService proxy = (OrderService) enhancer.create();
        proxy.createOrder();
    }
}

运行结果:

objectivec 复制代码
== CGLIB代理:方法执行前 ==
创建订单
== CGLIB代理:方法执行后 ==

1.3. 对比表格:JDK动态代理 vs CGLIB动态代理

特性 JDK动态代理 CGLIB动态代理
是否需要接口 必须实现接口 不需要接口
实现方式 基于接口生成代理类 通过继承生成子类代理
性能 生成代理较快,调用较慢(反射) 生成代理较慢,调用较快(直接调用)
依赖 无需额外依赖(Java自带) 需要引入CGLIB库(如cglib或Spring-core)
限制 无法代理没有接口的类 无法代理final类或final方法
适用场景 代理有接口的类(如Service层接口) 代理没有接口的类(如Controller、第三方类)

1.4. 如何选择?Spring的默认策略

Spring的代理选择规则:

  • 如果目标类实现了接口:默认使用JDK动态代理。
  • 如果目标类没有接口:自动使用CGLIB。
  • 强制使用CGLIB:可以通过配置@EnableAspectJAutoProxy(proxyTargetClass = true),让Spring始终使用CGLIB。

5. 终极总结

  • JDK动态代理:适合代理有接口的类,是Java的亲儿子,但有点"挑食"。
  • CGLIB动态代理:不挑食,但需要第三方库支持,且不能代理final方法。

下次写代码时,根据目标类是否有接口,选择你的"套壳工具"吧!️


2. 谁负责"套壳"?BeanPostProcessor!

BeanPostProcessor 是Spring生命周期中的"厨师长",它专门在Bean初始化前后"加工"Bean。当Spring发现某个Bean需要被代理(比如加了@Transactional),就会通过BeanPostProcessor来生成代理对象。

代码示例:手动实现一个代理生成器

typescript 复制代码
@Component
public class MyProxyCreator implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 只对UserService生成代理
        if (bean instanceof UserService) {
            // 用CGLIB创建代理(假装是CGLIB)
            return Enhancer.create(
                bean.getClass(),
                (MethodInterceptor) (obj, method, args, proxy) -> {
                    System.out.println("== 代理:方法执行前 ==");
                    Object result = proxy.invokeSuper(obj, args);
                    System.out.println("== 代理:方法执行后 ==");
                    return result;
                }
            );
        }
        return bean;
    }
}

效果:所有UserService的Bean都会被代理,每次调用方法都会打印日志。


3. BeanFactoryPostProcessor:提前修改"菜谱"

BeanFactoryPostProcessor 是"采购员",它在Bean实例化之前修改Bean的"菜谱"(Bean的定义)。虽然它不直接生成代理,但可以提前"标记"哪些Bean需要被代理。

代码示例:偷偷给Bean加上事务属性

typescript 复制代码
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 找到所有Bean的定义
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            // 如果是UserService,标记需要事务
            if (beanDef.getBeanClassName().contains("UserService")) {
                beanDef.setAttribute("needsTransaction", true);
            }
        }
    }
}

4. 完整流程:Spring代理的"厨房工作流"

  1. 采购员进场(BeanFactoryPostProcessor)
    修改Bean的定义(比如标记哪些Bean需要代理)。
  2. 厨师长加工(BeanPostProcessor)
    根据Bean的定义,在初始化后生成代理对象。
  3. 上菜(Bean使用)
    用户拿到的是代理后的Bean,调用方法时会触发代理逻辑。

5. 实际案例:Spring AOP的自动代理

Spring AOP的核心是**

AnnotationAwareAspectJAutoProxyCreator**,它就是一个BeanPostProcessor!它的工作流程如下:

  1. 扫描所有@Aspect注解的类。
  2. 匹配切入点(Pointcut),找到需要代理的Bean。
  3. 生成代理对象(JDK或CGLIB)。

代码示例:用AOP实现日志

less 复制代码
@Aspect
@Component
public class LogAspect {

    // 定义切入点:所有Service的方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // 前置通知
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("方法执行前:" + joinPoint.getSignature().getName());
    }
}

效果:所有Service层的Bean都会被自动代理,并在方法执行前打印日志。

6 spring中代理的使用场景

6.1. Spring中动态代理的核心场景

Spring的动态代理主要用在以下几个地方:

  1. AOP(面向切面编程)
  2. 事务管理(@Transactional)
  3. 缓存(@Cacheable)
  4. 安全控制(Spring Security)
  5. 异步方法(@Async)
  6. 自定义代理(通过BeanPostProcessor)

接下来,我们逐一展开,看看这些场景是如何使用动态代理的!


6.2. AOP(面向切面编程)

场景:

你想在Service层的方法执行前后打印日志。

实现方式:

Spring AOP默认使用动态代理(JDK或CGLIB)来生成代理对象,并在代理对象中执行切面逻辑。

代码示例:

java 复制代码
@Aspect
@Component
public class LogAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("方法执行前!");
    }
}

@Service
public class UserService {
    public void addUser() {
        System.out.println("添加用户");
    }
}

代理机制:

  • 如果UserService实现了接口,Spring会使用JDK动态代理
  • 如果没有接口,Spring会使用CGLIB动态代理

6.3. 事务管理(@Transactional)

场景:

你想在Service层的方法上加事务。

实现方式:

Spring通过动态代理为@Transactional注解的方法生成代理对象,在方法执行前后开启和提交/回滚事务。

代码示例:

typescript 复制代码
@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Transactional
    public void createOrder() {
        orderRepository.save(new Order());
        System.out.println("订单创建成功!");
    }
}

代理机制:

  • Spring会为OrderService生成代理对象。
  • 在createOrder方法执行前开启事务,执行后提交事务(如果抛出异常则回滚)。

6.4. 缓存(@Cacheable)

场景:

你想缓存某个方法的返回值。

实现方式:

Spring通过动态代理为@Cacheable注解的方法生成代理对象,在方法执行前检查缓存,如果缓存命中则直接返回缓存值。

代码示例:

kotlin 复制代码
@Service
public class ProductService {

    @Cacheable("products")
    public Product getProductById(Long id) {
        System.out.println("查询数据库...");
        return new Product(id, "手机");
    }
}

代理机制:

  • Spring会为ProductService生成代理对象。
  • 在getProductById方法执行前检查缓存,如果缓存命中则直接返回缓存值,否则执行方法并将结果缓存。

6.5. 安全控制(Spring Security)

场景:

你想在方法执行前检查用户权限。

实现方式:

Spring Security通过动态代理为@PreAuthorize注解的方法生成代理对象,在方法执行前检查权限。

代码示例:

kotlin 复制代码
@Service
public class AdminService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long userId) {
        System.out.println("删除用户:" + userId);
    }
}

代理机制:

  • Spring会为AdminService生成代理对象。
  • 在deleteUser方法执行前检查用户权限,如果权限不足则抛出异常。

6.6. 异步方法(@Async)

场景:

你想让某个方法异步执行。

实现方式:

Spring通过动态代理为@Async注解的方法生成代理对象,在方法执行时提交到线程池中异步执行。

代码示例:

typescript 复制代码
@Service
public class NotificationService {

    @Async
    public void sendNotification(String message) {
        System.out.println("发送通知:" + message);
    }
}

代理机制:

  • Spring会为NotificationService生成代理对象。
  • 在sendNotification方法执行时,提交到线程池中异步执行。

7. 自定义代理(通过BeanPostProcessor)

场景:

你想为某些Bean生成自定义代理。

实现方式:

通过实现BeanPostProcessor接口,在Bean初始化前后生成代理对象。

代码示例:

typescript 复制代码
@Component
public class MyProxyCreator implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof UserService) {
            return Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("== 自定义代理:方法执行前 ==");
                    Object result = method.invoke(bean, args);
                    System.out.println("== 自定义代理:方法执行后 ==");
                    return result;
                }
            );
        }
        return bean;
    }
}

代理机制:

  • 在UserService初始化后生成代理对象。
  • 每次调用UserService的方法时,都会执行自定义逻辑。

8. 总结:Spring中动态代理的应用场景

场景 实现方式 动态代理类型
AOP 通过@Aspect和@Before等注解 JDK或CGLIB
事务管理 通过@Transactional注解 JDK或CGLIB
缓存 通过@Cacheable注解 JDK或CGLIB
安全控制 通过@PreAuthorize注解 JDK或CGLIB
异步方法 通过@Async注解 JDK或CGLIB
自定义代理 通过BeanPostProcessor实现 JDK或CGLIB

7. 总结:代理机制的核心

  • BeanPostProcessor:实际生成代理对象的"厨师长"。
  • BeanFactoryPostProcessor:提前修改Bean定义的"采购员"。
  • 动态代理:JDK动态代理(接口)和CGLIB(类)是具体"裹面粉"技术。
相关推荐
kkk哥13 分钟前
基于springboot的教师工作量管理系统(031)
java·spring boot·后端
可了~14 分钟前
SpringBoot的配置文件了解
java·spring boot·后端
碧海饮冰16 分钟前
招聘面试季--一文顿悟,Java中字节流和字符流的区别及使用场景上的差异
java·开发语言·面试
SailingCoder35 分钟前
递归陷阱:如何优雅地等待 props.parentRoute?
前端·javascript·面试
小丁爱养花1 小时前
MyBatis-Plus:告别手写 SQL 的高效之道
java·开发语言·后端·spring·mybatis
Asthenia04121 小时前
RocketMQ:队列选型/Broker存储机制/三种发送策略/消息有序性/消息积压与处理/集群与广播/Rebalance
后端
重庆穿山甲1 小时前
外观模式实战指南:用Java案例讲透小白也能上手的实用设计模式
后端
Pandaconda1 小时前
【新人系列】Golang 入门(七):闭包详解
开发语言·经验分享·笔记·后端·golang·go·闭包
Asthenia04122 小时前
Elasticsearch分片与副本设置/拼写纠错原理/Linux下部署优化/安装依赖组件/服务器启动流程/Cluster与Node简述/数据库对比/映射
后端
Asthenia04122 小时前
ES:高量级数据聚合/ES数据类型/ES存储原理/ES怎么读文档/ES怎么删文档/为何脑裂/集群监控/如何调优
后端