Skip to content

Spring (基于 Spring 6.2.7 进行源码分析)

字数: 0 字 时长: 0 分钟

IOC 与 DI

Spring IOC (Inversion of Control 控制反转) 是 Spring 框架的核心概念之一。它通过依赖注入(DI Dependency Injection 实现的,让对象的创建与管理职责由容器控制,而不是由对象自身控制。

  • IOC 是一种思想,而不是一个具体的技术。控制反转指的是控制对象的创建这个行为不再由程序员自行完成,而是反转给 IOC 容器完成。

  • DI 是用来构建 IOC 容器的技术实现,IOC 容器在创建一个对象时,会自动将这个对象的依赖注入进去,这样可以让对象与其依赖的对象解耦。

IOC 有什么好处?

对象的创建由 IOC 控制,对象之间不会有很明确的依赖关系,非常容易设计出松耦合的程序

例如:对象 A 依赖一个实现 B,由 IOC 容器控制对象后,我们不需要明确地在对象 A 的代码里指定写死 B 的实现,只需要写明依赖 B 的接口;然后我们可以在配置文件里定义 A 依赖的 B 的具体实现,如果有新的 B 实现类要更换,只需要在配置文件里将 A 依赖 B 改为 B1 即可,而不需要去改动 A 的任何代码!

Spring 对象创建过程

  1. 获取 Bean 实例包装器 BeanWrapper,获取或创建 Bean 的原始实例。

如果是单例,先从 factoryBeanInstanceCache 中尝试获取已存在的包装器;若未命中,则创建实例包装器。

IOC1.webp

  1. 处理 MergedBeanDefinition 后处理器

这句话有点难理解,可以分为 MergedBeanDefinition(合并后的BeanDefinitionPostProcessor(后处理器)

  • BeanDefinition:每个 Spring Bean 在容器中都有对应的配置信息(元数据),比如类名、作用域、依赖关系、是否懒加载等。
  • MergedBeanDefinition:容器创建 Bean 实例时,需要将子定义与其继承的父定义合并,形成完整的配置信息,称为 MergedBeanDefinition(合并后 Bean 定义)。
  • PostProcessor:这里指的是 BeanPostProcessor ,它允许我们在 Bean 的生命周期中插入自定义逻辑,但不修改核心代码

实现 BeanPostProcessor 添加自定义逻辑

java
// 示例:自定义后处理器监控 Bean 初始化
public class MonitoringPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String name) {
        System.out.println("初始化前:" + name);
        return bean;
    }
    
    public Object postProcessAfterInitialization(Object bean, String name) {
        System.out.println("初始化完成:" + name);
        return bean;
    }
}

现在,再回到源码,这里调用 applyMergedBeanDefinitionPostProcessors 遍历所有 BeanPostProcessor 后处理器 ,解析注解信息并缓存到 BeanDefinition 中,为后续依赖注入准备元数据

  1. 依赖注入属性,并初始化 Bean

IOC3.webp

循环依赖

循环依赖 (Circular Dependency)是指两个或多个模块、类、组件之间相互依赖,形成一个闭环,无法确定加载或初始化的顺序

比如单例 A 和单例 B 循环依赖,Spring 是如何创建的呢?

  1. 先创建 A ,此时 A 是不完整的(没有注入 B),用个 map 保存这个不完整的 A
  2. 再创建 B ,B 需要 A ,所以去找 map 中找那个不完整的 A ,此时 B 就完整了
  3. 然后 A 就可以注入 B ,A 也就完整了

A和B循环依赖.webp

Spring 中使用了三级缓存来解决循环依赖,每一级缓存使用一个 map 实现,关键是提前暴露未完全创建完毕的 Bean

三级缓存 map.webp

