面试整理——Spring、SpringMVC和Spring Boot

Spring、SpringMVC和Spring Boot

一.Spring

1.1 Spring基础

问:Spring常用模块?

  • Spring Core:框架的最基础部分,提供 IoC 容器,对 bean 进行管理。
  • Spring Context:继承 BeanFactory ,提供上下文信息,扩展出 JNDIEJB 、电子邮件、国际化等功能。
  • Spring DAO:提供了 JDBC 的抽象层,还提供了声明 性事务管理方法。
  • Spring ORM:提供了 JPAJDOHibernateMyBatis 等ORM映射层(Object Relational Mapping,对象关系映射)。
  • Spring AOP:集成了所有 AOP 功能。
  • Spring Web:提供了基础的 Web 开发的上下文信息,现有的Web框架,如 JSFTapestryStructs 等,提供了集成。
  • Spring Web MVC:提供了 Web 应用的 Model-View-Controller 全功能实现。

问:介绍一下Spring?Spring和Spring MVC的关系?

Spring 是一个开源的 Java 框架,用于构建企业级应用程序。

核心特性包括:

  1. IoC 容器(Inversion of Control): 管理对象的生命周期和依赖关系,通过将对象的创建和组装工作委托给容器,实现了对象之间的解耦。

  2. AOP(面向切面编程): 可以通过配置方式实现横切关注点的模块化,例如事务管理、日志记录等。

  3. 数据访问: 提供了对 JDBC、ORM 框架(如 Hibernate、MyBatis)、JPA 等的集成支持,简化了数据访问的操作。

  4. 事务管理: 提供了声明式的事务管理,支持编程式和声明式两种事务管理方式。

  5. MVC 框架: Spring MVC 是 Spring 框架中的一个模块,用于构建基于模型-视图-控制器(MVC)设计模式的 Web 应用程序。

  6. 安全性: 提供了综合的安全性解决方案,包括认证(Authentication)和授权(Authorization)等。

  7. 集成支持:Spring 支持与各种其他框架和技术的集成,如消息队列、缓存、远程调用等。

Spring MVC 则是 Spring 框架中的 Web 模块,用于构建基于 MVC 设计模式的 Web 应用程序。

Spring 和 Spring MVC 的关系?

  • Spring可以看作一个集成了多种特性、多种功能模块的平台(如IoC、AOP、事务、ORM等)。

  • Spring MVC 是 Spring 框架的一个模块,用于构建 Web 层。

  • Spring 框架提供了全面的基础设施支持,而 Spring MVC 则专注于提供用于构建 Web 应用程序的 MVC 框架。

  • MVC模型则是一个Web框架不可缺少的内容,Spring MVC就是建立在Spring平台上的MVC模型,需要基于Spring支撑才能运行。

问:SpringMVC的执行流程?从用户请求到响应?⭐⭐⭐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
SpringMVC执行流程图:
+-----------------+
| User Request |
+--------+---------+
|
v
+--------+---------+
| DispatcherServlet |
+--------+---------+
|
v
+--------+---------+
| HandlerMapping |
+--------+---------+
|
v
+--------+---------+
| HandlerExecutionChain |
+--------+---------+
|
v
+--------+---------+
| HandlerAdapter |
+--------+---------+
|
v
+--------+---------+
| Handler |
+--------+---------+
|
v
+--------+---------+
| ModelAndView |
+--------+---------+
|
v
+--------+---------+
| ViewResolver |
+--------+---------+
|
v
+--------+---------+
| View |
+--------+---------+
|
v
+--------+---------+
| User Response |
+-----------------+

1. 用户发起请求

  • 用户通过浏览器或其他客户端发起 HTTP 请求,请求到达 Spring MVC 应用。
  • http://localhost:8080/user/getUser?id=1

2. DispatcherServlet 接收请求

  • DispatcherServlet 是 Spring MVC 的前端控制器(Front Controller),负责接收所有请求并分发。解析请求(路径、请求方法、参数等)。交给 HandlerMapping 进行处理。

3. HandlerMapping 解析请求,查找 Handler(处理器)

  • HandlerMapping 负责根据请求的 URL 找到对应的处理器(Handler)。
  • HandlerMapping 返回一个 HandlerExecutionChain,包含目标处理器(Handler)和拦截器(Interceptor)。
  • SpringMVC 提供了多种 HandlerMapping 实现,例如 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping 等。

4. 执行拦截器 HandlerInterceptor

  • HandlerExecutionChain 中的拦截器 HandlerInterceptor 在处理器执行前后执行拦截逻辑(如权限检查、日志记录等)。

5. HandlerAdapter 适配和执行处理器 Handler

  • HandlerAdapter 负责调用处理器(Handler)的具体方法。DispatcherServlet 接收到 HandlerExecutionChain 对象后,会调用 HandlerAdapter 来执行 Handler。调用 Handler 的 handleRequest 方法处理请求。
  • 流程
    • DispatcherServlet 调用 HandlerAdapterhandle() 方法。
    • HandlerAdapter 根据处理器的类型(如 @ControllerHttpRequestHandler 等)调用对应的处理方法。

6. 执行处理器(Handler)

  • @ControllerHttpRequestHandler 处理器执行业务逻辑,处理请求并返回结果。
  • Handler 通常是一个 Controller,它负责处理业务逻辑,并返回一个 ModelAndView 对象。ModelAndView 对象包含了模型数据和视图信息。
  • 流程
    • 处理器方法(如 @RequestMapping 注解的方法)被调用。
    • 处理器方法返回一个 ModelAndView 对象或直接返回数据(如 JSON)。

7. DispatcherServlet 查找 ViewResolver 处理返回结果

  • DispatcherServlet 接收到 ModelAndView 对象后,会调用 ViewResolver 接口的实现类来查找合适的 View。ViewResolver 负责将视图名称解析为具体的 View 对象。
  • 核心类ModelAndViewViewResolver
  • 作用
    • 如果处理器返回 ModelAndViewDispatcherServlet 会解析视图名称并渲染视图。
    • 如果处理器直接返回数据(如 JSON),DispatcherServlet 会直接返回响应。
  • 流程
    • DispatcherServlet 调用 ViewResolverresolveViewName() 方法,将视图名称解析为具体的 View 对象。
    • View 对象负责渲染视图(如 JSP、Thymeleaf 等)。

9. 渲染视图

  • 核心类View
  • 作用:将模型数据渲染到视图(如 HTML、JSON 等)。
  • 流程View 对象的 render() 方法被调用,生成最终的响应内容。

10. 返回响应

  • 核心类DispatcherServlet
  • 作用:将最终的响应返回给客户端。
  • 流程DispatcherServlet 将渲染后的视图或数据写入 HTTP 响应,返回给客户端。

总结:Spring MVC 执行流程

  1. 用户发起请求 → 2. DispatcherServlet 接收请求 → 3. HandlerMapping 查找 Handler → 4. 拦截器 preHandle → 5. HandlerAdapter 适配 Handler → 6. 处理器执行业务逻辑 → 7. 处理返回结果 → 8. 拦截器 postHandle → 9. 渲染视图 → 10. 拦截器 afterCompletion → 11. 返回响应

涉及的核心类

类名 作用
DispatcherServlet 前端控制器,负责接收请求并协调处理流程。
HandlerMapping 根据请求 URL 找到对应的处理器(Handler)。
HandlerAdapter 调用处理器(Handler)的具体方法。
HandlerInterceptor 拦截器,在处理器执行前后执行逻辑。
@Controller 处理器,执行业务逻辑并返回结果。
ModelAndView 封装模型数据和视图名称。
ViewResolver 解析视图名称并返回具体的 View 对象。
View 负责渲染视图(如 JSP、Thymeleaf 等)。

问:Spring的扩展点?⭐⭐

扩展接口 作用
BeanPostProcessor 支持在 Bean 初始化前、后对 Bean 进行处理
BeanFactoryPostProcessor 处理所有 Bean 前,对 BeanFactory 进行预处理
BeanDefinitionRegistryPostProcessor 可以添加自定义的Bean
initializingBean 在 Bean 创建完成,所有属性注入完成之后执行
DisposableBean 在 Bean 销毁前执行
Aware 接口族 获得 Spring 容器资源
FactoryBean 复杂 Bean 注入
ApplicationListener 监听响应容器事件

1.2 Spring IoC

问:讲一下IoC的原理与初始化流程?BeanDefinition加载流程?Spring 对于 IOC 的扩展点有哪些?⭐⭐⭐

  • IoC,即控制反转(Inversion of Control)。将对象的创建通过依赖注入(Dependency Injection)的方式由 Spring IoC容器来实现。

  • 原理:控制反转即将对象的创建和管理交给Spring容器,而不是由应用程序自己负责。通过依赖注入的方式将对象的依赖关系在容器启动时进行注入。

  • Spring容器的创建流程?

    1. 初始化配置:创建Spring容器ApplicationContext,在启动时扫描所有的Bean配置信息,包括XML配置和注解配置;

      • XML 配置文件方式:Spring 容器会通过解析 applicationContext.xml(或类似文件)来加载定义的 bean,存储成 BeanDefinition 对象。这个过程主要是由 BeanDefinitionReader 完成。
      • 注解配置方式:如果使用注解配置,Spring 会扫描指定包下的所有类,并检查是否有 @Component@Service@Repository@Controller 等注解。然后将这些类注册为容器中的 bean。
    2. 创建和注册BeanDefinition对象:Spring容器会根据配置信息创建BeanDefinition对象,并将其注册到 BeanFactory 中。

      • BeanDefinition对象包含了Bean的详细信息,如Bean的类名、依赖关系、生命周期等;
      • BeanFactoryBeanFactory 是 Spring 中用于管理和存储 BeanDefinition 的容器。DefaultListableBeanFactory 是常见的 BeanFactory 实现,它用于存储和管理所有的 bean 定义。
    3. Bean实例化:Spring 容器根据 BeanDefinition 反射实例化 bean。这个过程通常是懒加载的,即只有在真正需要该 bean 时才会被实例化。

      • 这一过程会调用Bean的构造方法;
      • 会调用Bean的setter方法,完成Bean的属性注入。
    4. 初始化 Bean:依赖注入和对象赋值/属性填充,调用Aware子类方法。调用BeanPostProcessor前置处理方法。调用init-method 。调用BeanPostProcessor后置处理方法。

      在实例化 bean 后,Spring 容器会注入该 bean 所依赖的其他 bean:

      • 字段注入:Spring 会通过反射机制向 bean 的字段注入依赖。
      • 构造函数注入:如果 bean 使用了构造函数注入方式,Spring 会解析构造函数的参数并注入相应的依赖。
      • setter 注入:如果 bean 使用了 setter 方法注入方式,Spring 会通过反射调用 setter 方法并注入依赖。

      在完成依赖注入后,Spring 会检查该 bean 是否有需要执行的初始化方法。可以通过以下几种方式指定初始化方法:

      • @PostConstruct 注解:如果 bean 类上有 @PostConstruct 注解,Spring 会在 bean 完成依赖注入后调用该方法。
      • InitializingBean 接口:如果 bean 实现了 InitializingBean 接口,Spring 会调用 afterPropertiesSet() 方法进行初始化。
      • init-method 属性:在 XML 配置中,可以通过 init-method 属性指定初始化方法。
    5. 使用 Bean:当 bean 初始化完成后,容器就可以将其提供给应用程序使用。通过getBean方法直接获取。如果是单例 bean,Spring 会将其保存在容器中,直到容器关闭。如果是原型 bean,Spring 会每次创建新的实例。

    6. 销毁 Bean:当容器关闭时,Spring 会销毁容器中的所有 bean。销毁过程会按照以下步骤进行:

      • @PreDestroy 注解:如果 bean 类上有 @PreDestroy 注解,Spring 会调用该方法。
      • DisposableBean 接口:如果 bean 实现了 DisposableBean 接口,Spring 会调用 destroy() 方法进行销毁。
      • destroy-method 属性:在 XML 配置中,可以通过 destroy-method 属性指定销毁方法。
    7. 容器关闭:销毁所有的 singleton beans,并释放容器中持有的所有资源。这通常通过调用 ApplicationContextclose() 方法来触发。

  • BeanDefinition加载流程:

    1. Resource资源定位:即容器寻找数据,寻找用户定义的bean资源,由ResourceLoader通过统一的接口Resource接口来完成。资源一般以XML配置文件、注解或Java配置类等形式存在。

    2. BeanDefinition载入:Bean将配置文件中描述的Bean信息转化为Spring容器内部的数据结构BeanDefinition。通过不同的BeanDefinitionReader读取、解析Resource定位的资源,转换成BeanDefinition,并载入到IoC中。

    3. BeanDefinition注册:即向IOC容器注册这些BeanDefinition,以便后续能够根据配置信息实例化和管理Bean。

      • 在 Spring 容器初始化时,会创建一个 DefaultListableBeanFactory 对象作为 BeanFactory。

      • 注册BeanDefinition:

        1
        2
        // 向 DefaultListableBeanFactory 注册 BeanDefinition
        beanFactory.registerBeanDefinition("myBean", beanDefinition);
      • 注册过程就是将beanDefinition放入到维护的ConcurrentHashMap中

      • DefaultListableBeanFactoryBeanFactoryBeanDefinitionRegistry 的默认实现类,它同时实现了这两个接口,用于承担 Bean 的注册和管理的职责。

  • BeanFactory 接口:是 Spring IoC 容器的基础接口,定义了 IoC 容器的基本行为。BeanFactory 的典型实现是 DefaultListableBeanFactory,它是 Spring 中最简单的 IoC 容器实现。

    • 获取 Bean 实例
    • 检查 Bean 是否存在
    • 获取 Bean 的类型
    • 控制 Bean 的生命周期
    • 解决 Bean 之间的依赖关系
  • ApplicationContext 接口:是 BeanFactory 的子接口,提供了更多的功能,使得应用程序开发更加方便。ApplicationContext 继承了 BeanFactory,同时还包含了一些额外的功能。ApplicationContextBeanFactory 的功能扩展,提供了更多的企业级特性,适用于绝大多数的 Spring 应用程序。

    • 支持国际化
      • 事件传播机制
      • 资源加载
      • AOP 支持
      • 事务管理

    典型实现包括:

    • ClassPathXmlApplicationContext: 从类路径加载 XML 配置文件创建容器。
    • FileSystemXmlApplicationContext: 从文件系统中加载 XML 配置文件创建容器。
    • AnnotationConfigApplicationContext: 基于注解的配置创建容器。

Spring IoC的扩展点:

  1. BeanPostProcessor: 允许在每个Bean的初始化前后进行自定义处理。开发者可以通过实现BeanPostProcessor接口来定义在Bean初始化前后进行的操作。

  2. BeanFactoryPostProcessor: 允许在BeanFactory实例化Bean之前对BeanFactory进行自定义处理。通过实现BeanFactoryPostProcessor接口,开发者可以在容器实例化Bean之前修改或添加Bean的定义信息。如PlaceHolderConfigurSupport、ConfigurationClassPostProcessor

  3. FactoryBean: 允许定义产生一个或多个对象的工厂Bean。通过实现FactoryBean接口,可以自定义创建Bean的逻辑,返回的对象将成为Spring容器中的一个Bean。

  4. ApplicationContextInitializer: 允许在容器初始化时对ApplicationContext进行扩展。通过实现ApplicationContextInitializer接口,可以在容器初始化的早期执行自定义逻辑。

  5. ApplicationListener: 允许监听容器中发布的事件。通过实现ApplicationListener接口,可以监听容器中各种事件,例如Bean初始化完成、容器启动完成等。

这些扩展点提供了灵活性,使得开发者可以在Spring容器的不同阶段插入自定义逻辑,实现更高度的定制和扩展。

问:IOC的底层实现?⭐⭐⭐

