Spring Boot 原理
介绍一下 Spring Boot 整体的启动流程?
Spring Boot 启动流程的核心是通过自动配置和上下文初始化,将零散的组件装配成一个可运行的应用上下文(ApplicationContext),整体分为 前置准备、上下文初始化、核心刷新、启动收尾 四大步
首先从 main 找到
SpringApplication.run(主类.class, args)方法,在执行run()方法之前先 new 一个SpringApplication实例对象。实例化时做的具体核心内容:
然后进行应用类型推断,判断当前应用是 Servlet 应用(Spring Web MVC)、Reactive 应用(Spring WebFlux)还是普通非 Web 应用。
紧接着进行初始化器加载,从
META-INF/spring.factories中加载所有ApplicationContextInitializer的实现类,用于在上下文刷新前进行自定义配置,包括 Spring Boot 配置的默认实现和开发者添加的自定义实现。接着加载所有实现
ApplicationListener的类,监听启动过程中的应用生命周期事件进入
SpringApplication.run()后启动事件监听机制,创建SpringApplicationRunListeners对象,它是所有监听器的 总调度器,发布ApplicationStartingEvent事件,通知所有监听器“应用要开始启动了”。监听器可以做一些前置工作推断出主启动类
所以说这一步是启动前的资源预先加载的步骤
然后加载 Spring Boot 配置环境
ConfigurableEnviroment,把配置环境Enviroment加入监听对象中这一步是环境准备,为整个应用提供配置来源,是配置中心初始化,把所有外部配置统一抽象成 Environment,供后续上下文和 Bean 使用。
然后进行上下文初始化,创建应用上下文
ConfigurableApplicationContext,当作 run 方法的返回对象。这里会根据推断的应用类型,创建对应类型的上下文,然后调用所有加载的Initializer对上下文进行定制化配置,通知所有监听器应用开始启动。这一步是容器的 空壳创建,只完成了基础结构搭建,还没开始加载 Bean。
最后创建 Spring 容器,调用
refreshContext(context)进行容器上下文刷新,实现 starter 自动化配置和 Bean 加载和实例化,发布ApplicationReadyEvent通知应用就绪
Spring Boot 只是在 Spring refresh()
之前加了自动配置、环境准备、内嵌容器等封装,核心容器逻辑还是
Spring 原生的。
讲解一下 Spring Boot 的特点,Spring Boot 和 Spring 有什么区别?
Spring Boot 是基于 Spring 框架的快速开发脚手架,核心是 “约定大于配置”。
解决了 Spring 框架配置繁琐、依赖管理复杂的问题;而 Spring 是一套完整的企业级开发框架,核心是 IOC(控制反转)和 AOP(面向切面编程),提供基础的核心能力,但需要大量手动配置。
简单说:Spring 是 基础能力库,Spring Boot 是 基于 Spring 的高效的开发脚手架。
核心特点如下
约定大于配置
Spring Boot 内置了大量默认配置,而且无需手动编写 XML 配置即可启动,而且使用注解就可以进行配置,比如开发一个 Web 项目,只需引入
spring-boot-starter-web依赖,就会自动配置一系列框架起步依赖 Starter
将常用的依赖组合打包成 Starter,面对一个开发场景,直接引入 starter 就可以包含一系列的开发依赖
自动配置
基于条件注解
@Conditional等,根据类路径下的依赖、配置文件等自动创建 Bean内嵌服务器
内置 Tomcat、Jetty、Undertow 等 Web 服务器,无需手动部署 WAR 包到外部服务器。项目可直接打成 JAR 包,通过
java -jar启动,部署、测试、运维更简单;简化监控与运维
什么是自动装配?SpringBoot自动装配原理是什么?
Spring Boot 的自动装配原理是基于 Spring Framework
的条件化配置和@EnableAutoConfiguration注解实现的。
自动装配就是通过注解或一些简单的配置就可以在 Spring Boot 的帮助下很方便的开启和配置各种功能,比如数据库访问、Web开发。
这种机制允许开发者在项目中引入相关的依赖,Spring Boot 将根据这些依赖自动配置应用程序的上下文和功能。
Spring Boot 定义了一套接口规范,这套规范规定:
- Spring Boot 在启动时会扫描外部引用 jar 包中的
META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 Spring Boot 定义的标准,就能将自己的功能装进 Spring Boot。
对于 Spring Boot 自动装配的原理
@SpringBootApplication注解的内部有个@EnableAutoConfiguration, 这个注解是实现自动装配的核心注解,其中这个注解的内容如下@AutoConfigurationPackage,将项目 src 中 main 包下的所有组件注册到容器中,例如标注了 Component 注解的类等@Import({AutoConfigurationImportSelector.class}),是自动装配的核心AutoConfigurationImportSelector是 Spring Boot 中一个重要的类,它实现了ImportSelector接口,用于实现自动配置的选择和导入。具体来说,它通过分析项目的类路径,然后进行条件判断,来决定应该导入哪些自动配置类。
Spring Boot 框架知识
Spring IoC 和 AOP 介绍一下?IoC和AOP是通过什么机制来实现的?
IoC:控制反转,它是一种创建和获取对象的技术思想,依赖注入 DI 是实现这种技术的一种方式。
传统开发过程中,我们需要通过new关键字来创建对象。使用 IoC 思想开发方式的话,我们不通过 new 关键字创建对象,而是通过 IoC 容器来帮我们实例化对象,把创建对象和对象之间的依赖管理的权利从自己身上交给了 Spring 容器,这就是控制反转的含义。 通过 IoC 的方式,可以大大降低对象之间的耦合度。
AOP:是面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以减少系统的重复代码,降低模块间的耦合度。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。
在 Spring 框架中,IOC 和 AOP 结合使用,可以更好地实现代码的模块化和分层管理。例如:
- 通过 IOC 容器管理对象的依赖关系,然后通过 AOP 将横切关注点统一切入到需要的业务逻辑中。
- 使用 IOC 容器管理 Service 层和 DAO 层的依赖关系,然后通过 AOP 在 Service 层实现事务管理、日志记录等横切功能,使得业务逻辑更加清晰和可维护
Spring IOC 实现机制
- 反射:Spring IOC容器利用 Java 的反射机制动态地加载类、创建对象实例及调用对象方法,反射允许在运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理。
- 依赖注入:IoC 的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring 通过构造函数注入、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解。
- 设计模式 - 工厂模式:Spring IoC 容器通常采用工厂模式来管理对象的创建和生命周期。容器作为工厂负责实例化Bean 并管理它们的生命周期,将 Bean 的实例化过程交给容器来管理。
- 容器实现:Spring IoC 容器是实现 IoC
的核心,通常使用
BeanFactory或ApplicationContext来管理 Bean。BeanFactory是 IoC 容器的基本形式,提供基本的IoC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。
Spring AOP 实现机制
Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时,从而实现在不修改源码的情况下增强方法的功能。
Spring AOP支持两种动态代理:
- 基于JDK的动态代理:使用
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。 - 基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring 会使用 CGLIB 库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。
怎么理解SpringBoot中的约定大于配置
约定大于配置是 Spring Boot 的特点,通过预设合理的默认行为和项目规范,大幅减少开发者需要手动配置的步骤,从而提升开发效率和项目标准化程度。可以从以下几个方面来解释:
自动化配置:Spring Boot 提供了大量的自动化配置,通过分析项目的依赖和环境,自动配置应用程序的行为。开发者无需显式地配置每个细节,大部分常用的配置都已经预设好了。
例如,引入
spring-boot-starter-web后,Spring Boot会自动配置内嵌Tomcat和Spring MVC,无需像 Spring 那样还需要手动编写XML中的很多配置才能启动。默认配置:Spring Boot 为诸多方面提供大量默认配置,如连接数据库、设置 Web 服务器、处理日志等。开发人员无需手动配置这些常见内容,框架已做好决策。
约定的项目结构:Spring Boot 提倡特定的项目结构,通常主应用程序类(含 main 方法)置于根包,控制器类、服务类、数据访问类等分别放在相应子包。此约定使团队成员更易理解项目结构与组织,规范项目开发,新成员加入项目时能快速定位各功能代码位置,提升协作效率。
Spring Boot 框架相关注解
@Aspect注解的使用?如何定义切面和通知?结合 Spring 框架聊聊
@Aspect注解是 Spring AOP
中定义切面类的核心注解,需要配合@Component注册为Bean。一个标准的切面类需要同时使用@Aspect和@Component两个注解
Spring AOP基于代理机制,所以只有通过Spring容器调用的方法才能被拦截,这也是为什么我们要确保被拦截的类要注册为Spring Bean的原因。
切面定义通过在类上添加@Aspect实现,切点定义使用@Pointcut注解指定拦截规则,比如@Pointcut("execution(* com.example.service.*.*(..))")表示拦截
service 包下所有方法。
谈到 @Aspect 时,你需要明确 Spring AOP 和 AspectJ 的关系:Spring AOP 实际上借鉴了 AspectJ 的注解体系,包括 @Aspect、@Pointcut、@Before 等注解都来自 AspectJ,但底层实现机制不同。Spring AOP 基于动态代理实现,只能拦截 Spring 管理的 Bean 的方法调用,而 AspectJ 是通过字节码织入实现的,功能更强大但也更复杂。在日常开发中,Spring AOP 已经能满足大部分场景需求。
通知类型包括五种:@Before在目标方法执行前运行,常用于参数校验;@After在方法执行后运行,用于资源清理;@AfterReturning在方法正常返回后执行,可获取返回值进行后置处理;@AfterThrowing在方法抛异常时执行,用于异常日志记录;@Around环绕整个方法执行,最为灵活,常用于性能监控和事务控制。
Spring 会在运行时为被拦截的 Bean 创建代理对象,当调用目标方法时实际执行的是代理逻辑,从而实现横切关注点的分离。记住切面只对 Spring 容器管理的 Bean 生效,直接 new 的对象无法被拦截。
对于多个切面可以进行执行顺序控制,当有多个切面作用于同一个方法时,Spring通过@Order注解来控制执行顺序。数值越小,优先级越高。但是对于这些注解并不是优先级高就一定先执行,例如对于@Before通知,Order值小的先执行;对于@After通知,Order值小的后执行
如何理解@SpringBootApplication注解,为什么标注了这个注解的类能够作为主启动类
@SpringBootApplication 是 Spring Boot 核心的组合注解,它整合了 3 个注解。
@ComponentScan做组件扫描,扫描当前包及子包下的@Controller/@Service/@Component等注解的类@EnableAutoConfiguration开启自动配置,自动加载 Spring Boot 内置的默认配置@SpringBootConfiguration也是标记该类为配置类,本质是@Configuration
标注了该注解的类之所以能作为主启动类,是因为它完成了 开启自动配置、扫描组件、标记配置类 三大核心工作,是 Spring Boot 约定大于配置 思想的集中体现。
1 |
|
启动类的核心是 main 方法 +
SpringApplication.run(),但真正让它成为 启动入口 的是
@SpringBootApplication 的三大能力:
@ComponentScan 会从启动类所在包开始,递归扫描所有子包下的组件
如果你的 Controller 放在启动类的上层包,会扫描不到,我被坑过,需要指定路径
@EnableAutoConfiguration会通过SpringFactoriesLoader加载META-INF/spring.factories文件中的自动配置类,自动配置类通过@Conditional注解实现按需要生效,只需配置配置文件yaml中的相关信息,无需手动创建 Bean,就是自动配置的体现。@SpringBootConfiguration让启动类具备配置类的能力,可直接在启动类中通过@Bean定义全局组件
执行SpringApplication.run(),进行的是 Spring Boot
项目启动那一套了
- 加载
@SpringBootApplication注解的配置; - 扫描组件并注册到 Spring 容器;
- 启动自动配置流程;
- 启动内嵌 Tomcat(如果引入 web 依赖);
- 初始化 Spring 上下文,项目启动完成。
Spring Boot 实践相关
如何在 Spring Boot 中定义和读取自定义配置?Spring Boot 配置文件加载优先级你知道吗?
Spring Boot 支持多种配置文件格式,而自定义配置项的核心就是定义配置项 + 读取配置项
定义自定义配置
先在配置文件中定义自定义配置项,这就是我们把配置写到配置文件里
1 | # application.yml |
而读取配置项有几种方式
@Value注解:${配置项key:默认值}直接注入单个配置项,需要注意配置文件需要成为 Bean 交给 Spring Boot 管理,加
@Component1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
// 必须交给Spring容器管理,否则无法注入
public class AppConfig {
// 注入单个配置项,冒号后是默认值
private String appName;
private String appVersion;
// 注入嵌套配置项
private String dbUrl;
}@ConfigurationProperties它是批量绑定配置项,适合配置项多、有层级的场景。一般情况下,我们需要自定义一系列自定义配置,就使用这个注解编写对应的配置类,业务中可以直接注入使用
一般需要如下几步
编写配置绑定类,也就是在这里声明里的
yaml中需要绑定哪些自定义配置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
30import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
// 前缀:绑定yaml配置文件中以app开头的配置项
// Spring Boot 2.2+ 也可通过 @EnableConfigurationProperties 启用
// 必须有getter/setter,否则无法绑定
public class AppProperties {
// 字段名与配置项后缀一致(name → app.name)
private String name;
private String version;
private String env;
// 嵌套配置(对应 app.database)
private Database database;
// 数组配置(对应 app.servers)
private List<String> servers;
// 内部类:绑定嵌套配置
public static class Database {
private String url;
private String username;
private String password;
}
}如果不写
@Component,可在启动类加@EnableConfigurationProperties然后就可以使用配置,注入依赖后直接使用就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
public class ConfigController {
private AppProperties appProperties;
public String getConfig() {
return "应用名称:" + appProperties.getName() +
"<br>数据库地址:" + appProperties.getDatabase().getUrl() +
"<br>服务器列表:" + appProperties.getServers();
}
}
然后,Spring Boot 会从多个位置加载配置文件,优先级高的配置会覆盖优先级低的,从高到低优先级如下
命令行参数
启动时通过启动命令传入的参数,会覆盖所有配置,适合临时修改配置。
操作系统环境变量
Spring Boot 会自动识别操作系统的环境变量
JVM系统属性
启动时通过 JVM 属性传入的配置,
-Dapp.name=prod传入,如java -Dapp.name=prod -jar app.jar。外置的配置文件
(项目根目录
config/> 项目根目录)项目根目录下的
config/文件夹:./config/application.yml项目根目录:
./application.yml内置的配置文件
(
classpath/config/ >classpath根目录)类路径(resources)下的
config/文件夹:classpath:/config/application.yml类路径根目录:
classpath:/application.yml配置类中的默认值
如
@Value("${app.name:default}")中的default,是最后兜底的默认值
如果配置了多环境,优先级规则如下
- 通过
spring.profiles.active=dev指定环境后,对应环境的配置(如application-dev.yml)会覆盖application.yml中的同名配置; - 多环境配置的加载优先级遵循上述「外置 > 内置」规则。
写过 Spring Boot starter 吗?
创建Maven项目
首先,需要创建一个新的Maven项目。在 pom.xml 中添加 Spring Boot 的 starter parent 和一些依赖
然后添加自动配置
在
resources文件夹下,在META-INF/spring.factories中添加自动配置的元数据。创建配置熟悉类
一般情况下,我们的 starter 中需要有供用户定义的配置,创建一个配置属性类,使用
@ConfigurationProperties注解来绑定配置文件中的属性。创建服务和控制器
创建一个服务类和服务实现类,以及一个控制器来展示和测试你的 starter 的功能。
发布 Starter
测试后,将你的starter发布到Maven仓库,可以是私有的或是公共的
使用 Starter
在你的主应用的pom.xml中添加你的starter依赖,然后在application.yml或application.properties中配置你的属性。
Spring Boot 如何处理跨域请求(CORS)?
跨域只存在于浏览器端,之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。不同的请求类型,CORS的处理也不一样
- 简单请求:
GET/POST/HEAD,且请求头只有默认字段(如Content-Type为text/plain),直接触发跨域检查; - 预检请求(OPTIONS):
PUT/DELETE/ 自定义请求头,Content-Type为application/json时,浏览器会先发送OPTIONS预检请求,确认后端允许跨域后,再发送真实请求。
Spring Boot 处理跨域的 3 种方案如下
全局跨域配置
通过配置类统一设置跨域规则,覆盖所有接口
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
56import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 标记为配置类
public class CorsConfig {
// 方式 1:通过 WebMvcConfigurer 配置(更简洁,推荐)
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 对所有接口生效
// 允许的跨域源(前端域名),* 表示允许所有(生产环境建议指定具体域名)
.allowedOriginPatterns("*")
// 允许的请求方法(GET/POST/PUT/DELETE 等)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
// 允许的请求头
.allowedHeaders("*")
// 是否允许携带 Cookie(跨域认证需要)
.allowCredentials(true)
// 预检请求的缓存时间(秒),减少 OPTIONS 请求次数
.maxAge(3600);
}
};
}
// 方式 2:通过 CorsFilter 配置(更灵活,面试可提)
/*
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许的源(Spring Boot 2.4+ 推荐用 allowedOriginPatterns,替代 allowedOrigins)
config.addAllowedOriginPattern("*");
// 允许的请求方法
config.addAllowedMethod("*");
// 允许的请求头
config.addAllowedHeader("*");
// 允许携带 Cookie
config.setAllowCredentials(true);
// 预检缓存时间
config.setMaxAge(3600L);
// 配置生效的路径
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
*/
}局部跨域配置
@CrossOrigin 注解针对单个接口 / 控制器生效,接口级
@CrossOrigin> 控制器级@CrossOrigin> 全局配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
// 对整个控制器的所有接口生效
public class TestController {
// 对单个接口生效(优先级高于控制器注解)
public String testCors() {
return "跨域请求成功";
}
}通过网关处理
如果项目中使用 Spring Cloud Gateway/Zuul 网关,可在网关层统一配置跨域,避免每个微服务重复配置
一般情况下,除非开发时测试,否则禁止使用
allowedOriginPatterns("*"),必须指定具体的前端域名(如
http://www.xxx.com),避免安全风险。而且开启
allowCredentials(true) 时,前端必须同步设置
withCredentials: true;
Spring Boot 的项目结构是怎么样的?
一个正常的企业项目里,通常会使用一种通用的项目结构和代码层级划分,来规范化项目开发和便于团队协作
一般情况下,一个 Spring Boot 项目里会有这样的一个结构
1 | com |
配置层 config
存放所有配置:Redis、Security、跨域配置、线程池等。包括你自定义配置中的内容。
控制器层 controller
接收前端请求、参数校验、调用 service、返回统一结果。
业务逻辑层 service
核心业务处理、调用DAO持久层,调用第三方服务等
数据访问层 dao
和数据库交互,CRUD。
数据模型层 model
- 数据库实体 entity:对应数据库表结构
- 数据传输对象 dto /vo:
- dto:前端传给后端的请求
- vo:后端返回给前端的视图对象
通用模块 common
里面可以放工具类,异常处理,自定义的一些注解啊什么
安全模块 security
登录、认证、授权、JWT、OAuth2、2FA等安全业务在这里处理
AOP 切面层 aspect
日志、操作记录、权限校验、接口耗时统计、网站 UV 收集等
所以说,Spring Boot
的标准结构是典型的三层架构,Controller → Service → Mapper,结构清晰、职责明确、便于团队协作和维护。
Spring Boot 当中,你是如何实现统一异常处理的?
在 Spring Boot 里,我是通过 @ControllerAdvice +
@ExceptionHandler
实现全局统一异常处理的,同时配合自定义业务异常和统一返回格式,让所有接口的异常都能集中处理、格式一致、便于排查。
@ExceptionHandler异常处理器注解,指定方法处理某一类异常,比如业务异常、参数异常、系统异常。
@ControllerAdvice开启全局控制器增强,能捕获所有
Controller 抛出的异常。
这样代码不冗余、返回格式统一、安全不泄露堆栈、方便日志记录、便于前端处理。
首先肯定要定义统一返回结果
所有接口成功 / 失败都用同一个格式
然后写一个自定义业务异常
BusinessException区分业务错误和系统错误
然后写一个全局异常处理器
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 GlobalExceptionHandler {
// 1. 处理业务异常
public Result<?> handleBusinessException(BusinessException e) {
log.error("业务异常:{}", e.getMessage());
return Result.fail(e.getCode(), e.getMessage());
}
// 2. 处理参数校验异常(@Valid 校验失败)
public Result<?> handleValidException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.fail(400, "参数错误:" + msg);
}
// 3. 兜底:处理所有未知系统异常
public Result<?> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(500, "服务器繁忙,请稍后重试");
}
}
这样,所有 Controller 不写
try/catch,直接抛自定义业务异常,代码非常干净,而且能够全局捕获,分类处理,让异常不再飞升老冯