Spring 如何利用三级缓存解决循环依赖?

  1. 获取单例 Bean 时,会通过 BeanName 先去 singletonObjects(一级缓存)查找完整的 Bean
  2. 如果一级缓存中没有,去看对象是否在创建中,如果没有直接返回 null ;如果正在创建,则去 earlySingletonObjects(二级缓存)中查找 Bean
  3. 如果二级缓存中也没有,则去 singletonFactories(三级缓存)中通过 BeanName 找到对应的工厂,如果存在工厂则通过工厂创建 Bean,并且放入到二级缓存中
  4. 如果三级缓存都每找到,则返回 null

三级缓存.webp

为什么 Spring 循环依赖需要三级缓存,二级不够吗?

其实如果单纯解决循环依赖问题,二级缓存已经够了。但是在涉及到**动态代理(AOP)**时:

  • 直接使用二级缓存不做任何处理,导致我们拿到的 Bean 是未代理的原始对象
  • 如果二级缓存内存放的是代理对象,则违反了 Bean 的生命周期

所以再回到 Bean 的创建源码,可以发现在依赖注入和完全创建 Bean 之前,如果检测到需要 AOP ,会注册三级缓存

AOP注册三级缓存.webp

AOP

AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,用于将通用功能(如日志管理、安全检查、事务管理)与业务逻辑分离开来。

AOP 的核心思想是将与业务逻辑无关的横切关注点抽取出来,通过声明的方式动态地应用到业务方法上,而不是将这些代码直接嵌入业务代码中。

具体实现是通过动态代理静态代理,在调用想要地对象方法时,进行拦截处理没执行切入逻辑,然后再调用真正的实现。

使用代理执行 计数 的切入逻辑

java
class A代理 {
    A a;// 被代理的 A
   void addUser(User user) {
     count();// 计数
     a.addUser(user); // 调用真正的实现
   }
}

AOP 术语

  • 切面 Aspect

在 AOP 中,切面(Aspect 通常包含多个通知(Advice切入点(Pointcut。这些通知和切入点一起定义了横切关注点的功能,例如日志切面可以记录应用中所有服务方法的调用。

java
@Aspect
public class LoggingAspect {
  @Before("execution(* com.example.service.*.*(..))")
  public void logBeforeMethod(JoinPoint joinPoint) {
      System.out.println("Executing method: " + joinPoint.getSignature());
  }
}
  • 连接点 Join Point

连接点通常是方法的执行点,例如在某个方法执行前或执行后插入一段代码。

  • 通知 Advice

通知是在特定的连接点上执行的动作:

  • 前置通知:在方法执行之前执行
  • 后置通知:在方法执行之后执行
  • 环绕通知:在方法执行前后都执行
  • 异常通知:在方法抛出异常后执行
  • 返回通知:在方法执行完成(无论是否抛出异常)后执行
java
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
  System.out.println("Method is about to execute");
}
  • 切入点 PointCut

切入点定义了哪些连接点需要应用通知,Spring AOP 使用 AspectJ 的表达式来定义切入点

通过 @Pointcut 注解定义切入点

java
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
  • 目标对象 Target Object

目标对象是被 AOP 代理的对象,它包含原始的业务逻辑,Spring AOP 为目标对象生成代理对象,来增强目标对象的方法,但目标对象本身是不会感知到的。

Spring AOP 支持两种代理模式:

  • JDK 动态代理:针对实现了接口的类

  • CGLIB 动态代理:针对没有实现接口的类,通过继承目标类生成代理对象

  • 织入 Weaving

织入是将切面应用到目标对象的过程。Spring AOP 通过运行时动态代理实现织入,而 AspectJ 可以在编译时通过静态代理实现织入。

AOP 拦截器链

AOP 实战示例代码

java
// controller 代码
@RestController
public class UserController {

    @Resource
    private HelloService helloService;

    @RequestMapping("hello")
    public String hello() {
        return helloService.hello("甜甜");
    }
}

// service 层代码
@Service
public class HelloService {

    public String hello(String name) {
        return "Hello " + name;
    }

}

// AOP 切面类
@Aspect
@Component
public class LogAspect {

    // 定义切入点,匹配 service 包下所有方法
    @Pointcut("execution(* cn.ttdgg.rpc.springboot.consumer.service.*.*(..))")
    public void serviceMethods() {}


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

    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("后置通知执行:" + joinPoint.getSignature().getName());
    }

    @AfterReturning(pointcut = "serviceMethods()",  returning = "result")
    public void logAfterReturn(JoinPoint joinPoint,Object result) {
        System.out.println("返回通知执行:" + result);
    }

    @AfterThrowing(pointcut = "serviceMethods()",  throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint,Throwable error) {
        System.out.println("异常通知执行:" + error.getMessage());
    }

    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知执行(前置):" + joinPoint.getSignature().getName());
        Object result = joinPoint.proceed();
        System.out.println("环绕通知执行(后置):" + result);
        return result;
    }

}