核心方法:

  • createBeanFactory:是创建 BeanFactory 的核心方法,它初始化一个 Spring 容器(通常是 DefaultListableBeanFactory)来存储和管理所有的 bean 定义。
    • 会根据配置文件(如 XML 配置文件、注解配置等)加载 bean 定义。
    • BeanFactory 是一个核心接口,它的实现 DefaultListableBeanFactory 用于存储和管理 BeanDefinition,并为 bean 提供实例化、依赖注入和生命周期管理等功能。
  • getBean、doGetBean:是获取容器中定义的 bean 的方法。doGetBean 是内部方法,负责从 BeanFactory 中获取或创建 bean 的实例。
    • getBean 会根据给定的 bean 名称(或类型)从容器中查找 bean。
    • 若容器中已有该 bean(对于单例 bean),则直接返回已缓存的实例。
    • 如果容器中没有该 bean,则调用 doGetBean,通常会调用 createBean 创建一个新的实例。
  • createBean、doCreateBean:负责实例化 bean。doCreateBeancreateBean 的内部调用,具体负责如何通过反射机制实例化对象。
    • 它首先检查是否有该 bean 的定义(BeanDefinition)。然后根据 BeanDefinition 中的信息(如类名、构造函数参数等)创建该 bean。
    • Spring 使用反射机制来实例化 bean,这通常是通过 getDeclaredConstructor 获取无参构造函数,然后使用 newInstance 创建对象。
  • createBeanInstance:负责通过反射机制或工厂方法实例化一个新的 bean 对象。它是 Spring bean 创建的最底层方法,负责通过构造方法实例化对象。
    • getDeclaredConstructor:是反射 API 中的方法,Spring 使用它来获取类的构造函数。如果容器配置的是无参构造函数,Spring 会优先使用该构造函数创建实例。
    • newInstance:通过反射机制实例化一个新的对象。
  • populateBean:负责给 bean 进行属性填充的过程,即将 bean 的依赖注入到实例中。
    • 在实例化 bean 后,populateBean 会根据 BeanDefinition 中的定义,通过构造函数注入、Setter 注入或字段注入的方式填充属性。
    • 它会通过反射来设置属性值,通常会调用 set 方法,或者直接注入字段的值。
  • initializingBean:是 Spring 提供的一种接口,用于在 bean 完成属性注入后执行初始化操作。
    • 调用 BeanPostProcessor 的前置处理方法。
    • 在 Spring 容器将依赖注入完成后,会调用实现了 InitializingBean 接口的 afterPropertiesSet() 方法进行初始化操作。
    • 如果在 bean 定义中配置了初始化方法(如 init-method),Spring 还会调用这个方法进行自定义初始化操作。
    • 此外,Spring 还会处理通过注解(如 @PostConstruct)标记的方法。
    • 调用 BeanPostProcessor 的后置处理方法。
  1. 先通过createBeanFactory创建出一个DefaultListableBeanFactory
  2. 开始循环创建对象,bean默认单例,通过getBean在容器中查找。
  3. 若未找到,通过createBean以反射的方式创建对象。一般通过无参构造方法getDeclaredConstructor,以及newInstance创建。
  4. 通过populateBean进行属性填充。
  5. 通过initializingBean进行其它初始化操作。

问:讲一下依赖注入?DI依赖注入流程? 如何实例化,怎么处理bean之间的依赖关系?

依赖注入(Dependency Injection,DI)是一种设计模式,用于处理对象之间的依赖关系。在 DI 中,对象的依赖关系不是由对象本身负责获取,而是由外部的容器(通常是 IoC 容器)负责注入。这使得系统更加灵活、松耦合,易于测试和维护。

依赖注入的流程:

  • 如果设置lazy-init=true,会在第一次getBean的时候才初始化bean,lazy-init=false,会容器启动的时候直接初始化(singleton bean);
  • 调用 BeanFactory.getBean() 生成bean的;
  • 生成bean过程运用装饰器模式产生的bean都是beanWrapper(bean的增强);

依赖注入怎么处理bean之间的依赖关系?

  • 其实就是通过在beanDefinition载入时,如果bean有依赖关系,通过占位符来代替,在调用getbean时候,如果遇到占位符,从ioc里获取bean注入到本实例来。

问:给你spring的jar包你要怎么让它启动?

使用 nohupjava 命令启动 Spring Boot 项目。添加 & 让项目在后台运行,并使用 nohup 避免在 SSH 断开连接时关闭项目。

1
nohup java -jar your-project.jar &

1.3 Spring Bean

问:讲一讲Spring Bean是什么?

Bean 通常代表一个对象的实例,这个对象可以是任何 Java 类的实例,包括自定义的类。Bean是由 Spring IoC 容器所初始化、装配及管理的对象。

问:Spring Bean定义5种作用域?⭐

在 Spring 框架中,Bean 可以具有不同的作用域,表示 Bean 实例的生命周期和可见性范围,这些作用域可以通过 XML 配置文件或通过 Java 注解的方式指定。

选择合适的作用域取决于 Bean 的用途和生命周期需求。:

  • Singleton(单例): 单例作用域是默认的作用域,Spring IoC 容器中仅存在一个Bean实例,Bean以单例的形式存在,无论有多少次请求,都返回同一个实例。适合Service类、工具类等。

    1
    <bean id="mySingletonBean" class="com.example.MyBean" scope="singleton"/>

    或者通过 Java 注解:

    1
    2
    3
    4
    5
    @Component
    @Scope("singleton")
    public class MySingletonBean {
    // ...
    }
  • Prototype(原型): 表示每次请求都创建一个新的 Bean 实例。每次注入或获取 Bean 时,都会创建一个新的对象。即每次调用 getBean() 时,相当于执行 new XxxBean() 。适合控制器、命令对象等。

    1
    <bean id="myPrototypeBean" class="com.example.MyBean" scope="prototype"/>

    或者通过 Java 注解:

    1
    2
    3
    4
    5
    @Component
    @Scope("prototype")
    public class MyPrototypeBean {
    // ...
    }
  • Request(请求): 请求作用域表示在每个 HTTP 请求中都创建一个新的 Bean 实例。该作用域仅适用于 Spring Web 应用程序 WebApplicationContext 环境,适合Web请求。

    1
    <bean id="myRequestBean" class="com.example.MyBean" scope="request"/>
  • Session(会话): 表示在每个用户会话中都创建一个新的 Bean 实例。该作用域同样仅适用于 Spring Web 应用程序 WebApplicationContext 环境。同一个 HTTP Session 共享一个Bean,不同Session使用不同Bean,适合用户的购物车、个性化设置等。

    1
    <bean id="mySessionBean" class="com.example.MyBean" scope="session"/>
  • GlobalSession(全局会话): 全局会话作用域是在一个全局 HTTP 会话中创建一个 Bean 实例。通常仅适用于分布式的 Spring Web 应用程序 WebApplicationContext 环境,适合集群共享的对象。

    1
    <bean id="myGlobalSessionBean" class="com.example.MyBean" scope="globalsession"/>

问:Spring的生命周期?Spring bean生命周期?(源码细节,以及各个位置的设计思路,有什么可扩展的)⭐⭐⭐

参考:Spring Bean

  1. 实例化(Instantiation): Ioc容器通过获取BeanDefinition对象中的信息进行实例化。读取bean的xml配置文件,将bean元素分别转换成一个BeanDefinition对象。然后通过BeanDefinitionRegistry将这些bean注册到beanFactory中,保存在它的一个ConcurrentHashMap中。
  2. 属性赋值(Populate Properties): 通过BeanWrapper提供的设置属性的接口完成属性依赖注入;实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。
  3. 注入Aware接口:Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。通过invokeAwareMethod完成BeanName、BeanFactory、BeanClassLoader对象的属性设置。
  4. BeanPostProcessor 的前置处理: 经过前几步,bean对象已经正确的构造。如果需要在使用对象前再进行一些自定义的步骤,就通过该接口来实现。如果应用中存在实现了 BeanPostProcessor 接口的类,容器会在 Bean 初始化前后调用这些处理器的方法。这提供了自定义初始化逻辑的扩展点。
    • BeanPostProcessor 接口中的两个方法是 postProcessBeforeInitializationpostProcessAfterInitialization,分别代表前置处理和后置处理。
  5. 初始化方法调用(InitializingBean与init-method):执行我们自己定义的初始化方法。如果 Bean 配置中指定了初始化方法,容器会在属性赋值后调用该初始化方法。
    • 如果 Bean 实现了 InitializingBean 接口,容器也会调用其 afterPropertiesSet 方法。
    • 如果bean配置了自定义的 init-method ,若声明则调用对应方法。
  6. BeanPostProcessor 的后置处理: 类似于前置处理,如果应用中存在实现了 BeanPostProcessor 接口的类,容器会在 Bean 初始化后调用这些处理器的方法。这提供了自定义后置处理逻辑的扩展点。AOP在此实现,AbstractAutoProxyCreator。
  7. Bean 使用中: 在初始化后,Bean 可以被应用程序使用。在这个阶段,Bean 执行它的业务逻辑。
  8. 销毁阶段(destroy): 如果 Bean 的作用域是 singleton,并且配置了销毁方法,容器在关闭时会调用 Bean 的销毁方法。
    • 如果bean实现了 DisposableBean 接口,Spring将调用 destroy() 方法。
    • 如果bean配置了自定义 destroy-method ,则会调用对应销毁方法。

可扩展:

  • 实现BeanPostProcessor接口的前置处理和后置处理。
  • 自定义初始化方法和销毁方法,在 Bean 配置中使用 init-methoddestroy-method 属性,或者实现 InitializingBeanDisposableBean 接口。

问:讲一下bean成员变量的参数注入有哪种方式?⭐

  1. 构造器注入(Constructor Injection):通过构造函数来注入依赖项。这是一种推荐的注入方式,因为它可以保证 Bean 在实例化后就拥有所有必需的依赖项。

    1
    2
    3
    4
    5
    6
    private Dependency dependency;

    // 构造器注入
    public Example(Dependency dependency) {
    this.dependency = dependency;
    }
  2. Setter 方法注入(Setter Injection): 通过 setter 方法为 Bean 的成员变量注入依赖项。这是最常见的注入方式,可以提供更灵活的配置。

    1
    2
    3
    4
    5
    6
    private Dependency dependency;

    // Setter 方法注入
    public void setDependency(Dependency dependency) {
    this.dependency = dependency;
    }
  3. 注解注入(Annotation-based Injection): 使用注解(如 @Autowired@Inject 等)在字段、方法或构造器上标注,容器在创建对象时通过反射机制将依赖注入。

    1
    2
    3
    4
    5
    6
    7
    8
    @Autowired
    private Dependency dependency;

    // 或者通过构造器注入
    @Autowired
    public Example(Dependency dependency) {
    this.dependency = dependency;
    }
  4. 接口注入(Interface-based Injection): 通过接口定义依赖注入的方法,类实现该接口,并在实现类中实现依赖的注入逻辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface DependencyInjector {
    void injectDependency(Dependency dependency);
    }

    public class Example implements DependencyInjector {
    private Dependency dependency;

    // 接口注入
    @Override
    public void injectDependency(Dependency dependency) {
    this.dependency = dependency;
    }
    }
  • 构造器注入通常用于强制依赖关系,确保对象在创建时就拥有所需的依赖。
  • Setter 方法注入提供了更灵活的依赖注入方式,允许在对象创建后进行动态变更。
  • 注解注入是一种简洁的方式,通过注解标注字段或方法,容器可以自动完成依赖注入。
  • 接口注入可通过定义接口实现依赖注入的方法,但相对来说较少使用。

问:为什么Spring不推荐使用属性注入?

使用@Autowired等进行属性注入时,其本质是通过反射机制,所以执行时刻是在对象创建完成后。而Spring推荐使用构造器注入,执行时刻是在对象创建过程中init阶段:

  1. 注入在对象创建过程中,而不是之后通过新增一个阶段,没有破坏流程。
  2. 相比反射,在项目启动时性能提升了。

问:Spring中的BeanFactory 、FactoryBean、ObjectFactory⭐

BeanFactory 、FactoryBean都创建Bean对象,BeanFactory是IOC容器,创建的对象要遵循严格的生命周期。若想自定义某个对象的创建,并最终把对象交给Spring来管理,则由FactoryBean实现。

  • BeanFactory 即IOC容器,有多种实现,负责管理Spring中的Bean。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public interface BeanFactory {

    ...

    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;

    ...

    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    ...

    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    ...

    }
  • FactoryBean是一种Bean,要获取某个 Bean 的时候容器会调用 FactoryBean#getObject() 方法,该方法封装了对象复杂的创建过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface FactoryBean<T> {

    // 自定义创建对象的过程
    T getObject() throws Exception;

    // 获取对象类型
    Class<?> getObjectType();

    // 是否单例
    default boolean isSingleton() {
    return true;
    }

    }
  • ObjectFactory用于延迟查找的场景(三级缓存),它就是一个普通工厂,当得到 ObjectFactory 对象时,相当于 Bean 没有被创建,只有当 getObject() 方法时,才会触发 Bean 实例化等生命周期。

    1
    2
    3
    4
    5
    public interface ObjectFactory<T> {

    T getObject() throws BeansException;

    }

问:单例模式的几种实现方式?Spring的单例是怎么实现的?单例的情况下怎么实例化,什么时候,多例呢?

单例模式有三种实现方式:

  1. 饿汉式: 在类加载的时候就创建单例实例。这意味着无论是否需要使用该单例对象,它都会在类加载时创建。这样可以确保在多线程环境下也能保持唯一性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
    // 私有构造方法
    }

    public static Singleton getInstance() {
    return instance;
    }
    }
  2. 懒汉式: 在需要使用单例对象的时候才创建实例。这样可以延迟实例的创建,避免在应用启动时就占用资源。它使用同步方法或双重检查锁定来确保线程安全。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
    // 私有构造方法
    }

    public static synchronized LazySingleton getInstance() {
    if (instance == null) {
    instance = new LazySingleton();
    }
    return instance;
    }
    }
  1. 单例注册表:通过一个ConcurrentHashMap存储Bean对象,保证Bean名称唯一的情况下也能保证线程安全。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class RegSingleton {
    private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(16);

    private RegSingleton(){}

    public static Object getInstance(String className){
    if (StringUtils.isEmpty(className)) {
    return null;
    }
    if (singletonObjects.get(className) == null) {
    try {
    singletonObjects.put(className, Class.forName(className).newInstance());
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    return singletonObjects.get(className);
    }
    }

Spring使用单例注册表来实现单例模式,单例注册表(Singleton Registry)是通过DefaultSingletonBeanRegistry这个类来实现的。其是负责管理单例bean的默认实现。它维护了一个单例缓存(通过ConcurrentHashMap实现的singletonObjects)来保存已经创建的单例bean实例,以及一个早期单例缓存(earlySingletonObjects)来保存在bean的实例化过程中早期暴露的bean实例。

实例化时机:

  • 单例: Bean 的实例化发生在容器启动阶段。当容器启动时,会根据配置或注解信息创建并初始化所有单例 Bean,并将它们放入容器中供后续使用。
  • 多例: 多例Bean在每次被请求时都会创建一个新的实例,因此实例化时机是在每次请求该Bean时。

1.4 循环依赖

问:什么是循环依赖?

什么是循环依赖?

  • 如代码所示,即 A 里面注入 B,B 里面又注入 A。此时,就发生了「循环依赖」。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class A {
    @Autowired
    private B b;
    }

    public class B {
    @Autowired
    private A a;
    }

问:怎么检测是否存在循环依赖?

  • 通过DefaultSingletonBeanRegistry类中的singletonsCurrentlyInCreation集合。这个集合用于跟踪当前正在创建的单例Bean的名称,如果发现正在创建中,说明存在循环依赖,抛出BeanCurrentlyInCreationException异常。
1
2

private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));

问:Spring中循环依赖场景?

  • 属性的循环依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class A {
    @Autowired
    private B b;
    }

    public class B {
    @Autowired
    private A a;
    }
  • 构造器的循环依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class ClassA {
    private ClassB b;

    public ClassA() {
    // 构造器中依赖ClassB
    this.b = new ClassB(this);
    }
    }

    public class ClassB {
    private ClassA a;

    public ClassB(ClassA a) {
    // 构造器中依赖ClassA
    this.a = a;
    }
    }

问:Spring 怎么解决单例 Bean 的循环依赖问题?⭐⭐⭐

  1. 解决方案三级缓存,只针对单例模式的属性循环依赖,不能解决其它模式的循环依赖问题。

  2. 哪三级缓存:三级缓存对应类 DefaultSingletonBeanRegistry 以及调用方 AbstractAutowireCapableBeanFactory 工厂类的doCreateBean

    • singletonObjects:第一级缓存,里面放置的是实例化好的单例对象,是最终的成品对象; 对象完成属性赋值和初始化,生成完整对象后放入一级缓存。

      1
      private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
    • earlySingletonObjects:第二级缓存,里面存放的是提前曝光的单例对象(尚未填充属性、未执行init方法),半成品对象; 从三级缓存中判断是否需要代理,决定返回代理对象还是普通对象后,放入二级缓存。

      1
      private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
    • singletonFactories:第三级缓存,存放 bean 工厂对象,是要被实例化的对象的对象工厂。ObjectFactory是函数接口,定义了getObject()方法,传入Bean名字和实例,通过AOP来创建,但不会立即调用。当对象调用createBeanInstance实例化后放入三级缓存,还未属性赋值和初始化

      1
      private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
  3. 核心方法getSingleton(name, ObjectFactory): 获取指定名称的单例bean,如果不存在,则使用提供的ObjectFactory接口创建一个新的实例。

    1. 尝试从一级缓存中获取。
    2. 若是获取不到,而且对象正在建立中,则尝试从二级缓存中获取。
    3. 若还是获取不到,且允许从三级缓存中经过 singletonFactory 的 getObject() 方法获取 Bean 对象,就会尝试从三级缓存中获取 Bean。
    4. 若是在三级缓存中获取到了 Bean,会将该 Bean 存放到二级缓存中。
  4. 三级缓存解决循环依赖的原因提前曝光半成品对象,当对象实例化后(还未进行属性赋值和初始化),会被放入三级缓存,提前曝光给IoC容器。

  5. 流程:

    1. 创建对象A,通过AbstractBeanFactory.createBean方法创建Bean(是DefaultSingletonBeanRegistry的子类),先进行实例化,实例化后加入三级缓存。调用 doCreateBean 方法时会调用 addSingletonFactory 方法,将已实例化但未属性赋值未初始化的对象 A 放入三级缓存 singletonFactories 中。即将对象 A 提早曝光给 IoC 容器。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
          boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
      if (earlySingletonExposure) {
      // 传入lambda到三级缓存
      this.addSingletonFactory(beanName, () -> {
      return this.getEarlyBeanReference(beanName, mbd, bean);
      });
      }

      protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
      Object exposedObject = bean;
      if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
      // 遍历beanPostProcessors找到SmartInstantiationAwareBeanPostProcessor接口
      Iterator var5 = this.getBeanPostProcessors().iterator();

      while(var5.hasNext()) {
      BeanPostProcessor bp = (BeanPostProcessor)var5.next();
      if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
      SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
      // 通过判断是否满足AOP条件,返回包装后的代理对象
      exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
      }
      }


      return exposedObject;
      }
    2. 实例化后继续为对象A进行属性赋值,发现A依赖对象B,尝试获取对象B。

    3. 发现B对象未创建,尝试创建对象B。

    4. 创建对象B,同A,执行实例化和属性赋值后,发现依赖对象A。

    5. 尝试在缓存中查找对象A:一级缓存未发现(因为A是半成品),二级缓存未发现A(还未完成属性赋值),三级缓存找到对象A若实现了AOP则调用,否则返回传入函数接口的Bean实例,将对象A放入二级缓存,避免重复创建。

    6. 对象B找到对象A后,继续执行属性赋值和初始化操作,完成后放入一级缓存,移除二级和三级缓存

    7. 此时对象A可以从一级缓存中找到对象B,继续完成后续操作。

源码分析:

  • 首先是DefaultSingletonBeanRegistry的一些关键方法和属性:

    1. singletonObjects: 一级缓存,保存已经实例化的单例bean。使用ConcurrentHashMap来保证线程安全。
    2. earlySingletonObjects: 二级缓存,保存在bean实例化过程中早期暴露的bean实例,即尚未完全初始化的bean。
    3. singletonFactories: 三级缓存,保存创建bean的工厂实例。
    4. singletonsCurrentlyInCreation: 检查是否存在循环依赖,用于快速检查某个bean是否已经注册。
    5. getSingleton(name, ObjectFactory): 获取指定名称的单例bean,如果不存在,则使用提供的ObjectFactory创建一个新的实例。
    6. createSingleton(name, ObjectFactory): 创建指定名称的单例bean实例。
    7. beforeSingletonCreation(name): 标记bean的创建过程已经开始。
    8. afterSingletonCreation(name): 标记bean的创建过程已经完成。
    9. destroySingleton(name): 销毁指定名称的单例bean。

    Spring通过以上方法和数据结构来维护单例bean的生命周期。在容器启动时,Spring会遍历配置文件或注解扫描的结果,通过createSingleton方法创建并注册所有的单例bean。在整个应用程序运行过程中,Spring负责维护这些单例bean的生命周期,包括初始化、依赖注入、销毁等阶段。

  • 函数调用过程:

    1. AbstractBeanFactory.getBean(A)
    2. AbstractBeanFactory.doGetBean(A)
    3. DefaultSingletonBeanRegistry.getSingleton(A, true):首次检查一级缓存到三级缓存
    4. DefaultSingletonBeanRegistry.getSingleton(A, createBean(A)):检查并创建一个Bean实例,调用doCreateBean。
    5. AbstractAutowireCapableBeanFactory.doCreateBean(A):
      • 首先实例化:createBeanInstance()
      • 实例化后先调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)):先放入三级缓存
      • 然后属性赋值:populateBean(),在这里注入依赖的属性。
        • 发现依赖对象B,B创建过程同A,在B属性赋值时发现A,此时再次getBean(A) -> doGetBean(A) -> getSingleton(A, true) ,此时发现三级缓存有A,则调用getObject()实例化并存入二级缓存
        • B继续完成初始化,并放入一级缓存。
      • 最后初始化:initializeBean()
      • 初始化后再次检查一级缓存:DefaultSingletonBeanRegistry.getSingleton(A, false),因为传入false,此时不放入二级缓存。
    6. DefaultSingletonBeanRegistry.getSingleton(A, createBean(A)) -> DefaultSingletonBeanRegistry.addSingleton:返回doGetBean函数,并通过addSingleton放入一级缓存,移除二级和三级缓存。
    7. returnBean
  • AbstractBeanFactory源码解析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
      
    public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
    throws BeansException {

    return doGetBean(name, requiredType, args, false);
    }

    protected <T> T doGetBean(
    String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    throws BeansException {

    // 得到标准的Bean名称,确保Bean名称的一致性
    String beanName = transformedBeanName(name);
    // 定义一个变量用于存储最终获取到的Bean实例
    Object beanInstance;

    // Eagerly check singleton cache for manually registered singletons.
    // 首次尝试从一级缓存获取Bean,调用getSingleton(beanName, true),检查一级到三级缓存
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
    if (logger.isTraceEnabled()) {
    if (isSingletonCurrentlyInCreation(beanName)) {
    logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
    "' that is not fully initialized yet - a consequence of a circular reference");
    }
    else {
    logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
    }
    }
    // 若该Bean已实例化过,则封装并返回从缓存中获取到的单例Bean实例
    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }

    else {
    // 缓存中不存在对应名称的单例Bean实例,或参数不为空
    // Fail if we're already creating this bean instance:
    // We're assumably within a circular reference.
    if (isPrototypeCurrentlyInCreation(beanName)) {
    // 检查是否正在创建原型Bean的实例,如果是,则抛出BeanCurrentlyInCreationException异常,防止出现循环引用
    throw new BeanCurrentlyInCreationException(beanName);
    }

    // Check if bean definition exists in this factory.
    BeanFactory parentBeanFactory = getParentBeanFactory();
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
    // 如果存在父BeanFactory且当前工厂中不包含对应名称的Bean定义,则委托给父BeanFactory进行获取
    // Not found -> check parent.
    String nameToLookup = originalBeanName(name);
    if (parentBeanFactory instanceof AbstractBeanFactory abf) {
    // 如果父BeanFactory是AbstractBeanFactory的实例,则调用其doGetBean方法,获取Bean实例
    return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
    }
    else if (args != null) {
    // Delegation to parent with explicit args.
    return (T) parentBeanFactory.getBean(nameToLookup, args);
    }
    else if (requiredType != null) {
    // No args -> delegate to standard getBean method.
    return parentBeanFactory.getBean(nameToLookup, requiredType);
    }
    else {
    return (T) parentBeanFactory.getBean(nameToLookup);
    }
    }

    if (!typeCheckOnly) {
    markBeanAsCreated(beanName);
    }

    StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
    .tag("beanName", name);
    try {
    if (requiredType != null) {
    beanCreation.tag("beanType", requiredType::toString);
    }
    // 获取合并后的本地Bean定义
    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    // 检查合并后的Bean定义,确保其有效性
    checkMergedBeanDefinition(mbd, beanName, args);

    // Guarantee initialization of beans that the current bean depends on.
    // 获取Bean定义中声明的依赖关系
    String[] dependsOn = mbd.getDependsOn();
    if (dependsOn != null) {
    for (String dep : dependsOn) {
    if (isDependent(beanName, dep)) {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
    }
    registerDependentBean(dep, beanName);
    try {
    getBean(dep);
    }
    catch (NoSuchBeanDefinitionException ex) {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
    }
    }
    }

    // Create bean instance.
    if (mbd.isSingleton()) {
    // 如果是单例Bean,则通过getSingleton(beanName, ObjectFactory<?> singletonFactory)方法获取单例Bean实例
    sharedInstance = getSingleton(beanName, () -> {
    try {
    // 其中函数接口为AbstractAutowireCapableBeanFactory.doCreateBean,通过其最终完成Bean的实例化
    return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
    // Explicitly remove instance from singleton cache: It might have been put there
    // eagerly by the creation process, to allow for circular reference resolution.
    // Also remove any beans that received a temporary reference to the bean.
    destroySingleton(beanName);
    throw ex;
    }
    });
    // 将获取到的单例Bean实例封装并赋值给beanInstance变量
    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }

    else if (mbd.isPrototype()) {
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
    beforePrototypeCreation(beanName);
    prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
    afterPrototypeCreation(beanName);
    }
    beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
    }

    else {
    String scopeName = mbd.getScope();
    if (!StringUtils.hasLength(scopeName)) {
    throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
    }
    Scope scope = this.scopes.get(scopeName);
    if (scope == null) {
    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
    }
    try {
    Object scopedInstance = scope.get(beanName, () -> {
    beforePrototypeCreation(beanName);
    try {
    return createBean(beanName, mbd, args);
    }
    finally {
    afterPrototypeCreation(beanName);
    }
    });
    beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
    }
    catch (IllegalStateException ex) {
    throw new ScopeNotActiveException(beanName, scopeName, ex);
    }
    }
    }
    catch (BeansException ex) {
    beanCreation.tag("exception", ex.getClass().toString());
    beanCreation.tag("message", String.valueOf(ex.getMessage()));
    cleanupAfterBeanCreationFailure(beanName);
    throw ex;
    }
    finally {
    beanCreation.end();
    if (!isCacheBeanMetadata()) {
    clearMergedBeanDefinition(beanName);
    }
    }
    }

    return adaptBeanInstance(name, beanInstance, requiredType);
    }
  • AbstractAutowireCapableBeanFactory源码分析:

    doCreateBean主要完成Bean实例的创建,主要包括:实例化 -> 属性赋值 -> 初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {

    // 1.实例化Bean Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
    // 尝试从单例缓存中获取实例包装器
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
    // 实例化Bean
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
    mbd.resolvedTargetType = beanType;
    }

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
    if (!mbd.postProcessed) {
    try {
    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
    }
    catch (Throwable ex) {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
    "Post-processing of merged bean definition failed", ex);
    }
    mbd.markAsPostProcessed();
    }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    // 2.提前暴露单例 Bean,解决循环引用的问题
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
    logger.trace("Eagerly caching bean '" + beanName +
    "' to allow for resolving potential circular references");
    }
    // 若Bean还未创建到一级缓存,则先将lambda传入到三级缓存暂存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 3.初始化Bean Initialize the bean instance.
    Object exposedObject = bean;
    try {
    // 填充 Bean 属性,调用AbstractBeanFactory.doGetBean查找依赖属性,doGetBean中仍然通过getSingleton查找实例对象
    populateBean(beanName, mbd, instanceWrapper);
    // 初始化 Bean 实例
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
    if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
    throw bce;
    }
    else {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
    }
    }

    // 如果启用了提前暴露单例 Bean
    if (earlySingletonExposure) {
    // 再次检查一级到二级缓存,此时应该返回一级或二级缓存中的Bean实例
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
    if (exposedObject == bean) {
    exposedObject = earlySingletonReference;
    }
    else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    String[] dependentBeans = getDependentBeans(beanName);
    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    for (String dependentBean : dependentBeans) {
    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
    actualDependentBeans.add(dependentBean);
    }
    }
    if (!actualDependentBeans.isEmpty()) {
    throw new BeanCurrentlyInCreationException(beanName,
    "Bean with name '" + beanName + "' has been injected into other beans [" +
    StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
    "] in its raw version as part of a circular reference, but has eventually been " +
    "wrapped. This means that said other beans do not use the final version of the " +
    "bean. This is often the result of over-eager type matching - consider using " +
    "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
    }
    }
    }
    }

    // Register bean as disposable.
    // 注册 Bean为可处理
    try {
    registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
    throw new BeanCreationException(
    mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    // 返回最终的 Bean 实例
    return exposedObject;
    }
  • DefaultSingletonBeanRegistry源码分析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
      
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    // 锁定一级缓存
    synchronized (this.singletonObjects) {
    // 若一级缓存不存在Bean,则放入三级缓存
    if (!this.singletonObjects.containsKey(beanName)) {
    this.singletonFactories.put(beanName, singletonFactory);
    this.earlySingletonObjects.remove(beanName);
    this.registeredSingletons.add(beanName);
    }
    }
    }

    public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
    }

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    // 首次检查
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null && allowEarlyReference) {
    // 加锁
    synchronized (this.singletonObjects) {
    // Consistent creation of early reference within full singleton lock
    singletonObject = this.singletonObjects.get(beanName);
    // 二次检查
    if (singletonObject == null) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null) {
    // 三级缓存能获取到
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
    // 调用代理对象的函数,放入二级缓存,移除三级缓存
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
    }
    }
    }
    }
    }
    }
    return singletonObject;
    }

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
    // 从一级缓存获取Bean
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
    if (this.singletonsCurrentlyInDestruction) {
    throw new BeanCreationNotAllowedException(beanName,
    "Singleton bean creation not allowed while singletons of this factory are in destruction " +
    "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
    }
    if (logger.isDebugEnabled()) {
    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
    }
    // 创建单例Bean前的预处理的操作
    beforeSingletonCreation(beanName);
    // 标记是否创建了新的单例Bean
    boolean newSingleton = false;
    // 标记是否需要记录抑制的异常
    boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
    if (recordSuppressedExceptions) {
    this.suppressedExceptions = new LinkedHashSet<>();
    }
    try {
    // 通过传入的ObjectFactory实例创建新的单例Bean
    singletonObject = singletonFactory.getObject();
    newSingleton = true;
    }
    catch (IllegalStateException ex) {
    // Has the singleton object implicitly appeared in the meantime ->
    // if yes, proceed with it since the exception indicates that state.
    singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
    throw ex;
    }
    }
    catch (BeanCreationException ex) {
    if (recordSuppressedExceptions) {
    for (Exception suppressedException : this.suppressedExceptions) {
    ex.addRelatedCause(suppressedException);
    }
    }
    throw ex;
    }
    finally {
    if (recordSuppressedExceptions) {
    this.suppressedExceptions = null;
    }
    // 创建单例Bean后的操作
    afterSingletonCreation(beanName);
    }
    if (newSingleton) {
    // 创建单例Bean成功,加入一级缓存
    addSingleton(beanName, singletonObject);
    }
    }
    return singletonObject;
    }
    }

问:Spring解决不了的循环依赖场景?⭐

不是所有的循环依赖Spring都能够解决的,三级缓存无法解决的:

  1. 构造器的循环依赖:因为构造器的调用发生在对象实例化之前,而三级缓存是在对象实例化过程中使用的。A和B都需要在构造函数中完成初始化,而此时二者都不在三级缓存中。
    • 对于构造器注入产生的循环依赖,可以使用 @Lazy 注解,延迟加载。在构造函数中使用Lazy注解延迟加载。在注入依赖时,先注入代理对象,等到第一次使用时才进行实际的创建。这就意味着即使存在构造器循环依赖,它们的实例化可以被推迟,从而避免了无法解决的循环依赖问题。
    • 改用字段注入或setter方法注入。
    • 代码重构,消除循环依赖场景。
  2. prototype 原型作用域循环依赖:IoC 容器只会管理单例 Bean 的生命周期,并将单例 Bean 存放到缓存池中(三级缓存)。Spring 并不会管理 prototype 作用域的 Bean,也不会缓存该作用域的 Bean,而 Spring 中循环依赖的解决正是通过缓存来实现的。
  3. 多例循环依赖:多实例 Bean 是每次调用 getBean 都会创建一个新的 Bean 对象,该 Bean 对象并不能缓存。而 Spring 中循环依赖的解决正是通过缓存来实现的。

问:为什么一定要三级缓存,可以只用二级缓存吗?⭐⭐

只有三级缓存能满足构造方法实例化->依赖注入->动态代理这个完整流程。

  • 只有一级缓存:只存储初始化完成对象
  • 只有二级缓存:一级存储初始化完成对象,二级存储早期曝光的半成品对象。无法支持动态代理
    • 假设对象A需要动态代理,A的代理对象在二级缓存时还未生成。
    • 所以从二级缓存获取的A对象,与实际A经过AOP生成的对象不是同一个。
  • 三级缓存:存放的是ObjectFactory函数式接口,其getObject方法只有需要A时才会调用,直接创建代理对象A。
    • 实例化A,未填充属性,放入三级缓存,发现依赖B
    • 实例化B,未填充属性,放入三级缓存,发现依赖A
    • 从三级取到A,ObjectFactory#getObject()返回代理A,并放入二级缓存。
    • B填充属性,完成初始化,放入一级缓存。
    • A从一级找到B,填充属性,完成初始化,放入一级缓存。
  • 是否可以把AOP调用提前到实例化来解决重复创建问题,并且不用三级缓存?
    • Spring是在初始化后,而不是实例化后直接调用AOP生成代理对象,实例化后仅仅保存一下lambda表达式,因为不希望破坏正常Bean的生命周期。三级缓存存放一个函数接口,出现循环依赖时判断对象是否需要进行AOP。
    • 若提前创建AOP代理对象,此时依赖注入还未完成,代理对象会依赖于不完整的Bean。
    • Spring的AOP是在Bean初始化后进行的,通过AnnotationAwareAspectJAutoProxyCreator后置处理器完成。若Bean需要进行AOP,最终会得到一个代理对象。

可以不用二级缓存吗? 不可以,如果没有二级缓存,多重循环依赖时无法保证得到单例的对象

  • 假设没有二级缓存,当对象B获取到对象A的三级缓存并创建成功,此时若又有依赖A的对象C创建,不管三级缓存能不能获取到A,最终得到的对象都可能不是同一个代理对象。
  • 二级缓存无法解决避免多重循环依赖时,重复创建动态代理的问题:假如A被多个对象依赖,此时A还在实例化,其它对象每次getBean(A)时,都会创建一次动态代理,而通过二级缓存感知即可避免。

问:@Async为什么会导致循环依赖解决不了