AOP通知.webp

当我们正常访问该接口时,控制台成功在方法执行前后打印出了相关信息,那么 Spring 具体是如何控制这些调用顺序的呢?

实际上这些注解都对应着不同的 interceptor 拦截器实现,他们采用责任链模式实现了拦截器链

注意上面的示例代码,我的 Service 直接使用的注入的是 HelloService 类,而不是接口,因此 AOP 采用的是 CGLIB 动态代理模式为我创建代理对象 (如果是接口,那么 AOP 会采用 JDK 动态代理创建,JDK 动态代理使用反射实现,性能会比 CGLIB 差些)

CGLIB 动态代理入口是 CglibAopProxy 类的内部类 DynamicAdvisedInterceptor,会尝试获取拦截器链

AOP拦截器链.webp

获取到拦截器链之后,会递归调用连接器链中所有拦截器

递归调用AOP拦截器.webp

IOC 容器相关注解

  • @Controller@Service@Repository@Component

这四个注解本质上都是 @Component 注解,标记一个类为 Spring 的 Bean,可以被自动扫描(@ComponentScan)检测

  • @Configuration

标记一个类是配置类,替代传统的 XML 配置文件,内部可以声明 Bean

  • @Bean

标注在 @Configuration 类或 @Component 类内部的方法上,表名该方法的返回值注册为 Spring 的 Bean

  • @ComponentScan

配置在 @Configuration 类(或 @SpringBootApplication 内)上,指示 Spring 容器需要扫描哪些包及其子包

依赖注入相关注解

  • @Autowired

Spring 提供,按类型(byType) 进行依赖注入,可以直接在字段上标注或在 Setter 方法上标注;但官方更推荐在构造器上标注,当类只有一个构造器时,@Autowired 可省略

  • @Qualifier@Primary

因为 @Autowired 按类型进行依赖注入,当有多个匹配的 Bean 时,优先注入被标记了 @Primary 那个

@Qualifier 可以显式指定要注入的 Bean 的名称

  • @Resource

作用与 @Autowired 一样,不同的是@Resource 是一个标准的 JSR-250 标准,不止能在 Spring 框架中使用,默认**按名字(byName)**进行依赖注入,不支持构造器注入

  • @Inject

行为几乎和 @Autowired 相同,是另一个标准化 JSR-330 依赖注入注解,是与 Spring 解耦的 Java 标准

  • @Value

主要用于注入外配配置文件(如 application.yml)的值,或注入简单的字面量。

作用域与生命周期注解

  • @Scope

指定一个 Bean 的作用域,默认是单例(singleton),也支持 prototyperequestsession

java
@Component
@Scope("prototype") // 或多用 ScopeConfigurer.PROTOTYPE
public class PrototypeBean { ... }
  • @PostConstruct(JSR-250)

标注在方法上,该方法会在Bean 依赖注入完成之后和初始化回调方法之前被执行一次

  • @PreDestroy(JSR-250)

标注在方法上,该方法会在容器关闭、Bean被销毁之前被执行一次

  • @Lazy

标注在 @Component@Bean 等地方:该 Bean 只有在第一次被需要时才会被初始化(懒加载),可以加快应用的启动速度

@Async 异步任务

@Async 标注在方法上,可以异步执行任务(需要使用 @EnableAsync 注解来开启异步任务功能,注解可以加在启动类或配置类上)。