@Async 注解用于声明一个方法是异步的,当这个方法被调用时,会在另一个线程中执行,而调用方不会等待它的完成。在 Spring 中,@Async 注解通常与 @EnableAsync 注解一起使用,以启用异步方法的支持。

循环依赖问题通常发生在 Spring 容器在创建 Bean 的过程中。循环依赖是指两个或多个 Bean 之间相互引用,形成一个闭环,而这种循环依赖的解决通常需要通过 Spring 容器在创建 Bean 的过程中暂时提供一个未完全初始化的 Bean。然而,异步方法的实现往往涉及到动态代理和 AOP,这使得容器可能无法正确地暂时提供未完全初始化的 Bean。

这是因为异步方法的代理对象在容器中的创建与原始 Bean 的创建过程有一定的耦合。

为了解决这个问题,可以考虑以下几种方式:

  1. 尽量避免在存在循环依赖的 Bean 上使用 @Async 注解。
  2. 考虑使用其他方式实现异步操作,如使用 TaskExecutor 接口。
  3. 通过调整 Bean 的初始化顺序来规避循环依赖。

总的来说,循环依赖和异步方法在某些情况下可能会产生冲突,需要谨慎设计和使用以避免出现问题。

问:@Lazy注解如何解决循环依赖问题?

@Lazy注解:

  1. 延迟创建:懒加载的Bean只有首次使用时才会被创建,和@Component、@Bean、@Configuration等配合。
  2. 延迟注入:@Lazy注解与@Autowired搭配、应用于构造方法、应用在构造方法的属性时,Spring会先给属性注入一个代理对象,当首次访问时才会执行代理对象的逻辑,注入一个真正的Bean。

当对象A和B循环依赖时,A构造方法使用@Lazy,A完成实例化后,会给构造方法的参数B先注入一个代理对象,此时A可以完成后续对象创建步骤。

1.5 Spring AOP

问:什么是AOP?面向切面的基本概念?

AOP(Aspect-Oriented Programming),面向切面编程,是一种软件开发的编程范式。AOP 的目标是通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,以提高代码的模块化、可维护性和可重用性。在传统的面向对象编程(OOP)中,程序的功能被划分为各个类,而某些功能可能会分散在多个类中。例如,日志记录、事务管理、权限控制等功能可能会在多个类的方法中散布。AOP 的思想是将这些横切关注点独立出来,形成单独的切面(Aspect),然后通过横切(weaving)的方式将切面插入到程序的执行流程中。

AOP 的关键概念包括:

  1. 切面(Aspect):切面是一个横切关注点的模块。它包含了一系列的通知(advice),定义了在哪些地方、何时执行这些通知。使用@Aspect注解标注的类
  2. 连接点(Join Point):****目标方法执行的时机。通常连接点是程序中的方法调用,比如一个方法的执行,一个异常的处理,但也可以是字段的访问或其他某些事件。
  3. 切入点(Pointcut):切入点是一组连接点的集合。它定义了切面在哪里生效。通过 @Pointcut("execution(* com.Service.*.*(..))") 匹配目标方法的规则
  4. 通知(Advice):通知定义了在程序的哪个时刻,以及在执行的哪个点上执行切面的代码。通知的类型包括:同时是注解名如@Before,@After,@AfterReturning,@AfterThrowing,@Around
    • 前置通知(before advice):方法之前执行
    • 后置通知(after advice):方法return后执行,无论成功还是异常
    • 异常通知(after-throwing advice):方法抛异常后执行
    • 返回通知(after-returning advice):成功返回通知
    • 最终通知(after-finally advice):方法执行完finally后执行,比return更后执行
    • 环绕通知(around advice):方法执行的前后,可以自定义执行顺序,控制方法执行。
  5. 横切(Weaving):横切是将切面的代码插入到程序的执行流程中的过程。横切可以在编译时、类加载时或运行时发生。

AOP 的主要优势在于解耦关注点,提高代码的可维护性。通过 AOP,可以将横切关注点的代码从主要业务逻辑中分离出来,使得代码更加清晰、可读,并且易于维护。 Spring 框架是一个广泛使用 AOP 的典型例子,它提供了强大的 AOP 支持。

在这个示例中:

  • @Aspect 注解标识这是一个切面。
  • @Pointcut 注解定义了切入点,匹配所有以 “Service” 结尾的类的所有方法。
  • 不同类型的通知通过 @Before@After@AfterReturning@AfterThrowing@Around 注解定义。
  • JoinPoint 用于获取连接点的信息,例如方法签名等。
  • @Around 注解的方法中,通过 ProceedingJoinPoint 执行目标方法,并在前后进行处理。

请注意,以上示例使用了Spring AOP和AspectJ的注解。这是基于Spring框架的AOP实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 定义切面
@Aspect
@Component
public class MyAspect {

// 定义切入点,匹配所有以Service结尾的类的所有方法
@Pointcut("execution(* *.*Service.*(..))")
public void serviceMethods() {}

// 前置通知
@Before("serviceMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before advice: Executing before " + joinPoint.getSignature().getName());
}

// 后置通知
@After("serviceMethods()")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("After advice: Executing after " + joinPoint.getSignature().getName());
}

// 后置返回通知
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
System.out.println("After returning advice: Executing after returning from " + joinPoint.getSignature().getName() +
". Result: " + result);
}

// 异常通知
@AfterThrowing(pointcut = "serviceMethods()", throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
System.out.println("After throwing advice: Executing after throwing exception from " +
joinPoint.getSignature().getName() + ". Exception: " + exception.getMessage());
}

// 环绕通知
@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around advice: Executing around " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
System.out.println("Around advice: Executing after " + joinPoint.getSignature().getName() +
". Result: " + result);
return result;
}
}

问:AOP 主要应用场景?AOP在什么场景下会失效?

  1. 日志记录:AOP 可以用于在方法执行前、后或抛出异常时记录日志,避免在每个方法中都编写相似的日志记录代码。
  2. 事务管理:AOP 可以用于将事务的开启、提交、回滚等操作织入到方法的执行过程中,使得事务管理更加方便。
  3. 权限控制:AOP 可以用于实现权限控制,例如在方法执行前判断用户是否有权限执行该方法。
  4. 性能监控:AOP 可以用于在方法执行前后统计方法的执行时间,帮助进行性能监控和优化。
  5. 异常处理:AOP 可以用于处理方法执行过程中抛出的异常,进行统一的异常处理或记录异常信息。
  6. 缓存管理:AOP 可以用于在方法执行前检查缓存,如果缓存中存在相应的结果,则直接返回缓存中的数据,避免重复计算。
  7. 国际化:AOP 可以用于在方法执行前根据用户的语言偏好设置,实现国际化的功能。
  8. 事件驱动:AOP 可以用于实现事件驱动的编程模型,例如在某个方法执行后触发某个事件。
  9. 跟踪调试:AOP 可以用于在方法执行前后插入跟踪代码,帮助进行调试和排查问题。

AOP 的主要优势在于解耦关注点,使得系统的各个模块更加独立,易于维护和扩展。通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,提高了代码的可维护性和可重用性。 AOP 在实际应用中通常与其他设计模式和框架结合使用,以更好地满足系统的需求。

失效场景?

  • 当前类没有被Spring容器所管理:AOP是在Bean创建初始化后进行的,若类没有被容器管理就无法进行AOP。
  • 同一个类中方法的调用:直接this.XXX(),而不是XXX.XXX();
  • 内部类方法的调用:直接调用内部类实例对象的方法,而不是代理对象。
  • 私有方法:代理对象无法调用私有方法。
  • static修饰的方法:属于类对象,而不是对象实例,无法被代理对象调用。
  • final修饰的方法:无法被重写,无法被代理对象调用。

问:讲一下Spring AOP 的原理?AOP使用哪种动态代理?JDK动态代理与CGlib的区别?⭐⭐⭐

Spring Bean创建过程中有许多扩展点,AOP本身就是扩展功能,在BeanPostProcessor阶段实现。Spring AOP(Aspect-Oriented Programming)的原理基于动态代理。在运行时生成代理对象,增强方法的功能,而不影响原始方法。主要使用两种方式实现 AOP 的动态代理:JDK 动态代理CGLIB(Code Generation Library)动态代理。在ProxyFactory类中进行了封装。

AOP流程:

  1. 生成代理对象,代理对象的创建过程:

    1. 识别切面(Aspect)
      • Spring在容器启动时,通过类路径扫描,查找标注了 @Aspect 注解的类,并将它们注册为切面。
      • 切面是由切点(Pointcut)和通知(Advice)组成的,它定义了增强的逻辑。
    2. 解析切点(Pointcut)
      • 切点用于指定哪些方法需要被代理,@Pointcut("execution(* com.example.service.*.*(..))") 表示匹配指定包下的所有方法。
      • Spring AOP使用表达式来定义切点,通常是基于方法签名、类路径等来选择目标方法。
    3. 创建通知(Advice)
      • 通知是增强的逻辑,Spring支持多种类型的通知,比如:
        • @Before:在方法执行之前执行
        • @After:方法执行后执行(无论方法是否抛出异常)
        • @AfterReturning:方法正常执行后执行
        • @AfterThrowing:方法抛出异常时执行
        • @Around:环绕通知,既可以在方法执行前后增加逻辑
      • 通知是与切点结合的,只有切点匹配的方法才会执行对应的通知。
    4. 选择代理方式:根据目标类是否实现接口来选择JDK动态代理或CGLIB动态代理
    5. 返回代理对象:Spring最终返回生成的代理对象,在调用该代理对象时,Spring会根据配置的切点和通知执行增强逻辑。
  2. 执行方法调用时,调用代理对象的 intercept() 方法

    • 当调用代理对象的方法时,Spring会通过 DynamicAdvisedInterceptor 拦截方法调用。
    • 代理对象的方法会被 DynamicAdvisedInterceptor 代理,这个类是代理机制的核心,负责方法的执行和增强。
    • intercept() 方法中,Spring会执行拦截器链,按照定义的顺序依次调用每个通知(如 @Before@After 等)。
  3. 根据定义好的通知生成拦截器链

    • Spring AOP会根据切点和通知生成拦截器链。每个通知都会被包装成一个 MethodInterceptor,并按顺序加入到拦截器链中。
    • 如果是环绕通知(@Around),则通过 ProceedingJoinPoint 可以控制是否继续执行目标方法。
    • 其他通知(@Before, @After)会在目标方法执行之前或之后执行。
  4. 从拦截器链中依次获取每个通知

    • 在代理对象方法执行时,Spring会逐个执行拦截器链中的每个通知(如:前置通知、后置通知等)。
    • 对于环绕通知(@Around),通知方法可以决定是否继续执行目标方法,或者在目标方法执行前后增加额外的逻辑。
    • CGLIB生成的代理类会创建一个 CGLibMethodInvocation 对象,通过它实现方法的顺序执行,目标方法的返回结果和异常都会被捕获并传递给后续的通知。

源码:

  • AbstractAutowireCapableBeanFactory中initializeBean方法初始化Bean:

    • initializeBean 会依次调用 BeanPostProcessor 进行后处理,比如 applyBeanPostProcessorsAfterInitialization()
  • 随后调用applyBeanPostProcessorsAfterInitialization()遍历所有BeanPostProcessor:

    • 遍历所有注册的 BeanPostProcessor,并调用它们的 postProcessAfterInitialization() 方法。
    • 如果 BeanPostProcessorInstantiationAwareBeanPostProcessor 类型,Spring会在此时检查是否需要对该 Bean 进行增强(例如:AOP)。
  • wrapIfNecessary() 方法

    • 该方法会判断当前 Bean 是否需要进行 AOP 增强。如果是,Spring会通过 AopProxyFactory 创建代理对象。
    • wrapIfNecessary() 会返回一个增强后的代理对象。
  • 若需要则调用createProxy方法生成代理对象并返回。

    **createProxy()**:

    • createProxy 是 Spring AOP 代理对象生成的核心方法。根据目标对象的类型(是否有接口),它会选择 JDK 动态代理或 CGLIB 代理。
    • 对于 JDK 动态代理,Spring会使用 Proxy.newProxyInstance() 创建代理对象。
    • 对于 CGLIB,Spring通过 Enhancer.create() 来创建代理对象。

JDK动态代理与CGlib的区别?

  • JDK 动态代理:JDK 动态代理是基于 Java 的 java.lang.reflect 包提供的 Proxy 类和 InvocationHandler 接口实现的。代理类来自JDK类库,实现接口。通过反射来调用目标类。

    包括:

    • 定义一个接口(或者使用已有的接口)作为代理对象的接口。
    • 实现 InvocationHandler 接口,编写代理逻辑。
    • 使用 Proxy.newProxyInstance() 创建代理对象

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // 定义一个接口(或者使用已有的接口)作为代理对象的接口
    public interface UserService {
    void addUser(String name);
    }

    public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
    System.out.println("User added: " + name);
    }
    }

    // 实现 `InvocationHandler` 接口,编写代理逻辑
    public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 方法执行前,需要XXX
    System.out.println("Before method execution");
    // 执行方法
    Object result = method.invoke(target, args);
    // 方法执行后,需要XXX
    System.out.println("After method execution");
    return result;
    }
    }

    public class JDKProxyExample {
    // 创建代理对象
    UserService userService = new UserServiceImpl();

    // 使用 `Proxy.newProxyInstance()` 创建代理对象
    UserService proxy = (UserService) Proxy.newProxyInstance(
    userService.getClass().getClassLoader(),
    userService.getClass().getInterfaces(),
    new MyInvocationHandler(userService));

    // 使用代理对象
    proxy.addUser("Alice");
    }
    输出
    Before method execution
    User added: Alice
    After method execution
  • CGLIB 动态代理:CGLIB 是一个代码生成库,通过在运行时生成目标类的子类来实现动态代理。依赖第三方ASM框架生成代理类。通过 Enhancer.create() 生成代理对象。代理类直接继承目标类,通过子类代理目标类。

    包括:

    • CGLIB 利用字节码生成库,为目标类创建一个子类,并覆盖其中的方法。
    • 在子类中,可以在目标方法的前后插入增强逻辑。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class MyService {
    public void doSomething() {
    System.out.println("Doing something...");
    }
    }

    // 实现MethodInterceptor接口来定义代理逻辑
    public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    System.out.println("Before method execution");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("After method execution");
    return result;
    }
    }

    // 创建代理对象
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(MyService.class);
    enhancer.setCallback(new MyMethodInterceptor());
    MyService proxy = (MyService) enhancer.create();

    // 使用代理对象
    proxy.doSomething();

Spring AOP 中的选择:

  • 如果目标类实现了接口或者是Proxy的子类,Spring AOP 将使用 JDK 动态代理。
  • 如果目标类没有实现接口,Spring AOP 将使用 CGLIB 动态代理。

JDK动态代理和 CGLib区别:

  1. 代理方式:
    • 动态代理: 动态代理是基于接口的代理,要求目标类必须实现至少一个接口。
    • CGLib: CGLib 是基于继承的代理,它通过生成目标类的子类来实现代理。不要求目标类必须实现接口。
  2. 生成代理类的方式:
    • 动态代理: 动态代理在运行时通过反射机制动态生成代理类。
    • CGLib: CGLib 利用字节码生成技术,在运行时生成目标类的子类,该子类作为代理类。
  3. 性能:
    • JDK:
      • 早期版本,因为反射,性能差于CGLIB。
      • JDK采用LambdaMetafactory直接生成字节码,性能提高。
    • CGLib:
      • CGLib 的性能相对较高,因为它直接操作字节码,无需通过反射调用。

问:代理对象调用过程(责任链+递归调用)?

责任链(Chain of Responsibility)是一种行为设计模式,其主要目的是将请求的发送者和接收者解耦,使多个对象都有机会处理请求。请求在一条链上依次经过多个处理者,每个处理者判断是否处理该请求,如果能够处理,则直接处理;否则,将请求传递给链上的下一个处理者。

责任链模式的核心思想是建立一个对象链,请求在链上传递,直到有一个对象处理它为止。这样做的好处是可以动态地改变链中的对象或顺序,而无需修改发送者和接收者的代码。

在责任链模式中,通常有以下角色:

  1. Handler(处理者): 定义一个处理请求的接口,并保持一个对下一个处理者的引用。如果自己能够处理请求,则直接处理;否则,将请求传递给下一个处理者。

  2. ConcreteHandler(具体处理者): 实现Handler接口,具体处理请求的对象。

  3. Client(客户端): 创建请求并将其发送到链上的第一个处理者。

责任链模式常用于以下场景:

  • 有多个对象可以处理同一请求,但具体处理者在运行时确定。
  • 在不明确指定接收者的情况下,向多个对象中的一个发送请求。
  • 可动态指定处理链,增强系统的灵活性。

在AOP(面向切面编程)中,责任链模式的概念被广泛应用。AOP中的切面(Aspect)就可以看作是责任链中的处理者,切入点(Pointcut)则是责任链的判断条件。

在AOP的代理对象调用过程中,责任链和递归调用更多地体现在AOP的通知(Advice)执行的过程中。在AOP中,通知的执行顺序可以看作是责任链,而通知的调用过程可能涉及到递归调用。

  1. 责任链: AOP的通知执行顺序是由切面中的各个通知的顺序决定的。通常情况下,通知的执行顺序包括前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)。这些通知形成一个责任链,按照定义的顺序依次执行。

  2. 递归调用: 在AOP中,特别是在环绕通知(Around)中,通知可以调用 proceed() 方法来继续执行下一个通知或目标方法。这种递归调用的方式允许在通知链中的某个点继续执行下一个通知。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Aspect
    public class MyAspect {
    @Around("execution(* com.example.MyService.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before method execution...");
    Object result = joinPoint.proceed(); // 递归调用,继续执行下一个通知或目标方法
    System.out.println("After method execution...");
    return result;
    }
    }

问:AOP如何实现?源码分析?TODO

  • @EnableAspectJAutoProxy给容器(beanFactory)中注册一个AnnotationAwareAspectJAutoProxyCreator对象;
  • AnnotationAwareAspectJAutoProxyCreator对目标对象进行代理对象的创建,对象内部,是封装JDK和CGlib两个技术,实现动态代理对象创建的(创建代理对象过程中,会先创建一个代理工厂,获取到所有的增强器(通知方法),将这些增强器和目标类注入代理工厂,再用代理工厂创建对象);
  • 代理对象执行目标方法,得到目标方法的拦截器链,利用拦截器的链式机制,依次进入每一个拦截器进行执行

1.6 Spring 事务

问:讲一下Spring事务管理机制及执行流程?⭐⭐⭐

Spring提供了编程式和声明式两种事务管理机制。

  • 编程式事务:调用Spring提供的相关API来手动操作和管理事务。
    • 通过TransactionTemplatePlatformTransactionManager两种方式来实现。
    • 优点:可以自定义、更加详细的管理事务。
    • 缺点:对业务代码有一定侵入性,复杂度高。
  • 声明式事务:使用注解**@TransactionalXML配置**的方式来管理事务。
    • 最常用注解的方式,因为简单易用。
    • 底层基于AOP实现,在目标方法被调用之前,代理对象会创建或加入一个事务,在目标方法执行完毕后,代理对象提交事务,若执行出现异常,代理对象回滚事务。
    • 优点:对业务代码侵入性低,可维护性高。
    • 缺点:只能做到方法级别的最小颗粒度。
    • 事务会影响数据库的QPS,并且使用时要考虑到各个场景的回滚方案,如缓存回滚、搜索引擎回滚、消息补偿、统计修正等。

代码示例:

  1. 编程式事务(使用TransactionTemplate):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.TransactionCallbackWithoutResult;
    import org.springframework.transaction.support.TransactionTemplate;

    @Service
    public class ProgrammaticTransactionService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void performTransaction() {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {
    try {
    // 执行业务逻辑,可能包含多个数据库操作
    jdbcTemplate.update("INSERT INTO users (username, password) VALUES (?, ?)", "user1", "pass1");
    jdbcTemplate.update("UPDATE account SET balance = balance - 100 WHERE user_id = ?", 1);
    } catch (Exception e) {
    // 发生异常,回滚事务
    status.setRollbackOnly();
    throw e;
    }
    }
    });
    }
    }
  2. 编程式事务(使用PlatformTransactionManager):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.DefaultTransactionDefinition;

    @Service
    public class ProgrammaticTransactionService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void performTransaction() {
    // 定义事务属性
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

    // 开启事务
    TransactionStatus status = transactionManager.getTransaction(transactionDefinition);

    try {
    // 执行业务逻辑,可能包含多个数据库操作
    jdbcTemplate.update("INSERT INTO users (username, password) VALUES (?, ?)", "user1", "pass1");
    jdbcTemplate.update("UPDATE account SET balance = balance - 100 WHERE user_id = ?", 1);

    // 提交事务
    transactionManager.commit(status);
    } catch (Exception e) {
    // 发生异常,回滚事务
    transactionManager.rollback(status);
    throw e;
    }
    }
    }
  3. 声明式事务(使用@Transactional):在声明式事务中,通过@Transactional注解标记需要进行事务管理的方法,Spring会在方法开始前开启事务,在方法执行完毕后根据情况提交或回滚事务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    @Service
    public class DeclarativeTransactionService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void performTransaction() {
    // 执行业务逻辑,可能包含多个数据库操作
    jdbcTemplate.update("INSERT INTO users (username, password) VALUES (?, ?)", "user1", "pass1");
    jdbcTemplate.update("UPDATE account SET balance = balance - 100 WHERE user_id = ?", 1);
    }
    }
  4. 声明式事务(使用XML):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 数据源配置,省略 -->

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 启用注解驱动的事务管理 -->
    <tx:annotation-driven/>

    <!-- 声明式事务的服务类 -->
    <bean id="declarativeTransactionService" class="com.example.DeclarativeTransactionService">
    <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    </beans>

    然后,服务类 DeclarativeTransactionService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;

    public class DeclarativeTransactionService {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
    }

    public void performTransaction() {
    // 执行业务逻辑,可能包含多个数据库操作
    jdbcTemplate.update("INSERT INTO users (username, password) VALUES (?, ?)", "user1", "pass1");
    jdbcTemplate.update("UPDATE account SET balance = balance - 100 WHERE user_id = ?", 1);
    }
    }

    此时,DeclarativeTransactionService 中的 performTransaction 方法上不需要添加 @Transactional 注解,而是通过XML配置中的 <tx:annotation-driven/> 启用了注解驱动的事务管理。 Spring 会在执行 performTransaction 方法时自动开启、提交或回滚事务。

Spring为事务管理提供了一致的编程模板,在高层次建立了统一的事务抽象,像Spring DAO为不同的持久化提供了模板类一样,Spring也提供了事务模板类TransactionTemplate。通过模板类并配合使用事务回调TransactionCallback指定具体的持久化操作,就可以通过编程方式实现事务管理,而无需关注资源获取、复用、释放、事务同步和异常处理等操作。

Spring事务通过创建 BeanFactoryTransactionAttributeSourceAdvisor 并将 TransactionInterceptor 注入其中,实现了在Spring AOP中对事务的处理。TransactionInterceptor 实现了 Advice 接口,而Spring AOP会将Advisor中的Advice转换成拦截器链,然后调用。这种方式使得Spring事务能够通过AOP的方式插入到方法执行的流程中,实现了声明式事务管理。这种方式适用于希望通过注解或XML配置来声明事务属性,实现声明式事务管理的场景。

Spring事务管理主要涉及到以下几个关键的概念和接口:

  1. 事务管理器(Transaction Manager): PlatformTransactionManager 接口是Spring事务管理的核心接口,定义了事务的开始、提交、回滚等基本操作。Spring提供了多个实现类,如DataSourceTransactionManagerJtaTransactionManager等,用于与不同的事务管理机制集成。

  2. 事务定义(Transaction Definition): TransactionDefinition 接口定义了事务的隔离级别、传播行为、超时时间等属性。在Spring中,通过DefaultTransactionDefinition等实现类来表示事务的属性。

  3. 事务状态(TransactionStatus): TransactionStatus 接口用于表示事务的状态,包括是否新的事务、是否已经完成、是否回滚等。

  4. 事务注解: Spring提供了基于注解的事务管理,通过@Transactional注解可以在方法或类级别声明事务属性。

Spring事务的组成部分

  1. 事务管理器(Transaction Manager)

    • Spring 提供了不同类型的事务管理器来支持不同的数据源:
      • DataSourceTransactionManager:支持 JDBC 事务
      • JpaTransactionManager:支持 JPA 事务
      • HibernateTransactionManager:支持 Hibernate 事务
      • JtaTransactionManager:支持 JTA 事务,通常用于分布式事务

    事务管理器的职责是开启、提交、回滚事务。

  2. 事务代理(Transaction Proxy)

    • Spring AOP 使用代理来实现事务的自动管理,通常是基于 JDK 动态代理或 CGLIB 代理的方式。
    • Spring 会在切点处插入事务逻辑,决定何时开始事务、何时提交或回滚。
  3. 事务属性(Transaction Attributes)

    • Spring 提供了注解和 XML 配置来定义事务的属性。
    • 最常用的事务属性包括:
      • propagation:事务传播行为(事务如何与其他事务交互)
      • isolation:事务隔离级别(事务如何隔离其他事务的影响)
      • timeout:事务的超时时间
      • readOnly:指定事务是否为只读事务,优化数据库操作
      • rollbackFor:指定哪些异常会导致事务回滚
  4. 事务管理接口(PlatformTransactionManager)

    • PlatformTransactionManager 是事务管理器的核心接口。它定义了开始、提交、回滚等方法,Spring 通过它来管理事务。
    • 通常不会直接使用 PlatformTransactionManager,而是通过事务注解或 XML 配置来间接使用。

Spring事务的基本执行流程:

  1. 获取对应事务属性,也就是获取 @Transactional 注解上的属性。事务的定义可以通过XML配置或使用@Transactional注解进行声明。定义事务的隔离级别、传播行为、超时时间等属性。判断是否需要开始新事务。
  2. 获取TransactionManager,常用的如DataSourceTransactionManager事务管理。Spring会根据配置选择合适的事务管理器。获取数据库连接、开启事务。
  3. 执行具体方法逻辑(如SQL)。
  4. 回调执行下一个调用链。
  5. 一旦出现异常,尝试异常处理,回滚事务。通过completeTransactionAfterThrowing来完成回滚操作,具体回滚逻辑通过doRollBack方法来实现。
  6. 正常执行完毕,提交事务,调用 commit() 方法。通过completeTransactionAfterReturning来完成提交操作,具体提交逻辑由doCommit方法来实现。
  7. 事务执行完毕,需要清除相关的事务信息,释放资源,cleanupTransactionInfo

问:@Transaction?

@Transaction?底层实现是AOP,动态代理

  1. 实现是通过Spring代理来实现的。生成当前类的代理类,调用代理类的invoke()方法,在invoke()方法中调用 TransactionInterceptor拦截器的invoke()方法;
  2. 非public方式其事务是失效的;
  3. 自调用也会失效,因为动态代理机制导致
  4. 多个方法外层加入try…catch,解决办法是可以在catch里 throw new RuntimeException()来处理

问:Spring事务如何回滚?

在 Spring 中,事务回滚是通过 异常 来控制的。默认情况下,Spring 只有在 未检查异常(RuntimeException)Error 时回滚事务。可以通过配置来定制回滚的行为。

默认的回滚规则

  • 事务会在遇到 RuntimeExceptionError 时回滚。
  • 事务 不会 在遇到 **checked exceptions**(如 SQLException)时回滚。

定制回滚规则

  • 使用 @Transactional注解的 rollbackFor或 noRollbackFor

    属性可以定制哪些异常会触发回滚。例如:

    1
    2
    3
    4
    @Transactional(rollbackFor = Exception.class) // 所有异常都会触发回滚
    public void someMethod() {
    // 业务逻辑
    }

回滚示例

1
2
3
4
5
6
7
@Transactional(rollbackFor = SQLException.class)
public void someMethod() throws SQLException {
// 业务逻辑
if (someCondition) {
throw new SQLException("Database error!");
}
}

在这个例子中,即使是 SQLException 这样的 checked exception,Spring 也会回滚事务。

方法级回滚配置

可以在方法上使用 @Transactional 注解来定制回滚行为:

1
2
3
4
@Transactional(rollbackFor = {RuntimeException.class, SQLException.class})
public void methodWithCustomRollback() {
// 业务逻辑
}

问:Spring事务传播?⭐⭐⭐

事务传播机制决定了在一个事务方法调用另一个事务方法时,如何管理它们之间的事务关系。Spring 提供了以下几种事务传播方式:

  1. **REQUIRED**(默认):
    • 如果当前方法有事务,则加入当前事务。如果没有事务,创建一个新的事务。
    • 常用的传播行为。
  2. **REQUIRES_NEW**:
    • 总是创建一个新的事务,挂起当前事务(如果有的话),在新事务完成后再恢复当前事务。
    • 用于某些操作需要独立的事务,即使外层事务回滚,内层事务也要提交。
  3. **SUPPORTS**:
    • 如果当前有事务,则加入当前事务。如果没有事务,方法将以非事务的方式执行。
    • 适用于某些方法希望在有事务的环境下执行,而在没有事务时执行不受事务管理的操作。
  4. **NOT_SUPPORTED**:
    • 如果当前有事务,则挂起当前事务,以非事务的方式执行当前方法。
    • 适用于方法不需要事务的情况,例如进行一些与数据库无关的操作。
  5. **MANDATORY**:
    • 如果当前没有事务,则抛出异常。
    • 适用于必须在事务上下文中执行的方法。
  6. **NEVER**:
    • 如果当前有事务,则抛出异常。方法必须在没有事务的情况下执行。
  7. **NESTED**:
    • 如果当前有事务,则在当前事务中创建一个嵌套事务(savepoint),允许提交或回滚。如果没有事务,表现和 REQUIRED 相同。
    • 适用于需要嵌套事务的场景。

事务传播示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 业务逻辑,当前事务存在
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 业务逻辑,新的事务会被创建
}

@Transactional(propagation = Propagation.SUPPORTS)
public void methodC() {
// 业务逻辑,支持事务环境下运行
}

在不同的方法嵌套调用过程中,事务要如何处理,是否用同一个事务?出现异常是否回滚或提交?

1.7 其他

问:讲一下spring常用的注解有哪些?都有什么作用?@Autowired和@Resource的区别?

  1. @ComponentScan: 用于指定要扫描的包,并自动注册标记为 @Component@Service@Repository@Controller 等的类到Spring容器中。
  2. @Component: 用于将类标记为一个Spring组件,交给Spring容器管理。
  3. @Service: 用于标记业务层的组件,通常用在服务类上。
  4. @Repository: 用于标记数据访问层的组件,通常用在DAO类上。
  5. @Controller: 用于标记控制器层的组件,通常用在Spring MVC的Controller类上。
  6. @Autowired: 用于自动装配(依赖注入)Spring容器中的Bean,可以用在构造方法、属性、方法上。
  7. @Qualifier:@Autowired 配合使用,指定具体要注入的Bean的名称。
  8. @Value: 用于注入简单类型的值或表达式到Bean的属性。
  9. @Configuration: 用于标记配置类,相当于传统的XML配置文件。
  10. @Bean: 用于在 @Configuration 类中声明Bean,Spring会自动托管这些Bean。
  11. @Scope: 用于指定Bean的作用域,如单例 (Singleton)、原型 (Prototype) 等。
  12. @Primary: 用于指定优先注入的Bean,当有多个相同类型的Bean时,被标记为 @Primary 的Bean会被优先选择。
  13. @Lazy: 用于延迟初始化Bean,即在第一次被请求时才进行实例化。
  14. @Transactional: 用于声明事务管理,可用在类或方法上,实现声明式事务。
  15. @RequestMapping: 用于映射处理请求的方法,常用于Spring MVC的Controller类中。
  16. @PathVariable: 用于提取请求URL中的占位符参数,通常用于RESTful风格的请求。
  17. @RequestParam: 用于获取请求参数的值,可用于方法的参数上。
  18. @ResponseBody: 用于指定方法返回的结果直接作为响应体,而不是视图名称。
  19. @ResponseStatus: 用于指定方法抛出特定异常时的HTTP响应状态。
  20. @ExceptionHandler: 用于处理控制器中抛出的异常,可以指定处理的异常类型。

@Autowired、@Resource和@Inject的区别?

相同点:三个注解都用于进行依赖注入,将一个 Bean 注入到另一个 Bean 中,简化了在 Spring 容器中注入依赖对象的过程。

不同点:

  1. 来源和适用性:

    -

  2. 支持类型:

    • @Autowired 支持按类型注入,可以与 @Qualifier 注解一起使用进行更精确的匹配。
    • @Resource 可以按照名称注入,也可以按照类型注入,但是按照类型时需要结合 @Named 使用。
    • @Inject 类似于 @Autowired,也是按类型注入,可以与 @Qualifier 注解一起使用。
  3. 可选性:

    • @Inject 默认为 required = true,表示注入的对象必须存在,如果找不到匹配的 bean,会抛出异常。可以通过设置 required = false 来使注入变为可选。
  4. 支持的容器:

    • @Autowired 主要用于 Spring 容器,是 Spring 框架的一部分。
    • @Resource 是 Java EE 的一部分,可以在支持 Java EE 规范的容器中使用,同时也可以在 Spring 中使用。
    • @Inject 是 JSR-330 的一部分,可以在支持 JSR-330 的容器中使用,Spring 也支持 JSR-330。

总体而言,这些注解在实际使用中可以根据具体的场景和需求进行选择,它们都提供了便捷的依赖注入方式,但在不同的容器和框架中可能有一些差异。

  • @Autowired 是 Spring 提供的注解,可以用于构造函数、属性、方法以及字段上。
  • @Resource 是由 Java EE 提供的注解,可以用于字段、setter 方法和构造函数(需要和 @Named 注解一起使用)。