Spring 通过动态代理为 @Async 方法生成代理对象,拦截方法调用,通过切面将方法调用转交给线程池进行异步执行。

优化线程池配置

默认使用 SimpleAsyncTaskExecutor (每次执行任务都会新开线程池,不推荐),可通过自定义 TaskExecutor 优化线程池管理。

java
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.initialize();
        return executor;
    }
}

自调用失效问题

如果类自己调用内部的 Async 方法(this.method)会绕过代理,不会触发 AOP 代理增强,因此该注解会失效

解决方案

java
@Service
public class MyService {
    // 关键:注入自身代理
    @Autowired
    private MyService selfProxy; 

    public void syncMethod() {
        // 正确:通过代理对象调用
        selfProxy.asyncMethod();
    }

    @Async
    public void asyncMethod() {}
}
java
@Service
public class SyncService {
    @Autowired
    private AsyncService asyncService; // 独立异步服务

    public void syncMethod() {
        asyncService.asyncMethod(); // 必定生效
    }
}

@Service
public class AsyncService {
    @Async
    public void asyncMethod() {}
}

@Transactional 事务

Spring 通过 AOP 动态代理创建目标对象的代理,拦截 @Transactional 方法,交给 PlatformTransactionManager 事务管理器接管。

要使用 @Transactional 注解必须先引入依赖,位于 spring-tx 模块中, SpringBoot 直接引入 spring-boot-starter-jdbc 即可

Transactional注解.webp

@EventListener 事件监听

标注在方法上,使方法自动监听特定类型的事件,并在事件发布时触发执行。

1. 发布事件

在 Spring 中,可以使用 ApplicationEventPublisher 发布事件。可以发布自定义事件或 Spring 内置事件。

java
@Component
public class EventPublisher {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publish(String message) {
        MyCustomEvent event = new MyCustomEvent(this, message);
        eventPublisher.publishEvent(event); // 发布事件
    }
}

2. 监听事件

使用 @EventListener 监听事件,事件参数可以是自定义事件或内置事件。

java
@Component
public class MyEventListener {

    @EventListener
    public void handleEvent(MyCustomEvent event) {
        System.out.println("Event received: " + event.getMessage());
    }
}

3. 异步事件监听

在处理某些耗时任务时,我们希望事件监听是异步的,以避免阻塞主线程。通过 @Async 注解,将事件监听方法变为异步执行

java
@Configuration
@EnableAsync  // 启用异步功能
public class AsyncConfig {
}

@Component
public class MyEventListener {

    @Async
    @EventListener
    public void handleEvent(MyCustomEvent event) {
        System.out.println("Handling event asynchronously: " + event.getMessage());
    }
}

4. 条件事件监听

通过 @EventLiistener(condition = "..."),可以私用 Spring 的 SpEL 表达式定义条件,只有满足条件时,才会处理该事件。

java
@Component
public class MyEventListener {

    @EventListener(condition = "#event.message.startsWith('Spring')")
    public void handleEvent(MyCustomEvent event) {
        System.out.println("Event received: " + event.getMessage());
    }
}

5. 内置事件

Spring 提供了很多内置事件,例如:

  • ContextRefreshedEvent:Spring 容器初始化或刷新时触发
  • ContextClosedEvent:Spring 容器关闭时触发
  • ApplicationReadyEvent:应用启动完成时触发
java
@Component
public class ContextEventListener {

    @EventListener
    public void onContextRefreshed(ContextRefreshedEvent event) {
        System.out.println("Context refreshed");
    }
}

6. 返回值事件

@EventListener 标注的方法可以有返回值,如果返回值是事件类型,那么 Spring 会自动将返回值作为新的事件发布

java
@Component
public class MyEventListener {

    @EventListener
    public AnotherEvent handleEvent(MyCustomEvent event) {
        System.out.println("Handling event: " + event.getMessage());
        return new AnotherEvent(this, "Follow-up event");
    }
}