不同点:

  1. 来源:
    • @Autowired 是Spring提供的注解,它是按照类型(byType)进行自动装配的。
    • @Resource 是JavaEE提供的注解,它是按照名称(byName)进行自动装配的。
    • @Inject 是 JSR-330 提供的标准注解。
  2. 注入类型:
    • @Autowired 可以用在字段、方法、构造方法、setter方法等地方,支持类型匹配的自动注入。
    • @Resource 主要用在字段上,也可以用在setter方法上,不支持构造方法注入。
    • @Inject 可以用在字段、方法、构造方法上
  3. 注解中的属性
    • @Autowired 只有一个 required 属性默认为 true,表示注入的对象是否可以为null,如果找不到匹配的 bean,会抛出异常。可以通过设置 required = false 来使注入变为可选。
    • @Resource 包含7个属性,最重要的两个属性为name和type,分别用于指定依赖注入的bean的名称和类型。
    • @Inject 没有属性。
  4. 注入方式:
    • @Autowired 默认按照类型(byType)进行注入,如果存在多个匹配的Bean,可以结合 @Qualifier 使用指定Bean的名称。
    • @Resource 默认按照名称(byName)进行注入,通过 name 属性指定具体的Bean名称。@但Resource还有属性type,解析为bean的类型。如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。默认使用byName自动注入策略。
    • @Inject 默认按照类型(byType)进行注入,若想要按名称(byName)进行注入,需要搭配@Named注解使用
  5. 可选性:
    • @Autowired 是默认必须找到匹配的Bean,如果找不到则抛出异常。可以通过 required 属性设置为 false,使其变为非必须的。
    • @Resource 如果找不到匹配的Bean,则会尝试按照类型进行匹配,如果还找不到才会抛出异常。
  6. 适用范围:
    • @Autowired 是Spring提供的注解,更通用,可以用于任何Bean的自动装配。
    • @Resource 是JavaEE提供的注解,相对于 @Autowired 来说功能较少,主要用于注入其他JavaEE组件,如EJB等。

问:有用过Spring提供的扩展组件吗?比如拦截器?请说下这个拦截器的api?还有没用过其它的组件?

Spring框架提供了许多扩展组件,其中拦截器(Interceptor)是常用的之一,主要用于在处理器执行过程中进行预处理、后处理,实现对请求的拦截和处理

Spring的拦截器主要涉及以下接口和类:

  1. HandlerInterceptor 接口:

    • preHandle: 在处理器执行之前被调用,用于进行预处理,可以决定是否继续执行处理器。
    • postHandle: 在处理器执行之后、视图渲染之前被调用,用于进行后处理。
    • afterCompletion: 在整个请求完成后、视图渲染结束后被调用,用于进行资源清理等工作。
  2. HandlerInterceptorAdapter 类:

    • HandlerInterceptor 接口的适配器类,可以继承这个类来简化拦截器的实现。
  3. InterceptorRegistry 类:

    • 用于配置拦截器,包括添加、顺序等。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在处理器执行之前进行预处理
return true; // 返回 true 表示继续执行后续操作,返回 false 将中断执行链。
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在处理器执行之后、视图渲染之前进行后处理
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求完成后、视图渲染结束后进行清理工作
}
}

配置拦截器:

1
2
3
4
5
6
7
8
@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/secure/**");
}
}

除了拦截器,Spring还提供了许多其他的扩展组件,比如:

  • BeanPostProcessor 接口: 允许在Bean初始化前后进行一些处理。
  • ApplicationListener 接口: 用于监听应用事件。
  • HandlerMethodArgumentResolver 接口: 用于自定义方法参数解析器。
  • HandlerMethodReturnValueHandler 接口: 用于自定义方法返回值处理器。

问:登录态怎么去处理的?

  • 什么是登录态?

    • 登录态是用于标识用户身份和维护用户登录状态的一种机制。区分无状态下HTTP协议的浏览器中用户的身份和状态。
  • 有哪些实现方案?

    1. HTTP:HTTP本身是无状态的协议,每个请求都独立,不借助其它手段,难以实现既安全又可靠的登录态。

    2. Cookie、Session

      • Cookie:是客户端请求服务端时,由服务端创建并由客户端存储和管理的小文本文件。
        • 工作原理: 服务器在响应中通过Set-Cookie头将Cookie信息发送给客户端,浏览器在后续请求中通过Cookie头将存储在客户端的信息发送给服务器。
        • 特点:
          • 存储在客户端,对于敏感信息应该进行加密。
          • 有大小限制,一般不超过4KB。
          • 可以设置过期时间,可以是会话级别的(浏览器关闭后失效)或持久的。
      • Session:是服务器端存储的一种会话机制,用于跟踪用户的状态。客户端请求服务端时服务端会为这次请求创建一个数据结构,这个结构可以通过内存、文件、数据库等方式保存。
        • 工作原理: 当用户首次访问服务器时,服务器为其创建一个唯一的Session ID,并将该ID存储在Cookie中或作为URL的一部分发送给客户端。客户端在后续请求中携带Session ID,服务器通过Session ID来识别用户,并在服务器端保存用户的状态信息。
        • 特点:
          • 存储在服务器端,相对于Cookie更安全。
          • 无大小限制,但服务器需要维护Session存储。
          • 可以设置过期时间,一般在一段时间内没有访问会话将被销毁。
      • 优点:
        • Cookie由客户端管理,支持设定有效期、安全加密、防篡改、请求路径等属性。
        • Session由服务端管理,支持有效期,可以存储各类数据。
      • 缺点:
        • Cookie只能存储字符串,有大小和数量限制,对移动APP端支持不好,同时有跨域限制(主域不同)。
        • Session存储在服务端,对服务端有性能开销,客户端量太大会影响性能。如果集中存储(如存储在Redis),会带来额外的部署维护成本。
    3. Token:是一种在客户端和服务器之间进行身份验证的令牌。在身份验证成功后,服务器生成一个Token并返回给客户端,客户端在后续请求中通过携带Token来进行身份验证

      • 工作原理: 用户在登录成功后,服务器生成一个Token,并将其返回给客户端。客户端将Token存储起来,通常通过Cookie或其他手段。在后续请求中,客户端将Token发送给服务器,服务器通过验证Token的有效性来确认用户身份。

      • 一般由以下数据组成:

        1
        2
        3
        uid(用户唯一的身份标识)
        time(当前时间的时间戳)
        sign(签名,由token的前几位+盐用哈希算法压缩成一定长的十六进制字符串)
      • 特点:

        • 存储在客户端,可通过多种方式传递,如HTTP头、URL参数等。
        • 无大小限制,但需要注意安全性,可以进行签名或加密。
        • 通常具有一定的时效性,避免长时间内使用相同的Token。
      • 优点:

        • 客户端可以用Cookie、LocalStorage等存储,服务端不需要存储。
        • 安全性高(有签名校验)。
        • 支持移动APP端。
        • 支持跨域。
      • 缺点:

        • 占用额外传输宽带,因为Token比较大,可能会消耗一定的流量。
        • 每次签名校验会消耗服务端性能。
        • 有效期短(避免被盗用)。但是有效期太短会造成客户端不断重新登录,体验太差。解决: Refresh Token 就是再来一个Token,一个专门生成Token的Token。
    4. JWT:服务器端若频繁校验token是否有效,可能会因为查库而影响性能。可以通过JWT(JSON Web Token)来优化。

      • JWTAuth0 提出的通过 对JSON进行加密签名 来实现授权验证的方案。

      • JWT也是一种Token,由三部分组成: Header头部Payload负载Signature签名。它是一个很长的字符串,中间用点( . )分隔成三个部分,如:

        1
        eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
      • JWT的流程和Token的基本一样,因为已经携带了客户端信息(如用户ID等),所以服务端校验Token时不需要查询数据库了。

    5. 单点登陆:上面都是同一域名下的解决方案,当网站有诸多域名、要求一次登陆可以访问所有域名/系统时,即单点登陆问题(Single Sign On SSO)。

      • 常见的实现方式包括CAS(Central Authentication Service)等。
      • 认证中心(CAS Server): CAS服务器是单点登录的核心,负责用户的认证和票据的颁发。它通常维护一个用户账户数据库,处理用户的登录请求,并生成用于标识用户身份的票据(Ticket)。
      • 票据(Ticket): 用户在CAS服务器上成功登录后,CAS服务器会颁发一个票据
    6. OAuth(开放授权): OAuth是一种开放标准,允许用户授权第三方应用访问其在服务提供商上存储的资源,而无需将用户名和密码提供给第三方应用。OAuth在授权后会返回一个Token,该Token可以用于访问用户的资源。

    7. OpenID Connect: OpenID Connect是在OAuth 2.0基础上构建的身份认证协议,它允许客户端应用获取关于终端用户的信息,以及验证终端用户的身份。通过OpenID Connect,可以实现身份认证和用户信息的获取。

一般来说,登录态的处理包括以下几个方面:

  1. 用户认证: 在用户登录时,需要对用户进行认证,通常使用用户名和密码验证用户身份。认证成功后,生成并返回一个唯一的标识符(如Token)。

  2. Token管理: 将生成的Token保存在客户端,通常存储在Cookie或者LocalStorage/SessionStorage中。Token中包含用户的身份信息以及一些其他信息,用于后续的认证。

  3. 请求认证: 客户端在每次请求时,需要将Token携带到服务端,通常通过请求头或者参数传递。服务端通过验证Token的有效性来判断用户的登录状态。

  4. Token刷新: 为了增加安全性,Token通常有一个过期时间。当Token即将过期时,可以通过刷新操作来延长Token的有效期,而不需要用户重新登录。

  5. 安全性保护: 在登录态处理中需要注意保护用户的敏感信息,使用HTTPS来加密通信,避免使用明文传输密码等敏感信息。

下面是一个简单的基于Token的登录态处理的示例(使用JWT作为Token):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 用户登录时生成Token
String token = Jwts.builder()
.setSubject(userId)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();

// 2. 将Token返回给客户端,保存在Cookie中或者LocalStorage/SessionStorage中

// 3. 客户端每次请求时携带Token
// 请求头中添加 Authorization: Bearer <Token>

// 4. 服务端验证Token
try {
Claims claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
// 验证成功,从claims中获取用户信息
String userId = claims.getSubject();
// 进行其他业务逻辑处理
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
// Token验证失败,处理异常
}

在实际应用中,可以根据具体的需求选择不同的实现方式,比如使用第三方认证服务(OAuth、OpenID Connect)、Spring Security等。

问:Spring 使用了哪些设计模式?⭐⭐⭐

  • 工厂模式:通常以*Factory形式呈现。proxyFactory

    • 简单工厂模式:Spring中的BeanFactory就是简单工厂模式的体现,根据传入唯一的标识beanName来获得bean对象
    • 工厂方法模式:FactoryBean接口则体现了工厂方法,通过实现此接口的getObject()方法来自定义Bean对象。
    • 抽象工厂模式:
  • 单例模式:Spring依赖注入Bean实例默认是单例的。Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。

    • 单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

      spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。

  • 代理模式: Spring AOP(面向切面编程)和事务管理使用了代理模式。动态代理cglib或JDK。

  • 适配器模式:可以作为两个不兼容的接口之间的桥梁,使不兼容的类可以一起工作,通常以*Adapter形式呈现。

    • Spring 的 AOP 中,通过适配器模式实现不同通知类型的统一调用。AdvisorAdapter类通知。
    • SpringMVC中的适配器HandlerAdatper根据Handler规则执行不同的Handler。
  • 责任链模式:为请求创建了一个接收者的链,每个接收者都有对另外一个接收者的引用。当一个接收者无法处理请求时就会传给下一个。通常以*Chain形式呈现。

    • 如Spring MVC的HandlerExecutionChain。
    • 如使用AOP那些通知调用时先生成一个拦截器链。
  • 装饰器模式: 允许我们向一个现有的对象添加新的功能,同时不改变其现有结构。Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。

    • Spring 的 BeanWrapper 使用了装饰者模式,对 Bean 进行装饰和增强。依赖注入就需要使用BeanWrapper。
  • 观察者模式:定义了一种一对多的依赖关系,当一个对象状态发生变化时,所有依赖者都会收到通知。listener,event,multicast监听器,监听事件,广播器(多播器)

    • spring 的事件监听机制基于观察者模式,通过 ApplicationEvent 和 ApplicationListener 实现。
    • 使用 ApplicationEventPublisher 接口 publishEvent() 方法来发布事件。通常,这个接口由 ApplicationContext 实现。
    • 事件类通常是继承自 ApplicationEvent
    • 事件监听器类需要实现 ApplicationListener 接口。ApplicationListener 是一个函数式接口,只有一个 onApplicationEvent() 方法。
    • 事件广播(Multicast)将事件传递给多个监听器。在一个应用中可能有多个监听器监听同一个事件,这时就需要使用 多播器(Multicaster)。Spring 提供的 SimpleApplicationEventMulticaster 是事件广播的实现类。它会遍历所有的 ApplicationListener(事件监听器),并调用其 onApplicationEvent() 方法来处理事件。当 Spring 容器启动时,ApplicationContext 会扫描并注册所有的监听器(实现了 ApplicationListener 接口的类或使用 @EventListener 注解的方法)。
  • 策略模式: 定义了一系列算法或策略,将每个算法封装在独立的类中,可以互相替换,在运行时根据需要选择不同的算法。通常以*Strategy形式呈现。ClassPathXmlApplicationContext,FileSystemApplicationContext,XmlBeanDefinitionReader,PropertiesBeanDefinitionReader实例化策略(simple,cglib)

    • Spring中的InstantiationStrategy接口,根据创建对象情况的不同,提供了Bean实例化的三种策略:默认构造方法、指定构造方法、工厂方法。
    • Spring 的事务管理机制中,通过不同的事务管理策略实现。
  • 模板方法模式:抽象父类提供一套定义好的方法供子类使用,某些子类会根据自己情况进行定制。通常以*Template形式呈现。postProcessBeanFactory,onFresh,initPropertyValue

    • 在 Spring 的 JdbcTemplate 和 HibernateTemplate 中使用了模板方法模式。Spring 是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式。Spring几乎所有的外接扩展都采用这种模式。引入回调原因:JdbcTemplate是抽象类,不能够独立使用,我们每次进行数据访问的时候都要给出一个相应的子类实现,这样肯定不方便,所以就引入了回调。
    • Spring事务管理的TransactionTemplate。
  • 委托者模式:delegate,如BeanDefinitionParserDelegate

  • 访问者模式:BeanDefinitionVisitor

  • 构造者模式:BeanDefinitionBuilder

  • ……

二.Spring MVC

问:讲一下SpringMVC的全流程,可以画一下UML时序图吗?

参考:SpringMVC的工作流程及原理详解

img

  1. 客户端发送请求:客户端向服务器发送HTTP请求,请求到达SpringMVC前端控制器(DispatcherServlet)。
  2. DispatcherServlet接收请求: DispatcherServlet是SpringMVC的前端控制器,它接收所有的请求,并负责将请求分发给合适的处理器(Controller)。DispatcherServlet根据请求URI进行解析,找到相应的HandlerMapping(处理器映射器)。
  3. HandlerMapping定位处理器: DispatcherServlet通过HandlerMapping查找合适的处理器,HandlerMapping根据请求的URL映射到相应的Controller。
  4. HandlerAdapter(处理器适配器):DispatcherServlet通过HandlerMapping获取到相应的HandlerExecutionChain(一个执行链对象),根据HandlerExecutionChain内的Handler对象来匹配相应的HandlerAdapter。HandlerAdapter请求映射,对处理器进行适配,以使其能正确处理请求。
  5. Controller控制器:HandlerAdapter执行handle方法将HandlerExecutionChain内的Handler运行(即我们写的Controller),并且返回一个ModelAndView模型视图对象。
  6. HandlerIntercepter拦截器:中央控制器将接收到的ModelAndView对象进行检查,实际调用的是HandlerInterceptor的postHandle方法来检查视图是否存有异常。
  7. ViewResolver视图解析器:DispatcherServlet将ModelAndView传给ViewResolver解析生成View。
  8. View视图渲染:DispatcherServlet根据View进行渲染视图,即将模型数据填充至视图中,将结果返回给用户。

HttpRequest->DispatcherServlet->HandlerMapping->Handler ->DispatcherServlet->HandlerAdapter处理handler->ModelAndView ->DispatcherServlet->ModelAndView->ViewReslover->View ->DispatcherServlet->Response

问:讲一下SpringMVC不同用户登录的信息怎么保证线程安全的?

  • Controller默认是单例的,无需为每个请求创建新的Controller,所以Controller非线程安全。
  • 措施:
    1. 不要在Controller中定义成员变量,若有必要定义非静态变量,通过@Scope("prototype") ,将Controller设置为多例模式;或者@Scope("request") 在每个HTTP请求中创建一个新的实例。Controller也是一个Bean,默认的 Scope 属性为Singleton
    2. 在Controller 中使用 ThreadLocal 变量,每一个线程都有一个变量的副本。

三.Spring Boot

问:SpringBoot和Spring的区别?⭐⭐⭐

  • Spring提供了很多好用的功能:SpringJDBC、SpringMVC、SpringSecurity、SpringAOP、SpringORM、SpringTest等。
  • SpringBoot可以看作是Spring的扩展和进化。
  • 简化配置: 首先设计理念约定大于配置,使用大量默认配置,可以快速搭建项目。支持JavaConfig,减少了大量的XML配置,使得应用配置更加简洁。
  • 内嵌Web服务器: 嵌入应用服务器如Tomcat、 Jetty、 Undertow容器等,不需要再单独配置Web服务器,使得应用可以通过单个可执行JAR文件来运行。
  • 自动化配置: Spring Boot提供了大量的自动化配置,通过分析类路径下的各种库和框架,尽可能地为应用提供默认配置。
  • 微服务支持: Spring Boot天生支持微服务的开发,通过Spring Cloud等项目,可以更方便地构建和管理分布式系统。
  • 监控和管理: Spring Boot提供了一套丰富的监控和管理功能,包括应用信息端点、健康检查、性能指标等。
特性 Spring Spring Boot
配置方式 需要手动配置大量的 XML 或注解 提供自动配置,减少手动配置
启动方式 需要部署到外部 Web 容器 可直接运行为独立的 JAR 文件
项目结构 项目结构复杂,手动配置 Bean 和依赖 项目结构简化,通过 @SpringBootApplication 启动
自动配置 无自动配置,需要手动设置配置 提供自动配置,自动推测依赖并配置应用
依赖管理 手动管理依赖,可能出现版本冲突 使用 Spring Boot Starters 自动管理依赖
事件驱动 支持应用事件机制 更加简化的事件驱动支持
异步执行 支持异步执行,但需要额外配置 提供内建的异步支持

问:SpringBoot启动流程?⭐⭐⭐

SpringBoot在main()方法中调用SpringApplication.run()方法来启动项目。其中内置了常见的Web服务器,所以无需再依赖外部服务器(会在refreshContext()进行容器的启动,默认完成Tomcat容器的创建)。

总结:

  1. 创建SpringApplication对象,初始化过程主要包括:
    • 推断并设置当前的web应用类型;
    • 配置基本的环境变量、资源、构造器、监听器
    • 根据当前线程的调用栈,推断主应用程序类。
  2. 调用run()方法启动Spring Boot应用。
  3. 首先创建一个计时器。
  4. 设置headless 系统属性。
  5. 获取一个运行监听器。
  6. 创建应用参数对象。
  7. 加载环境变量。
  8. 打印启动界面Banner。
  9. 创建Spring容器。
  10. 容器启动前的处理。
  11. 启动Spring容器。
  12. 容器启动后的处理。
  13. 计算耗时时间。
  14. 发布容器已启动事件。
  15. 调用所有 ApplicationRunner 和 CommandLineRunner 接口的实现类的run()。
  16. 若发生异常则发布应用启动失败事件。
  17. 发布应用就绪事件。

启动流程中的各个事件发布:

  1. 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象。
  2. 然后由 SpringApplicationRunListener 来发出 starting 消息
  3. 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment
  4. 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息
  5. 创建 ApplicationContext
  6. 初始化 ApplicationContext,并设置 Environment,加载相关配置等
  7. 由 SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK
  8. 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK
  9. refresh ApplicationContext,完成IoC容器可用的最后一步
  10. 由 SpringApplicationRunListener 来发出 started 消息
  11. 完成最终的程序的启动
  12. 由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了。

根据代码可以分为3个阶段:

  1. 程序主入口main方法调用 SpringApplication.run 方法,该方法会创建一个SpringApplication对象,用来管理应用程序上下文和启动应用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootApplication
    public class XxxApplication {

    public static void main(String[] args) {
    SpringApplication.run(XxxApplication.class, args);
    }
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
    }
  2. 初始化SpringApplication:推断并设置当前的web应用类型;配置基本的环境变量、资源、构造器、监听器;根据当前线程的调用栈,推断主应用程序类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
       
    public SpringApplication(Class<?>... primarySources) {
    this((ResourceLoader)null, primarySources);
    }

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    // 资源加载器 NULL
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 将传入的配置类赋值给primarySources
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 推测web应用类型(NONE、REACTIVE、SERVLET)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 从spring.factories中获取初始化器、监听器等集合
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 推断主应用程序类,根据当前线程的调栈判断main()方法在哪个类
    this.mainApplicationClass = this.deduceMainApplicationClass();
    }

    // 通过工厂模式获取一组实例集合
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return this.getSpringFactoriesInstances(type, new Class[0]);
    }
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = this.getClassLoader();
    // 通过 SpringFactoriesLoader 从类路径中加载指定类型的工厂名字。SpringFactoriesLoader 是 Spring 框架提供的工具类,用于加载 META-INF/spring.factories 文件中的配置信息。
    Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 根据工厂名字和其他参数创建实例。
    List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 对实例集合进行排序.排序是根据 @Order 注解的值来进行的。这确保了实例按照指定的顺序执行。
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
    }
    // 通过配置 SpringApplication 的 initializers 属性,你可以将自定义的初始化器添加到应用中。这是 Spring Boot 提供的一种扩展机制,让开发者可以在 Spring Boot 启动过程中插入自己的逻辑。
    // 这些初始化器在 SpringApplication 的 prepareContext 方法中被调用,用于在 ApplicationContext 创建之前对其进行定制。
    // 一些典型的用例包括:加载额外的配置。注册额外的 bean 定义。修改 Environment 属性。
    private List<ApplicationContextInitializer<?>> initializers;
    public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList(initializers);
    }

    private List<ApplicationListener<?>> listeners;
  3. 运行SpringApplication:过程中分别发送starting、environmentPrepared、contextPrepared和contextLoaded、started、running等事件。

    • SpringApplicationRunListeners 引用启动监控模块
    • ConfigrableEnvironment配置环境模块和监听:包括创建配置环境、加载属性配置文件和配置监听
    • ConfigrableApplicationContext配置应用上下文:包括配置应用上下文对象、配置基本属性和刷新应用上下文
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    public ConfigurableApplicationContext run(String... args) {
    // 1.计时器,统计启动耗时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 2.上下文
    ConfigurableApplicationContext context = null;
    // 异常报告集合
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    // 设置 java.awt.headless 系统属性
    this.configureHeadlessProperty();
    // 3.获取 Spring Boot 运行监听器,getRunListeners通过调用getSpringFactoriesInstances私有协议从META-INF/spring.factories文件中取得SpringApplicationRunListener监听器实例
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    // 发送启动事件,通知监听器 Spring Boot 应用即将启动
    listeners.starting();
    // 当前事件监听器SpringApplicationRunListener中只有一个EventPublishingRunlistener广播事件监听器,它的starting方法会封装成SpringApplicatiionEvent事件广播出去,被SpringApplication中配置的listener监听。这一步骤执行完成后也会同时通知SpringBoot其他模块目前监听初始化已经完成,可以开始执行启动方案了。

    Collection exceptionReporters;
    try {
    // 4.创建应用参数对象
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    // 5.加载环境变量和配置信息,调用 prepareEnvironment 方法,返回配置好的 ConfigurableEnvironment 对象
    // 其中会加载环境配置文件,并发送environmentPrepared事件
    ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
    this.configureIgnoreBeanInfo(environment);
    Banner printedBanner = this.printBanner(environment);
    // 6.创建应用上下文对象,根据应用类型创建Spring容器,ConfigurableApplicationContext是Spring框架中负责Bean注入容器的主要载体,负责bean加载、配置管理、维护bean之间依赖关系及Bean生命周期管理。
    context = this.createApplicationContext();
    exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
    // 7.准备应用上下文,spring容器启动前的处理。
    // 设置环境、监听器、应用参数等信息,将listener、environment、banner、applicationArguments等重要组件与Spring容器上下文对象关联。发送contextPrepared和contextLoaded事件
    this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    // 8.刷新应用上下文,启动Spring容器,将通过工程模式产生应用上下文中所需的bean。实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载、bean的实例化、启动WebServer等核心工作。
    this.refreshContext(context);
    // 9.刷新后的回调,容器启动后的处理。执行一些刷新后的操作,默认为空
    this.afterRefresh(context, applicationArguments);
    // 10.计时结束,计算耗时
    stopWatch.stop();
    if (this.logStartupInfo) {
    (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
    }

    // 11.发送已启动事件
    listeners.started(context);
    // 12.调用所有 ApplicationRunner 和 CommandLineRunner 接口的实现类的run();
    this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
    // 13.发布应用启动失败事件
    this.handleRunFailure(context, var10, exceptionReporters, listeners);
    throw new IllegalStateException(var10);
    }

    try {
    // 14.发送运行中事件
    listeners.running(context);
    return context;
    } catch (Throwable var9) {
    this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
    throw new IllegalStateException(var9);
    }
    }

Spring Boot 的启动流程大致可以分为 6 个核心步骤,涉及 SpringApplication 的初始化、Environment 配置、ApplicationContext 初始化、Bean 的创建等。

Spring Boot 启动流程

当我们运行一个 Spring Boot 应用时(比如 SpringApplication.run(MainApplication.class, args);),Spring Boot 会经历以下核心步骤:

1. 创建 SpringApplication 对象

Spring Boot 的启动从 SpringApplication 类开始:

1
2
SpringApplication app = new SpringApplication(MainApplication.class);
app.run(args);

这一步主要做了:

  • 推断应用类型WebApplicationType):判断是 REACTIVE(响应式应用)、SERVLET(普通 Web 应用)还是 NONE(非 Web 应用)。
  • 设置 ApplicationContext 类型:如果是 Web 应用,使用 AnnotationConfigServletWebServerApplicationContext,否则使用 AnnotationConfigApplicationContext
  • **加载 SpringApplicationRunListener**:用于监听 Spring Boot 的生命周期事件。
  • 推断 main 方法的主类(通常是带 @SpringBootApplication 的类)。

2. 运行 SpringApplication#run()

SpringApplication.run(MainApplication.class, args) 方法中,Spring Boot 进行一系列启动步骤,主要包括:

2.1. 启动 SpringApplicationRunListeners

Spring Boot 通过 SpringApplicationRunListeners 监听启动过程,触发 startedrunning 等事件:

1
listeners.starting();

监听器可以通过 META-INF/spring.factories 进行扩展。

2.2. 创建并配置 Environment

Spring Boot 创建 ConfigurableEnvironment,用于管理系统属性、环境变量、application.properties 配置:

1
2
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, args);

这里会:

  • 加载系统环境变量(如 System.getenv())。
  • **解析 application.properties / application.yml**。
  • 支持 spring.profiles.active 机制。

2.3. 创建 ApplicationContext

Spring Boot 创建 Spring 容器(ApplicationContext

1
context = createApplicationContext();

不同应用类型有不同的 ApplicationContext

  • Web 应用AnnotationConfigServletWebServerApplicationContext
  • 普通应用AnnotationConfigApplicationContext
  • 响应式应用AnnotationConfigReactiveWebServerApplicationContext

2.4. ApplicationContext 准备

Spring Boot 在 prepareContext() 方法中进行 ApplicationContext 的预处理:

1
prepareContext(context, environment, listeners, applicationArguments, printedBanner);

包括:

  • 注册 EnvironmentApplicationContext
  • **应用 ApplicationContextInitializer**,用于自定义 ApplicationContext 初始化逻辑。
  • 广播 ApplicationPreparedEvent 事件,可用于监听 ApplicationContext 初始化完成但未刷新前的操作。

2.5. ApplicationContext 刷新

Spring Boot 通过 refreshContext(context) 完成 ApplicationContext 刷新:

1
refreshContext(context);
  • 解析 @Configuration@ComponentScan 扫描 Bean。
  • 调用 @Bean 方法创建 Bean。
  • 解析 @Import@ComponentScan
  • 处理 @PostConstructInitializingBean 相关生命周期回调。

2.6. 执行 ApplicationRunnerCommandLineRunner

Spring Boot 允许我们在应用启动后执行 ApplicationRunnerCommandLineRunner

1
callRunners(context, applicationArguments);
  • ApplicationRunner 适用于需要解析 ApplicationArguments 参数的任务。
  • CommandLineRunner 适用于直接处理 String[] args 参数的任务。

示例:

1
2
3
4
5
6
7
@Component
public class MyRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("应用启动完成,执行自定义逻辑...");
}
}

3. Spring Boot 生命周期

Spring Boot 主要的生命周期事件如下:

阶段 事件 触发时机
启动阶段 starting() Spring Boot 启动时
环境准备阶段 environmentPrepared() Environment 配置完成后
容器初始化阶段 contextPrepared() ApplicationContext 创建完成但未刷新
容器刷新阶段 contextLoaded() ApplicationContext 加载完成
应用准备完成 started() ApplicationContext 刷新完成
应用运行 running() 应用启动完成
应用关闭 failed() / closed() 发生异常 / 关闭应用

监听 Spring Boot 事件

我们可以实现 ApplicationListener 监听这些事件:

1
2
3
4
5
6
7
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("Spring Boot 应用启动完成!");
}
}

4. Spring Boot SpringApplicationRunListener 监听机制

SpringApplicationRunListener 用于监听 Spring Boot 的启动过程,例如:

1
2
3
4
5
6
7
8
9
10
11
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
public void starting() {
System.out.println("应用开始启动...");
}
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("环境变量准备完成...");
}
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("ApplicationContext 创建完成...");
}
}

Spring Boot 通过 META-INF/spring.factories 自动加载这些监听器。

5. Spring Boot 的 Exit 关闭流程

Spring Boot 通过 SpringApplication.exit() 触发应用关闭:

1
SpringApplication.exit(context, () -> 0);
  • 关闭 ApplicationContext,销毁所有 Bean。
  • 触发 ApplicationListener<ContextClosedEvent> 事件。
  • 执行 DisposableBean@PreDestroy 标注的方法。

示例:

1
2
3
4
@PreDestroy
public void cleanUp() {
System.out.println("应用即将关闭,执行资源释放...");
}

总结

  1. 创建 SpringApplication
  2. 配置 Environment
  3. 创建 ApplicationContext
  4. 初始化 ApplicationContext
  5. 刷新 ApplicationContext
  6. 执行 ApplicationRunner / CommandLineRunner
  7. 应用启动完成,进入 running 状态
  8. 监听应用生命周期,处理 Exit 关闭

Spring Boot 通过自动化配置和事件监听机制,极大地简化了应用的启动流程,使得开发者可以快速构建应用并进行扩展。

问:Spring Boot的核心注解?

Spring Boot 的核心注解主要包括:

  1. @SpringBootApplication: 该注解是一个复合注解,包含 @Configuration@EnableAutoConfiguration@ComponentScan。通常标注在应用的启动类上,用于启动 Spring Boot 应用。

  2. @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

  3. @Configuration: 表明该类是一个配置类,类似于 XML 配置文件。

  4. @EnableAutoConfiguration: 启用 Spring Boot 的自动配置机制,让 Spring Boot 根据项目的依赖添加对应的配置。

  5. @ComponentScan: 扫描指定包及其子包下的组件,将其注册到 Spring 容器中。通常用于扫描 @Component@Service@Repository 等注解标识的类。

  6. @RestController: 标注在类上,表示该类中的所有方法返回的都是 JSON 格式的数据,等同于 @Controller + @ResponseBody

  7. @RequestMapping: 用于映射 HTTP 请求路径,可以标注在类上,表示类中的所有方法的公共路径,也可以标注在方法上,表示单个方法的路径。

  8. @Autowired: 自动注入依赖对象,可以用于构造器、方法、字段上。Spring Boot 利用它实现了依赖注入。

  9. @Value: 用于从配置文件中读取值,例如读取 application.properties 中的配置。

  10. @SpringBootTest: 用于在测试中加载 Spring Boot 的应用上下文,可以进行集成测试。

  11. @ConfigurationProperties: 用于将配置文件中的属性绑定到 Java 对象上。

这些注解是 Spring Boot 中常用的核心注解,通过它们,可以实现自动配置、依赖注入、路径映射等功能。

问:说说Spring Boot的条件注解?

在Spring Boot中,条件注解是一种基于条件判断的注解,用于根据满足特定条件来决定是否创建或加载一个Bean或配置。@Conditional注解需要实现Condition接口以及其matches方法,它标注在类和方法上,当条件成立时,配置的内容会生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 元注解,它标注在类和方法上。主要有一个value()属性,该属性定义了条件的具体实现类,这些实现类必须继承自Condition接口。
public @interface Conditional {

/**
* All {@link Condition} classes must be checked and all must return true.
* @return the conditional classes that must all return true
*/
Class<? extends Condition>[] value() default {};

}

@FunctionalInterface
public interface Condition {

/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata for the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

Spring Boot提供了一系列条件注解,主要使用在@Conditional注解之下,以实现在不同条件下的Bean或配置的动态加载。

以下是一些常见的Spring Boot条件注解:

  1. @ConditionalOnClass:是否存在某个类
    • 该注解用于在类路径中存在指定的类时,创建或加载Bean。例如:
      1
      2
      3
      4
      5
      @Configuration
      @ConditionalOnClass(name = "com.example.SomeClass")
      public class MyConfiguration {
      // Bean definitions go here
      }
  2. @ConditionalOnMissingClass:是否缺少某个类
    • @ConditionalOnClass相反,用于在类路径中不存在指定的类时,创建或加载Bean。
  3. @ConditionalOnBean:Spring容器是否存在某个类型或名字的Bean
    • 在容器中存在指定的Bean时,创建或加载当前Bean。例如:
      1
      2
      3
      4
      5
      @Configuration
      @ConditionalOnBean(name = "myBean")
      public class MyConfiguration {
      // Bean definitions go here
      }
  4. @ConditionalOnMissingBean:Spring容器是否缺少某个类型或名字的Bean
    • @ConditionalOnBean相反,用于在容器中不存在指定的Bean时,创建或加载当前Bean。
  5. @ConditionalOnProperty:判断Environment环境变量中是否存在某个属性
    • 根据配置文件中的属性值来判断是否创建或加载Bean。例如:
      1
      2
      3
      4
      5
      @Configuration
      @ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true")
      public class MyConfiguration {
      // Bean definitions go here
      }
  6. @ConditionalOnExpression:指定的表达式返回的是true还是false
    • 基于SpEL表达式的条件判断,满足表达式时创建或加载Bean。
  7. @ConditionalOnWebApplication:判断当前应用是不是一个Web应用
    • 在Web应用程序环境中才会创建或加载Bean。可选的参数type可以指定Web应用程序的类型,如Type.SERVLETType.REACTIVE
  8. @ConditionalOnResource:
    • 当类路径中存在指定的资源文件时,创建或加载Bean。

这些条件注解允许根据应用程序的上下文、环境或配置动态地调整Bean的加载。在实际的应用中,可以根据具体的需求选择合适的条件注解进行使用。

问:SpringBoot常用starter都有哪些?如何自定义starter?⭐

  1. spring-boot-starter-web: 用于构建 Web 应用程序,包括 RESTful、Spring MVC 等。
  2. spring-boot-starter-data-jpa: 提供了 JPA 数据库支持。
  3. spring-boot-starter-data-redis: 集成了 Redis 缓存支持。
  4. spring-boot-starter-data-mongodb: 提供了 MongoDB 数据库支持。
  5. spring-boot-starter-data-rest:使用 Spring Data REST 公布简单的 REST 服务
  6. spring-boot-starter-security: 集成了 Spring Security,用于处理应用程序的安全性需求。
  7. spring-boot-starter-test: 包含了常用的测试依赖,例如 JUnit、TestNG 等。
  8. spring-boot-starter-log4j2: 集成了 Log4j2,用于日志记录。
  9. spring-boot-starter-logging: 集成了 Spring Boot 默认的日志框架。
  10. spring-boot-starter-thymeleaf: 集成了 Thymeleaf 模板引擎,用于构建视图。
  11. spring-boot-starter-cache: 集成了 Spring Cache,用于添加缓存支持。
  12. spring-boot-starter-mail: 提供了邮件发送的支持。
  13. spring-boot-starter-actuator: 集成了 Spring Boot Actuator,用于监控和管理应用程序。
  14. spring-boot-starter-tomcat: 嵌入式 Tomcat 的支持。
  15. spring-boot-starter-jdbc: 提供了 JDBC 数据库支持。

命名规范:

  • 官方:spring-boot-starter-*
  • 第三方:*-spring-boot-starter
  • 使用两个模块分别来开发:
    • starter:空项目,用来辅助依赖管理,包括自动配置类和其它类库
    • autoconfigure:用来开发自动配置类、声明配置文件消息等
  • 步骤:
    1. 创建空的Maven项目作为父项目:springboot_starter
    2. 新建模块命名为xx-spring-boot-starter,注意模块为空
    3. 新建模块命名为xx-spring-boot-starter-autoconfigurer
    4. 模块2下新建XxProperties类,使用@ConfigurationProperties声明,其作用为声明一个配置文件
    5. 模块2下新建XxService类,该类注入了XxProperties对象,并定义一个getXx()方法
    6. 模块2下新建XxAutoConfiguration自动配置类,使用@EnableAutoConfiguration将其加入到Spring容器,另外在类中声明一个XxService的Bean
    7. 添加自动配置类的全限定名到文件中,若使用的SpringBoot版本低于2.7,则要在spring.factories文件下添加配置项;若高于2.7,则在AutoConfiguration.imports文件中添加内容。
    8. 执行Maven的install命令进行两个模块的安装
    9. 在新的项目中进行测试:引入依赖、在application.properties中配置、调用接口测试。

如何自定义一个 Spring Boot Starter?

假设我们要封装 Redis 组件,创建一个 my-starter-redis

(1) 创建 my-starter-redis 模块

1
2
3
4
5
<dependency>
<groupId>com.example</groupId>
<artifactId>my-starter-redis</artifactId>
<version>1.0.0</version>
</dependency>

(2) 提供自动配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@ConditionalOnClass(RedisTemplate.class) // 只有 Redis 依赖存在时才启用
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}

(3) 配置 META-INF/spring.factories

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.mystarter.redis.RedisAutoConfiguration

这样 只需引入 my-starter-redis 依赖,就能自动配置 Redis 🎉。

问:SpringBootStarter如何理解?⭐

Spring Boot Starter 是 一组预定义的依赖集合,用于 简化项目的依赖管理自动配置
本质上,它是一个 Maven 或 Gradle 依赖,封装了 一组常用的 Spring 组件及其依赖

如**spring-boot-starter-web** 内部封装了 Spring MVC、Tomcat、Jackson 等依赖,开发者只需要引入这个 Starter,即可使用 Web 相关功能,而不需要手动管理多个依赖。

作用 描述
封装常用依赖 通过 Starter 一次性引入多个相关依赖,避免版本冲突。
提供默认配置 结合 Spring Boot 自动配置(@EnableAutoConfiguration),让开发者无需手动配置。
提升开发效率 开发者只需引入 Starter,即可使用相关功能,降低学习成本。
统一维护版本 Starter 通过 spring-boot-dependencies 统一管理版本,防止 JAR 版本不兼容问题。
增强可扩展性 可以自定义 Starter,封装企业级组件,提高代码复用率。

问:Spring Boot常用配置方式?配置优先级?

优先级由高到低:

  1. 命令行参数(Command Line Arguments):
    • 通过命令行传递的参数具有最高的优先级。可以通过--前缀和=符号来设置属性,例如:--server.port=8080
  2. Java系统属性(Java System Properties)/JVM环境变量:
    • 通过在启动应用程序时使用-D参数设置的Java系统属性。例如:-Dserver.port=8080
  3. 操作系统环境变量(OS Environment Variables):
    • 可以通过操作系统的环境变量来设置配置属性。环境变量的命名规则通常是全大写,使用下划线作为分隔符,例如:SERVER_PORT=8080
  4. 配置文件(Application Properties或YAML Files):
    • application.propertiesapplication.yml 文件中的配置。这些文件可以位于类路径下的/config目录中,或者在项目根目录中。同时,可以使用spring.config.namespring.config.location属性指定自定义的配置文件名和位置。
  5. 默认值(Default Values):
    • Spring Boot提供了一组默认的配置属性,可以在没有显式设置的情况下作为默认值使用。这些默认值通常在Spring Boot的各个模块中定义。

问:SpringBoot自动配置的原理?⭐⭐⭐

什么是自动配置?

  • 在项目启动时,根据项目的依赖和配置,Spring Boot会自动地对项目进行一系列的配置,以简化开发者的工作。
  • 使Spring Boot 应用可以 零配置 地启动所需组件。

如何实现的自动配置?

  • 基于条件注解 + SpringFactoriesLoader 机制实现的,spring-boot-autoconfigure包提供了大量的自动配置类。SPI机制。
    • SPI(Service Provider Interface)是Java提供的一种服务发现机制。允许 在运行时动态加载实现类,而不需要硬编码。采用 SpringFactoriesLoader 作为 SPI 实现,读取 spring.factories 来加载自动配置类。
  • Spring Boot 启动时,@SpringBootApplication 这个注解包含了 @EnableAutoConfiguration,它的作用是 开启自动配置,会触发 AutoConfigurationImportSelector,找到所有的自动配置类。
  • Spring Boot 通过 SpringFactoriesLoader 机制,从 spring-boot-autoconfigure.jar 包中的 META-INF/spring.factories 读取自动配置类。
  • 自动配置并不是全部启用,而是通过 条件注解 控制:@ConditionalOnXXX 条件注解。
    • @ConditionalOnClass:类路径下是否存在某个类(如 DataSource.class)。
    • @ConditionalOnMissingBean:容器中是否已经存在某个 Bean。
    • @ConditionalOnProperty:是否配置了某个 properties 选项。
  • 如果符合条件,则自动注册 @Bean,如 DataSourceAutoConfiguration 注册数据库连接池。

详细概述:

  1. SpringBoot的main方法通过调用SpringApplication.run(Application.class, args)来启动应用,第一个参数需要是一个使用@SpringBootApplication注解声明的类。@SpringBootApplication是一个复合注解,包含 @Configuration@EnableAutoConfiguration@ComponentScan。其中**@EnableAutoConfiguration实现了自动配置**。导入到AutoConfigurationImportSelector类。

  2. @EnableAutoConfiguration 注解是核心,它可以根据项目的依赖和配置自动配置 Spring 应用程序。

  3. @EnableAutoConfiguration找到META-INF/spring.factories(需要创建的bean在里面)配置文件,读取每个starter中的spring.factories文件。

  4. AutoConfigurationImportSelector实现了DeferredImportSelector接口的getImportGroup()方法,所以其不会直接调用selectImports(),而是依次调用其静态内部类的AutoConfigurationGroup.process()以及AutoConfigurationGroup.selectImports()。

  5. 获取候选的自动配置类:AutoConfigurationImportSelector.getAutoConfigurationEntry(),早期版本通过SpringFactoriesLoader.loadFactoryNames()方法来读取 spring.factories 文件并匹配key为org.springframework.boot.qutoconfigure.EnableAutoConfiguration的值;2.7以后会直接一行行的读取classpath:/META-INF/spring/org.springframework.boot.qutoconfigure.AutoConfiguration.imports文件,其保存的就是所有的自动配置类。

  6. 筛选项目需要的自动配置类:将获取到的自动配置类进行去重、排除等操作,进行关键的过滤操作。过滤的核心思想是通过@ConditionalOnClass、@ConditionalOnProperty等注解完成,判断项目中是否存在自动配置类指定的某些关键的类或特定的属性,若不存在就会被过滤掉。

  7. 导入筛选到的自动配置类到Spring容器:自动配置类中的Bean能否被注册还要看Bean的条件注解是否满足。

  8. Spring Boot 的自动配置是通过条件化的注解条件匹配器实现的。

自动配置的原理主要包括:

  1. SpringFactoriesLoader: Spring Boot 使用 spring.factories 文件定义了各种自动配置的类。这个文件通常位于项目的 META-INF 目录下。在该文件中,使用全限定名定义了一系列自动配置类。

  2. EnableAutoConfiguration注解: 在主配置类上使用了 @EnableAutoConfiguration 注解,它会从 spring.factories 文件中加载一系列配置类,这些配置类将被注册到 Spring 容器中。

  3. 条件化注解: 自动配置类上使用了多个条件化注解,例如 @ConditionalOnClass@ConditionalOnMissingBean 等。这些注解用于根据条件判断是否应该应用该自动配置。条件判断由条件匹配器(ConditionEvaluator)完成。

  4. 条件匹配器: 条件匹配器会检查项目的 classpath、配置、系统属性等,判断是否满足条件。如果满足条件,则相应的自动配置类将被注册到 Spring 容器中。

  5. 自动配置的顺序:spring.factories 文件中,每个自动配置类的条目都有一个优先级。Spring Boot 根据这个优先级来确定自动配置的顺序。较低优先级的配置类将在较高优先级的配置类之后被注册。

问:SpringBoot的自动配置流程?

Spring Boot 的自动配置流程主要包括以下几个步骤:

  1. SpringFactoriesLoader 加载配置类: Spring Boot 使用 SpringFactoriesLoader 机制加载 spring.factories 文件,该文件中定义了各种自动配置类的全限定名。这个文件通常位于项目的 META-INF 目录下。

  2. EnableAutoConfiguration 注解: 在主配置类上使用了 @EnableAutoConfiguration 注解,该注解会触发 Spring Boot 的自动配置机制。它将加载 spring.factories 中配置的自动配置类。

  3. 条件化注解判断是否生效: 自动配置类上通常使用了条件化注解,例如 @ConditionalOnClass@ConditionalOnMissingBean 等。这些注解根据条件判断是否应该应用该自动配置。条件判断由条件匹配器(ConditionEvaluator)完成。

  4. 条件匹配器判断是否满足条件: 条件匹配器会检查项目的 classpath、配置、系统属性等,判断是否满足条件。如果满足条件,则相应的自动配置类将被注册到 Spring 容器中。

  5. 自动配置的执行: 自动配置类中通常包含了一系列的 Bean 定义和配置。这些配置会在 Spring 容器初始化的过程中生效。

  6. 自动配置的顺序:spring.factories 文件中,每个自动配置类的条目都有一个优先级。Spring Boot 根据这个优先级来确定自动配置的顺序。较低优先级的配置类将在较高优先级的配置类之后被注册。

问:SpringBoot把对象注入容器的几种方法?

  • 通过注解注入:**@Configuration** + @Bean + new Object()
  • 通过构造方法注入:**@Component** + @Autowired + 构造器
    • @Controller / @Service / @Repository / @ComponentScan 都是细化的 @Component
  • 通过set方法注入:**@Component** + @Autowired + set()
  • 通过属性注入:**@Autowired** + private Object obj

在 Spring Boot 中,将对象注入容器的主要方式是使用 @Component 及其衍生注解,以及使用 @Bean 注解。

以下是几种常见的注入方式:

  1. 使用 @Component 注解: @Component 注解用于标识一个类为 Spring Bean,并由 Spring 容器进行管理。其他衍生注解如 @Service@Repository@Controller 都是基于 @Component 的特化,用于表示不同层次的组件。

    1
    2
    3
    4
    @Component
    public class MyComponent {
    // Class implementation
    }
  2. 使用 @Bean 注解: @Bean 注解用于在配置类中声明一个 Bean。该注解通常与 @Configuration 注解一起使用。

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class AppConfig {
    @Bean
    public MyComponent myComponent() {
    return new MyComponent();
    }
    }
  3. 使用 @Autowired 注解: @Autowired 用于在需要注入的地方标注,Spring 容器会自动将符合类型的 Bean 注入进来。

    1
    2
    3
    4
    5
    @Service
    public class MyService {
    @Autowired
    private MyComponent myComponent;
    }
  4. 使用构造器注入: 在类的构造器上使用 @Autowired 注解,Spring 容器会尝试将匹配类型的 Bean 注入到构造器中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Service
    public class MyService {
    private final MyComponent myComponent;

    @Autowired
    public MyService(MyComponent myComponent) {
    this.myComponent = myComponent;
    }
    }

这些方式可以根据具体情况和项目需求选择使用,它们都能够实现对象的注入和管理。

问:假如现在在一个类了要引用到几十个Bean,难道要在类里声明这些bean然后加注解吗,有没有更好的做法?

  1. 使用@Configuration类集中管理Bean

将相关Bean集中在一个配置类中,通过@Bean方法定义,然后在需要的地方注入整个配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
public class MyBeansConfig {
@Bean
public MyBean1 myBean1() {
return new MyBean1();
}

@Bean
public MyBean2 myBean2() {
return new MyBean2();
}
// 其他Bean定义
}

@Service
public class MyService {
private final MyBeansConfig myBeansConfig;

@Autowired
public MyService(MyBeansConfig myBeansConfig) {
this.myBeansConfig = myBeansConfig;
}

public void someMethod() {
MyBean1 bean1 = myBeansConfig.myBean1();
MyBean2 bean2 = myBeansConfig.myBean2();
// 使用Bean
}
}
  1. 使用@Qualifier@Autowired按类型或名称注入

如果Bean有特定类型或名称,可以通过@Qualifier@Autowired按需注入。

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class MyService {
private final MyBean1 myBean1;
private final MyBean2 myBean2;

@Autowired
public MyService(@Qualifier("myBean1") MyBean1 myBean1,
@Qualifier("myBean2") MyBean2 myBean2) {
this.myBean1 = myBean1;
this.myBean2 = myBean2;
}
}
  1. 使用@ComponentScan自动扫描Bean

通过@ComponentScan自动扫描并注册Bean,减少手动配置。

1
2
3
4
5
6
7
@SpringBootApplication
@ComponentScan(basePackages = {"com.example"})
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
  1. 使用@Autowired注入集合

如果需要注入多个相同类型的Bean,可以直接注入集合。

1
2
3
4
5
6
7
8
9
@Service
public class MyService {
private final List<MyBeanInterface> myBeans;

@Autowired
public MyService(List<MyBeanInterface> myBeans) {
this.myBeans = myBeans;
}
}
  1. 使用@Resource按名称注入

@Resource可以按名称注入Bean,适合需要特定Bean的场景。

1
2
3
4
5
6
7
8
@Service
public class MyService {
@Resource(name = "myBean1")
private MyBean1 myBean1;

@Resource(name = "myBean2")
private MyBean2 myBean2;
}
  1. 使用ApplicationContext动态获取Bean

通过ApplicationContext动态获取Bean,避免在类中声明大量依赖。

1
2
3
4
5
6
7
8
9
10
11
@Service
public class MyService {
@Autowired
private ApplicationContext applicationContext;

public void someMethod() {
MyBean1 bean1 = applicationContext.getBean(MyBean1.class);
MyBean2 bean2 = applicationContext.getBean(MyBean2.class);
// 使用Bean
}
}
  1. 重构设计

如果类依赖过多Bean,可能是设计问题,考虑将功能拆分到多个类中,或使用设计模式如策略模式工厂模式等。

总结

  • 集中管理:使用@Configuration类集中管理Bean。
  • 按需注入:通过@Qualifier@Resource@Autowired按需注入。
  • 自动扫描:使用@ComponentScan减少手动配置。
  • 动态获取:通过ApplicationContext动态获取Bean。
  • 重构设计:考虑拆分功能或使用设计模式优化。