SpringBoot细磕
前言
项目注释代码:https://github.com/nimbusking/spring-boot/tree/2.3.x
与传统Web架构比较
- 简化配置
- 优势:Spring Boot 采用“约定优于配置”的理念,提供了大量的默认配置,开发者无需手动配置大量的 XML 或注解。
- 传统架构:传统的 Spring MVC 或 Java EE 需要手动配置大量的 XML 文件(如
web.xml
、dispatcher-servlet.xml
等),开发效率较低。
- 内嵌服务器
- 优势:Spring Boot 内置了 Tomcat、Jetty 或 Undertow 等 Web 服务器,无需额外部署 WAR 文件到外部服务器,直接运行 JAR 文件即可启动应用。
- 传统架构:传统 Web 应用需要将 WAR 文件部署到外部的 Tomcat、WebLogic 等服务器,部署流程复杂。
- 快速启动和开发
- 优势:Spring Boot 提供了丰富的 Starter 依赖,只需引入相关依赖即可快速集成常用功能(如数据库、缓存、安全等),极大提高了开发效率。
- 传统架构:传统架构需要手动引入和配置各种依赖,开发周期较长。
- 自动化依赖管理
- 优势:Spring Boot 通过
spring-boot-starter-*
依赖简化了依赖管理,避免了版本冲突问题。 - 传统架构:传统架构需要手动管理依赖版本,容易出现依赖冲突。
- 优势:Spring Boot 通过
- 独立运行
- 优势:Spring Boot 应用可以打包为可执行的 JAR 文件,独立运行,无需依赖外部服务器。
- 传统架构:传统 Web 应用需要依赖外部服务器(如 Tomcat)运行。
- 强大的开发工具支持
- 优势:Spring Boot 提供了丰富的开发工具,如:
- Spring Boot DevTools:支持热部署,开发时无需重启应用。
- Actuator:提供应用监控和管理功能。
- 传统架构:传统架构缺乏这些工具支持,开发和调试效率较低。
- 优势:Spring Boot 提供了丰富的开发工具,如:
- 微服务友好
- 优势:Spring Boot 是构建微服务的理想选择,与 Spring Cloud 无缝集成,支持服务发现、负载均衡、配置中心等微服务特性。
- 传统架构:传统架构更适合单体应用,微服务支持较弱。
- 更好的测试支持
- 优势:Spring Boot 提供了完善的测试支持,如
@SpringBootTest
注解,可以轻松编写单元测试和集成测试。 - 传统架构:传统架构的测试配置复杂,测试效率较低。
- 优势:Spring Boot 提供了完善的测试支持,如
- 生态系统丰富
- 优势:Spring Boot 基于 Spring 生态系统,支持大量的第三方库和工具,社区活跃,文档丰富。
- 传统架构:传统架构的生态系统相对较弱,社区支持有限。
- 云原生支持
- 优势:Spring Boot 天生支持云原生开发,与 Docker、Kubernetes 等云原生技术无缝集成。
- 传统架构:传统架构需要额外适配云原生环境。
总结对比
特性 | Spring Boot | 传统 Web 架构 |
---|---|---|
配置 | 自动配置,简化开发 | 手动配置,复杂且繁琐 |
服务器 | 内嵌服务器,独立运行 | 依赖外部服务器(如 Tomcat) |
依赖管理 | 自动化依赖管理,避免冲突 | 手动管理依赖,易冲突 |
开发效率 | 快速启动和开发,工具支持完善 | 开发周期长,工具支持有限 |
部署方式 | 可执行 JAR,独立运行 | 需要部署 WAR 文件到外部服务器 |
微服务支持 | 与 Spring Cloud 无缝集成 | 微服务支持较弱 |
测试支持 | 完善的测试支持 | 测试配置复杂 |
云原生支持 | 天生支持云原生 | 需要额外适配 |
适用场景
- Spring Boot:适合快速开发、微服务架构、云原生应用等场景。
- 传统 Web 架构:适合传统单体应用或对现有系统进行维护的场景。
知识点总结
一、Spring Boot 基本原理
- Spring Boot 的核心目标是什么?
简化 Spring 应用的初始搭建和开发流程,通过自动配置、内嵌容器(Tomcat/Jetty)和 Starter 依赖实现“约定优于配置”。 - Spring Boot 自动配置是如何工作的?
- 基于
@EnableAutoConfiguration
触发,通过spring-boot-autoconfigure
下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
加载配置类。 - 使用条件注解(如
@ConditionalOnClass
,@ConditionalOnMissingBean
)按需生效配置。
- 基于
- 什么是 Starter 依赖?举例说明其作用。
Starter 是一组预定义的依赖集合(如spring-boot-starter-web
包含 Web MVC、Tomcat、JSON 支持),简化依赖管理和版本兼容。 - 如何覆盖默认的自动配置?
- 自定义同名 Bean 覆盖自动配置类中的 Bean。
- 通过
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
排除特定配置。
- Spring Boot 如何简化配置管理?
- 支持多环境配置(
application-{profile}.properties
)。 - 外化配置优先级:命令行参数 > 系统变量 > 配置文件 > 默认值。
- 支持多环境配置(
二、Spring Boot 启动流程
- SpringApplication.run() 方法的核心流程是什么?
- 创建
SpringApplication
实例,加载META-INF/spring.factories
中的初始化器和监听器。 - 执行
ApplicationContextInitializer
。 - 创建并刷新
ApplicationContext
(如AnnotationConfigServletWebServerApplicationContext
)。 - 执行
CommandLineRunner
和ApplicationRunner
。
- 创建
- @SpringBootApplication 注解的作用是什么?
组合注解,包含:@SpringBootConfiguration
:标记主配置类。@EnableAutoConfiguration
:启用自动配置。@ComponentScan
:扫描当前包及子包的组件。
- Spring Boot 如何启动内嵌 Tomcat?
- 通过
ServletWebServerFactory
接口实现类(如TomcatServletWebServerFactory
)创建 Web 服务器实例。 - 自动配置类
ServletWebServerFactoryAutoConfiguration
触发。
- 通过
三、应用设计
- 如何读取自定义配置?
@Value("${property.key}")
注解注入单个值。@ConfigurationProperties(prefix="my.app")
绑定配置到 Java 对象。
- Spring Boot 如何实现多环境配置?
- 使用
application-{dev|prod}.properties
文件。 - 激活方式:命令行
--spring.profiles.active=dev
或配置spring.profiles.active
。
- 使用
- 如何设计 RESTful API 并统一异常处理?
- 使用
@RestController
定义接口。 - 全局异常处理:
@ControllerAdvice
+@ExceptionHandler
。
- 使用
- Spring Boot 中的事务管理如何实现?
- 添加
@Transactional
注解,依赖spring-boot-starter-jdbc
或spring-boot-starter-data-jpa
。 - 配置
PlatformTransactionManager
Bean(自动配置默认提供)。
- 添加
四、进阶场景
- 如何扩展 Spring Boot 的自动配置?
- 创建自定义 Starter:
- 编写
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件。 - 使用条件注解控制 Bean 的加载。
- 编写
- 创建自定义 Starter:
- Spring Boot Actuator 的作用是什么?
提供生产级监控端点(如/health
,/metrics
),需引入spring-boot-starter-actuator
,通过management.endpoints.web.exposure.include=*
暴露。 - 如何优化 Spring Boot 应用的启动速度?
- 开启懒加载(
spring.main.lazy-initialization=true
)。 - 减少不必要的自动配置(
@SpringBootApplication(exclude=...)
)。 - 使用 AOT(提前编译)技术(Spring Native)。
- 开启懒加载(
- Spring Boot 如何支持响应式编程?
- 使用
spring-boot-starter-webflux
替代spring-boot-starter-web
。 - 核心组件:
RouterFunction
、HandlerFilterFunction
、响应式仓库(如ReactiveMongoRepository
)。
- 使用
- 如何实现分布式配置中心?
- 结合 Spring Cloud Config Server,通过
@EnableConfigServer
注解启动配置服务器。 - 客户端通过
spring-cloud-starter-config
读取远程配置。
- 结合 Spring Cloud Config Server,通过
五、源码与原理深入
- 自动配置类的加载顺序如何控制?
- 使用
@AutoConfigureOrder
或@AutoConfigureBefore
/@AutoConfigureAfter
注解。
- 使用
- SpringApplication 的生命周期事件有哪些?
ApplicationStartingEvent
(启动前)ApplicationPreparedEvent
(上下文已创建但未刷新)ApplicationReadyEvent
(应用已就绪)
- 嵌入式容器的启动流程源码分析?
核心类:ServletWebServerApplicationContext
,在refresh()
阶段调用createWebServer()
创建并启动容器。
其它问题
Spring Boot 2.x 和 3.x 的主要区别?
如何实现多数据源和动态数据源?
一、多数据源配置
- 1. 添加依赖
确保包含JDBC和数据库驱动依赖:1
2
3
4
5
6
7
8<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency> - 2. 配置数据源信息
在application.yml
中配置多个数据源:1
2
3
4
5
6
7
8
9
10
11
12spring:
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/db1
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://localhost:3306/db2
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver - 3. 配置主数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DataSourceConfig {
// 主数据源
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
// 次数据源
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
} - 4. 配置事务管理器
为每个数据源单独配置事务管理器:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TransactionManagerConfig {
public PlatformTransactionManager primaryTransactionManager(
{ DataSource dataSource)
return new DataSourceTransactionManager(dataSource);
}
public PlatformTransactionManager secondaryTransactionManager(
{ DataSource dataSource)
return new DataSourceTransactionManager(dataSource);
}
} - 5. 使用不同数据源
在DAO层通过@Qualifier
指定数据源:1
2
3
4
5
6
7
8
9
10
11
12
public class UserDao {
private final JdbcTemplate jdbcTemplate;
public UserDao( { DataSource dataSource)
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// 使用主数据源操作数据库
}
二、动态数据源切换
- 1. 定义动态数据源路由类
继承AbstractRoutingDataSource
实现动态路由:1
2
3
4
5
6
7
8public class DynamicDataSource extends AbstractRoutingDataSource {
protected Object determineCurrentLookupKey() {
// 从ThreadLocal中获取数据源标识
return DataSourceContextHolder.getDataSourceKey();
}
} - 2. 数据源上下文管理
使用ThreadLocal
存储当前线程的数据源标识:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
CONTEXT.set(key);
}
public static String getDataSourceKey() {
return CONTEXT.get();
}
public static void clearDataSourceKey() {
CONTEXT.remove();
}
} - 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
public class DynamicDataSourceConfig {
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
public DataSource dynamicDataSource(
DataSource primaryDataSource,
{ DataSource secondaryDataSource)
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("primary", primaryDataSource);
targetDataSources.put("secondary", secondaryDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
public PlatformTransactionManager transactionManager(
{ DataSource dataSource)
return new DataSourceTransactionManager(dataSource);
}
} - 4. 定义切面切换数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DataSourceAspect {
// 根据注解切换数据源
public void switchDataSource(JoinPoint point, DataSourceSwitch dataSourceSwitch) {
DataSourceContextHolder.setDataSourceKey(dataSourceSwitch.value());
}
// 执行后清理数据源标识
public void restoreDataSource(JoinPoint point, DataSourceSwitch dataSourceSwitch) {
DataSourceContextHolder.clearDataSourceKey();
}
}
// 自定义注解
public DataSourceSwitch {
String value() default "primary";
} - 5. 使用动态数据源
在Service层通过注解切换数据源:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserService {
private UserDao userDao;
public List<User> getPrimaryUsers() {
return userDao.findAll();
}
public List<User> getSecondaryUsers() {
return userDao.findAll();
}
}
三、注意事项
- 事务管理:动态数据源切换需确保事务管理器与当前数据源一致。
- 连接池配置:为每个数据源单独配置连接池参数(如HikariCP)。
- 线程安全:使用
ThreadLocal
确保多线程环境下数据源隔离。 - 异常处理:在切面中需处理异常并清理数据源标识。
四、总结
- 多数据源:通过手动配置多个
DataSource
和事务管理器实现。 - 动态切换:基于
AbstractRoutingDataSource
和AOP实现运行时动态路由。 - 适用场景:读写分离、分库分表、多租户系统等需要灵活切换数据源的场景。
如何集成 Spring Security?
一、快速集成(自动配置)
- 添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 添加依赖
- 默认行为
- 所有端点自动启用 HTTP Basic 认证
- 自动生成默认用户(用户名为
user
,密码在控制台输出) - 默认启用 CSRF 保护
二、自定义安全配置
- 创建安全配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SecurityConfig {
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
}
- 创建安全配置类
- 配置用户存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- 配置用户存储
三、常用配置选项
- 禁用 CSRF(开发环境)
1
http.csrf(csrf -> csrf.disable());
- 禁用 CSRF(开发环境)
- 配置 CORS
1
2
3
4
5
6
7
8
9
10
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://example.com"));
config.setAllowedMethods(List.of("GET", "POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
- 配置 CORS
方法级安全
1
2
3
4
5
public class MethodSecurityConfig {
// 启用方法注解
}在 Service 层使用:
1
2
3
4
public void deleteUser(Long id) {
// ...
}
四、数据库用户认证
- 使用 JPA 用户存储
1
2
3
4
5
public UserDetailsService userDetailsService(UserRepository userRepo) {
return username -> userRepo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
- 使用 JPA 用户存储
- 实体类示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private Set<Role> roles;
// 必须实现的方法
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.toList();
}
// 其他 UserDetails 方法实现...
}
- 实体类示例
五、OAuth2 集成(示例)
1 |
|
六、测试配置
- 测试类注解
1
2
3
4
5
6
7
public class SecurityTest {
private MockMvc mockMvc;
}
- 测试类注解
- 模拟认证用户
1
2
3
4
5
6
void testAuthenticatedEndpoint() throws Exception {
mockMvc.perform(get("/api/data"))
.andExpect(status().isOk());
}
- 模拟认证用户
七、常见问题解决
- 403 Forbidden 错误
- 检查 CSRF 配置
- 验证用户权限是否正确
- 登录重定向循环
- 确保登录页面允许匿名访问
- 检查权限配置顺序
- 密码加密不匹配
- 确认密码编码器与存储格式一致
- 使用
{bcrypt}
前缀
启动流程总结归纳
Spring Boot可执行JAR启动流程详解
1. JAR结构与启动入口
- 可执行JAR结构:
1
2
3
4
5
6BOOT-INF/
├── classes/ # 应用类文件(含主类)
└── lib/ # 依赖的第三方库JAR
META-INF/
└── MANIFEST.MF # 指定Main-Class为JarLauncher
org/springframework/boot/loader/ # Spring Boot Loader类 - 启动入口:
java -jar
执行时,根据MANIFEST.MF
中的Main-Class
调用JarLauncher
。
2. Launcher类加载机制
JarLauncher
作用:- 初始化自定义类加载器
LaunchedURLClassLoader
,加载BOOT-INF/lib
和BOOT-INF/classes
下的资源。 - 反射调用应用主类的
main
方法。
- 初始化自定义类加载器
- 嵌套JAR处理:通过
JarFile
扩展处理嵌套JAR条目,支持直接读取嵌套依赖。
3. 应用主类启动
- 主类执行:调用
SpringApplication.run(主类.class, args)
。 - SpringApplication阶段:
- 初始化阶段:
- 推断应用类型(Servlet/Reactive/None)。
- 加载
META-INF/spring.factories
中的ApplicationContextInitializer
和ApplicationListener
。
- 配置环境:
- 读取
application.properties
/application.yml
及环境变量。 - 发布
ApplicationEnvironmentPreparedEvent
事件。
- 读取
- 初始化阶段:
4. 应用上下文刷新
- 创建上下文:根据应用类型创建
AnnotationConfigServletWebServerApplicationContext
等实例。 refresh()
核心流程:- 准备BeanFactory:注册主类(
@SpringBootApplication
标注类)为配置类。 - 加载Bean定义:
- 解析
@ComponentScan
、@Import
(如@EnableAutoConfiguration
)。 - 加载
spring.factories
中的AutoConfiguration
类,按条件装配Bean。
- 解析
- 初始化Web服务器:
- 检测类路径存在
Servlet
类时,创建TomcatServletWebServerFactory
。 - 启动嵌入式Tomcat并绑定端口。
- 检测类路径存在
- 完成刷新:发布
ApplicationStartedEvent
,启动完成。
- 准备BeanFactory:注册主类(
5. 自动配置原理
- 条件化装配:通过
@ConditionalOnClass
、@ConditionalOnMissingBean
等条件注解按需加载配置。 - 配置加载顺序:
AutoConfiguration
类在用户自定义Bean之后加载,允许用户覆盖默认配置。
6. 嵌入式服务器启动
- Web服务器初始化:
ServletWebServerApplicationContext
创建WebServer
实例。- 注册
DispatcherServlet
并映射到根路径("/"
)。
- 端口监听:从
Environment
获取server.port
配置(默认8080)。
7. 流程图解
1 | java -jar → JarLauncher.main() |
8. 关键组件说明
- **
LaunchedURLClassLoader
**:负责从嵌套JAR中加载类,突破传统类加载器限制。 - **
SpringApplication
**:协调启动流程,处理事件监听与上下文配置。 - **
AutoConfigurationImportSelector
**:自动加载spring.factories
中的配置类。
9. 扩展:启动事件与监听器
- 核心事件:
ApplicationStartingEvent
:启动开始,环境未准备。ApplicationEnvironmentPreparedEvent
:环境就绪,配置加载完毕。ApplicationStartedEvent
:上下文刷新完成,应用运行。
- 自定义监听器:实现
ApplicationListener
接口,监听特定事件执行初始化逻辑。
下面就开始看看SpringBoot的启动
从怎么运行Jar包开始
Spring Boot 提供了 Maven 插件 spring-boot-maven-plugin,可以很方便的将我们的 Spring Boot 项目打成 jar 包或者 war 包。
考虑到部署的便利性,我们绝大多数(99.99%)的场景下,都会选择打成 jar 包,这样一来,我们就无需将项目部署于 Tomcat、Jetty 等 Servlet 容器中。
通过 Spring Boot 插件生成的 jar 包是如何运行,并启动 Spring Boot 应用的呢?
先来看一个SpringBoot打包出来的jar包,我们来看看内部结构
其中:
- BOOT-INF 目录:里面保存了我们自己 Spring Boot 项目编译后的所有文件,其中 classes 目录下面就是编译后的 .class 文件,包括项目中的配置文件等,lib 目录下就是我们引入的第三方依赖
- META-INF 目录:通过 MANIFEST.MF 文件提供 jar 包的元数据,声明 jar 的启动类等信息。每个 Java jar 包应该是都有这个文件的,参考 Oracle 官方对于 jar 的说明,里面有一个 Main-Class 配置用于指定启动类
- org.springframework.boot.loader 目录:也就是 Spring Boot 的 spring-boot-loader 工具模块,它就是 java -jar xxx.jar 启动 Spring Boot 项目的秘密所在,上面的 Main-Class 指定的就是该工具模块中的一个类
启动整体流程
1 | java -jar → JarLauncher.main() |
MANIFEST.MF
1 | Manifest-Version: 1.0 |
先解释一个问题,为什么不直接将我们的 Application 启动类设置为 Main-Class 启动呢?
有俩原因:
- 通过 Spring Boot Maven Plugin 插件打包后的 jar 包,我们的 .class 文件在 BOOT-INF/classes/ 目录下,在 Java 默认的 jar 包加载规则下找不到我们的 Application 启动类,也就需要通过 JarLauncher 启动加载。
- Java 规定可执行器的 jar 包禁止嵌套其它 jar 包,在 BOOT-INF/lib 目录下有我们 Spring Boot 应用依赖的所有第三方 jar 包,因此spring-boot-loader 项目自定义实现了 ClassLoader 实现类
LaunchedURLClassLoader
,支持加载 BOOT-INF/classes 目录下的 .class 文件,以及 BOOT-INF/lib 目录下的 jar 包。
JarLauncher
上面的 WarLauncher 是针对 war 包的启动类,和 JarLauncher 差不多,感兴趣的可以看一看,这里我们直接来看到 JarLauncher 这个类
1 | public class JarLauncher extends ExecutableArchiveLauncher { |
可以看到它有个 main(String[]) 方法,前面说到的 META-INF/MANIFEST.MF 文件中的 Main-Class 配置就是指向了这个类,也就会调用这里的 main 方法,会做下面两件事:
- 创建一个 JarLauncher 实例对象,在 ExecutableArchiveLauncher 父类中会做以下事情:
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// org.springframework.boot.loader.ExecutableArchiveLauncher
public abstract class ExecutableArchiveLauncher extends Launcher {
public ExecutableArchiveLauncher() {
try {
// 为当前应用创建一个 Archive 对象,可用于解析 jar 包(当前应用)中所有的信息
this.archive = createArchive();
// 创建一个提供 JAR 排序信息的类路径索引文件,子类实现的
this.classPathIndex = getClassPathIndex(this.archive);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
// org.springframework.boot.loader.Launcher#createArchive
protected final Archive createArchive() throws Exception {
// 获取 jar 包(当前应用)所在的绝对路径
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
// 当前 jar 包的文件句柄
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException("Unable to determine code source archive from " + root);
}
// 为当前 jar 包创建一个 JarFileArchive(根条目),需要通过它解析出 jar 包中的所有信息
// 如果是文件夹的话则创建 ExplodedArchive(根条目)
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
} - 调用 JarLauncher#launch(String[]) 方法,也就是调用父类 Launcher 的这个方法
Launcher类
Spring Boot 应用的启动器
1 | public abstract class Launcher { |
会做下面几件事情:
- 调用 JarFile#registerUrlProtocolHandler() 方法:注册 URL(jar)协议的处理器,主要是使用自定义的 URLStreamHandler 处理器处理 jar 包
- 调用 getClassPathArchivesIterator() 方法:先从 archive(当前 jar 包应用)解析出所有的 JarFileArchive,这个 archive 就是在上面创建 JarLauncher 实例对象过程中创建的
- 调用
createClassLoader(Iterator<Archive>)
方法:创建 Spring Boot 自定义的 ClassLoader 类加载器,可加载当前 jar 包中所有的类,包括依赖的第三方包 - 调用 getMainClass() 方法:获取当前应用的启动类(你自己写的那个 main 方法所在的 Class 类对象)
- 调用 launch(…) 方法:执行你的项目中那个启动类的 main 方法(反射)
可以理解为会创建一个自定义的 ClassLoader 类加载器,主要可加载 BOOT-INF/classes 目录下的类,以及 BOOT-INF/lib 目录下的 jar 包中的类,然后调用你 Spring Boot 应用的启动类的 main 方法
接下来看看里面几个重要的方法
registerUrlProtocolHandler 方法
1 | public static void registerUrlProtocolHandler() { |
getClassPathArchivesIterator 方法
createClassLoader 方法
创建 Spring Boot 自定义的 ClassLoader 类加载器,可加载当前 jar 中所有的类
LaunchedURLClassLoader类
org.springframework.boot.loader.LaunchedURLClassLoader 是 spring-boot-loader 中自定义的类加载器,实现对 jar 包中 BOOT-INF/classes 目录下的类和 BOOT-INF/lib 下第三方 jar 包中的类的加载。
SpringApplication启动类启动过程
Spring Boot 项目的启动类通常有下面三种方式
1 | // 方式一 |
其中:方式一和方式二本质上都是通过调用 SpringApplication#run(..) 方法来启动应用,不同的是方式二通过构建器模式,先构建一个 SpringApplication 实例对象,然后调用其 run(..) 方法启动应用,这种方式可以对 SpringApplication 进行配置,更加的灵活。
方式三,它和方式一差不多,不同的是它继承了 SpringBootServletInitializer 这个类,作用就是当你的 Spring Boot 项目打成 war 包时能够放入外部的 Tomcat 容器中运行,如果是 war 包,那上面的 main(…) 方法自然是不需要的,当然,configure(..) 方法也可以不要
启动流程概括
启动应用
- 执行
main
方法,调用SpringApplication.run()
。
- 执行
初始化SpringApplication
- 设置应用类型(Servlet、Reactive等)。
- 加载
ApplicationContextInitializer
和ApplicationListener
。 - 读取
spring.factories
中的配置。
准备环境
- 创建并配置
Environment
(加载配置文件如application.properties
)。
- 创建并配置
创建应用上下文
- 根据应用类型创建
ApplicationContext
(如AnnotationConfigServletWebServerApplicationContext
,实际持有的是ServletWebServerApplicationContext对象)。
- 根据应用类型创建
刷新应用上下文
调用refresh()
方法,执行以下步骤:- 准备上下文。
- 加载Bean定义。
- 调用BeanFactoryPostProcessor。
- 注册BeanPostProcessor。
- 初始化MessageSource。
- 初始化事件广播器。
- 注册监听器。
- 实例化单例Bean。
- 完成上下文刷新。
执行Runners
- 执行
CommandLineRunner
和ApplicationRunner
。
- 执行
启动完成
- 应用启动完成,开始处理请求。
SpringApplication类
Spring 应用启动器。正如其代码上所添加的注释,它来提供启动 Spring 应用的功能。
相关属性
1 | public class SpringApplication { |
构造器
1 | public SpringApplication(Class<?>... primarySources) { |
实例化的过程中做了不少事情,如下:
- 设置资源加载器,默认为 null,可以通过 SpringApplicationBuilder 设置
- 设置 primarySources 为主要的 Class 类对象,通常是我们的启动类
- 通过 classpath 判断是否存在相应的 Class 类对象,来决定当前 Web 应用的类型(REACTIVE、SERVLET、NONE),默认为 SERVLET,不同的类型后续创建的 Environment 类型不同
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
30public enum WebApplicationType {
NONE, SERVLET, REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
} - 初始化所有 ApplicationContextInitializer 类型的对象,并保存至 initializers 集合中
- 初始化所有 ApplicationListener 类型的对象,并保存至 listeners 集合中,例如 ConfigFileApplicationListener 和 LoggingApplicationListener
- 获取当前被调用的 main 方法所属的 Class 类对象,并设置(主要用于打印日志)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15private Class<?> deduceMainApplicationClass() {
try {
// 获取当前的调用栈
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 对调用栈进行遍历,找到 `main` 方法所在的 Class 类对象并返回
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
上面的第 4 和 5 步都是通过类加载器从 META-INF/spring.factories 文件中分别获取 ApplicationContextInitializer 和 ApplicationListener 类型的类名称,然后进行实例化。
这个两种类型的对象都是对 Spring 的一种拓展,像很多框架整合 Spring Boot 都可以通过自定义的 ApplicationContextInitializer 对 ApplicationContext 进行一些初始化,通过 ApplicationListener 在 Spring 应用启动的不同阶段来织入一些功能。
SpringApplication#run 方法
直接看run方法
1 | public ConfigurableApplicationContext run(String... args) { |
refreshContext 方法
refreshContext(ConfigurableApplicationContext) 方法,刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat
1 | private void refreshContext(ConfigurableApplicationContext context) { |
前文第8步 createApplicationContext 方法 方法中讲到,我们默认情况下是 SERVLET 应用类型,也就是创建一个 AnnotationConfigServletWebServerApplicationContext 对象,在其父类 ServletWebServerApplicationContext 中重写了 onRefresh() 方法,会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境。
这部分就跟我们的SpringIOC关联起来了,下面一小节就来介绍是怎么关联的。
内嵌Tomcat容器实现
先来回顾一下refresh方法
1 | /** |
这里面跟springboot关联的是第10步和第13步:
在该方法的第 10 步可以看到会调用 onRefresh() 方法再进行一些初始化工作,这个方法交由子类进行扩展,那么在 Spring Boot 中的 ServletWebServerApplicationContext 重写了该方法,会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境。
第 13 步会调用 onRefresh() 方法,ServletWebServerApplicationContext 重写了该方法,启动 WebServer,对 Servlet 进行加载并初始化
小结
Spring Boot 的 Spring 应用上下文(ServletWebServerApplicationContext)在 refresh() 刷新阶段进行了扩展,分别在 onRefresh() 和 finishRefresh() 两个地方,分别做了以下事情:
- 创建一个 WebServer 服务对象,例如 TomcatWebServer 对象,对 Tomcat 的封装,用于控制 Tomcat 服务器
- 先创建一个 org.apache.catalina.startup.Tomcat 对象 tomcat,使用临时目录作为基础目录(tomcat.端口号),退出时删除,同时会设置端口、编码、最小空闲线程和最大线程数
- 为 tomcat 创建一个 TomcatEmbeddedContext 上下文对象,会添加一个 TomcatStarter(实现 javax.servlet.ServletContainerInitializer 接口)到这个上下文对象中
- 将 tomcat 封装到 TomcatWebServer 对象中,实例化过程会启动 tomcat,启动后会触发 javax.servlet.ServletContainerInitializer 实现类的回调,也就会触发 TomcatStarter 的回调,在其内部会调用 Spring Boot 自己的 ServletContextInitializer 初始器,例如 ServletWebServerApplicationContext#selfInitialize(ServletContext) 匿名方法
- 在这个匿名方法中会找到所有的 RegistrationBean,执行他们的 onStartup 方法,将其关联的 Servlet、Filter 和 EventListener 添加至 Servlet 上下文中,包括 Spring MVC 的 DispatcherServlet 对象
- 启动上一步创建的 TomcatWebServer 对象,上面仅启动 Tomcat 容器,Servlet 添加到了 ServletContext 上下文中,这里会将这些 Servlet 进行加载并初始化
外部Tomcat容器实现
TODO 针对WAR包的处理
剖析@SpringBootApplication注解
SpringBootApplication 注解在 Spring Boot 的 spring-boot-autoconfigre 子模块下,当我们引入 spring-boot-starter 模块后会自动引入该子模块
该注解是一个组合注解,如下:
1 |
|
@SpringBootConfiguration 注解
SpringBootConfiguration 注解,Spring Boot 自定义注解
1 |
|
该注解很简单,上面标注了 @Configuration 元注解,所以作用相同,同样是将一个类标注为配置类,能够作为一个 Bean 被 Spring IoC 容器管理
@ComponentScan 注解
ComponentScan 注解,Spring 注解,扫描指定路径下的标有 @Component 注解的类,解析成 Bean 被 Spring IoC 容器管理
1 |
|
该注解通常需要和 @Configuration 注解一起使用,因为需要 先被当做一个配置类,然后解析到上面有 @ComponentScan 注解后则处理该注解,通过 ClassPathBeanDefinitionScanner
扫描器去扫描指定路径下标注了 @Component 注解的类,将他们解析成 BeanDefinition(Bean 的前身),后续则会生成对应的 Bean 被 Spring IoC 容器管理
当然,如果该注解没有通过 basePackages 指定路径,Spring 会选在以该注解标注的类所在的包作为基础路径,然后扫描包下面的这些类
@EnableAutoConfiguration 注解
EnableAutoConfiguration 注解,Spring Boot 自定义注解,用于驱动 Spring Boot 自动配置模块,这个也是SpringBoot能够实现自动装配的核心注解
1 |
|
对于 Spring 中的模块驱动注解的实现都是通过 @Import 注解来实现的
模块驱动注解通常需要结合 @Configuration 注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @Import 注解后则进行处理,对于 @Import 注解的值有三种情况:
- 该 Class 对象实现了
ImportSelector
接口,调用它的selectImports(..)
方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理- 该 Class 对象实现了 DeferredImportSelector 接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持 @Order 排序
- 该 Class 对象实现了
ImportBeanDefinitionRegistrar
接口,会调用它的registerBeanDefinitions(..)
方法,自定义地往BeanDefinitionRegistry
注册中心注册 BeanDefinition(Bean 的前身) - 该 Class 对象是一个 @Configuration 配置类,会将这个类作为一个 Bean 被 Spring IoC 管理
这里的 @EnableAutoConfiguration 自动配置模块驱动注解,通过 @Import 导入 AutoConfigurationImportSelector 这个类(实现了 DeferredImportSelector 接口)来驱动 Spring Boot 的自动配置模块,下面会进行分析
@AutoConfigurationPackage 注解
@EnableAutoConfiguration 注解上面还有一个 @AutoConfigurationPackage 元注解,它的作用就是注册一个 Bean,保存了当前注解标注的类所在包路径
1 |
|
同样这里使用了 @Import 注解来实现的,对应的是一个 AutoConfigurationPackages.Registrar 内部类,如下:
1 | static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { |
AutoConfigurationImportSelector类
AutoConfigurationImportSelector,实现了 DeferredImportSelector 接口,是 @EnableAutoConfiguration 注解驱动自动配置模块的核心类
selectImports 方法
selectImports(AnnotationMetadata) 方法,返回需要注入的 Bean 的类名称
1 |
|
上面第 2 步调用的方法:
1 | final class AutoConfigurationMetadataLoader { |
这里补充解释一下spring-autoconfigure-metadata.properties作用:
Spring Boot 做了一个优化,通过自己提供的工具,在编译阶段将自动配置类的一些注解信息保存在一个 properties文件中,这样一来,在你启动应用的过程中,就可以直接读取该文件中的信息,提前过滤掉一些自动配置类,相比于每次都去解析它们所有的注解,性能提升不少
getAutoConfigurationEntry 方法
getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata) 方法,返回符合条件的自动配置类,如下:
1 | protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, |
这个过程解释如下:
- 如果通过 spring.boot.enableautoconfiguration 配置关闭了自动配置功能,则返回一个“空”的对象
- 获取 @EnableAutoConfiguration 注解的配置信息
- 从所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解对应的类(需要自动配置的类),保存在 configurations 集合中
1
2
3
4
5
6
7
8protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 从所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解对应的类(需要自动配置的类)
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// 如果为空则抛出异常
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
} - 对所有的自动配置类进行去重
1
2
3protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
} - 获取需要排除的自动配置类,可通过 @EnableAutoConfiguration 注解的 exclude 和 excludeName 配置,也可以通过 spring.autoconfigure.exclude 配置
- 处理 exclusions 中特殊的类名称,保证能够排除它
- 从 configurations 中将 exclusions 需要排除的自动配置类移除
- 调用 filter(..) 方法, 目的就是过滤掉一些不符合 Condition 条件的自动配置类,和在 1. selectImports 方法 小节中讲到的性能优化有关哦
- 从 META-INF/spring.factories 找到所有的 AutoConfigurationImportListener 事件监听器,触发每个监听器去处理 AutoConfigurationImportEvent 事件,该事件中包含了 configurations 和 exclusions
Spring Boot 中配置了一个监听器,目的就是将 configurations 和 exclusions 保存至 AutoConfigurationImportEvent 对象中,并注册到 IoC 容器中,名称为 autoConfigurationReport,这样一来我们可以注入这个 Bean 获取到自动配置类信息 - 将所有的自动配置类封装成一个 AutoConfigurationEntry 对象,并返回
AutoConfigureAnnotationProcessor
AutoConfigureAnnotationProcessor,Spring Boot 的 spring-boot-autoconfigure-processor 工具模块中的自动配置类的注解处理器,在编译阶段扫描自动配置类的注解元信息,并将他们保存至一个 properties 文件中
1 |
|
其中:
AbstractProcessor 是 JDK 1.6 引入的一个抽象类,支持在编译阶段进行处理,在构造器中做了以下事情:
- 创建一个 Map 集合
- 将指定注解的简称和全称之间的对应关系保存至第 1 步创建的 Map 中
1
2
3
4
5
6
7
8
9protected void addAnnotations(Map<String, String> annotations) {
annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
annotations.put("ConditionalOnBean", "org.springframework.boot.autoconfigure.condition.ConditionalOnBean");
annotations.put("ConditionalOnSingleCandidate", "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate");
annotations.put("ConditionalOnWebApplication", "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication");
annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore");
annotations.put("AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureAfter");
annotations.put("AutoConfigureOrder", "org.springframework.boot.autoconfigure.AutoConfigureOrder");
} - 将 1.1 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 annotations
- 创建一个 Map 集合
- 将指定注解的简称和对应的 ValueExtractor 对象保存至第 2 步创建的 Map 中
1
2
3
4
5
6
7
8
9private void addValueExtractors(Map<String, ValueExtractor> attributes) {
attributes.put("ConditionalOnClass", new OnClassConditionValueExtractor());
attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
attributes.put("ConditionalOnSingleCandidate", new OnBeanConditionValueExtractor());
attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type"));
attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name"));
attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name"));
attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value"));
} - 将 2.1 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 valueExtractors
process 方法
1 |
|
process 重载方法
1 | private void process(RoundEnvironment roundEnv, String propertyKey, String annotationName) { |
processElement 方法
1 | private void processElement(Element element, String propertyKey, String annotationName) { |
小结
@EnableAutoConfiguration 注解的实现原理并不复杂,借助于 @Import 注解,从所有 META-INF/spring.factories 文件中找到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值。
会将这些自动配置类作为一个 Bean 尝试注入到 Spring IoC 容器中,注入的时候 Spring 会通过 @Conditional 注解判断是否符合条件,因为并不是所有的自动配置类都满足条件。当然,Spring Boot 对 @Conditional 注解进行了扩展,例如 @ConditionalOnClass 可以指定必须存在哪些 Class 对象才注入这个 Bean。
Spring Boot 会借助 spring-boot-autoconfigure-processor 工具模块在编译阶段将自己的自动配置类的注解元信息保存至一个 properties 文件中,避免每次启动应用都要去解析这么多自动配置类上面的注解。同时会通过几个过滤器根据这个 properties 文件过滤掉一些自动配置类。
Condition接口扩展
Condition 演进史-先从Profile看起
在 Spring 3.1 的版本,为了满足不同环境注册不同的 Bean ,引入了 @Profile 注解。例如:
1 |
|
Spring 3.1.x 的 @Profile 注解如下:
1 |
|
最开始 @Profile 注解并没有结合 @Conditional 注解一起使用,而是在后续版本才引入的
Condition 的出现
在 Spring 4.0 的版本,出现了 Condition 功能,体现在 org.springframework.context.annotation.Condition 接口,如下:
1 |
|
函数式接口,只有一个 matches(..) 方法,判断是否匹配,从入参中可以知道,它是和注解配合一起实现 Condition 功能的,也就是 @Conditional 注解,如下:
1 |
|
随之 @Profile 注解也进行了修改,和 @Conditional 注解配合使用
Spring 5.1.x 的 @Profile 注解如下:
1 |
|
这里指定的的 Condition 实现类是 ProfileCondition,如下:
1 | class ProfileCondition implements Condition { |
逻辑很简单,从当前 Spring 应用上下文的 Environment 中判断 @Profile 指定的环境是否被激活,被激活了表示匹配成功,则注入对应的 Bean,否则,不进行操作
但是 Spring 本身提供的 Condition 实现类不多,只有一个 ProfileCondition 对象
SpringBootCondition 的完善
Spring Boot 为了满足更加丰富的 Condition 场景,对 Spring 的 Condition 接口进行了扩展,提供更多的实现类,如下:
上面仅列出了部分 SpringBootCondition 的子类,同时这些子类与对应的注解配置一起使用
- @ConditionalOnClass:必须都存在指定的 Class 对象们
- @ConditionalOnMissingClass:指定的 Class 对象们必须都不存在
- @ConditionalOnBean:必须都存在指定的 Bean 们
- @ConditionalOnMissingBean:指定的 Bean 们必须都不存在
- @ConditionalOnSingleCandidate:必须存在指定的 Bean
- @ConditionalOnProperty:指定的属性是否有指定的值
- @ConditionalOnWebApplication:当前的 WEB 应用类型是否在指定的范围内(ANY、SERVLET、REACTIVE)
- @ConditionalOnNotWebApplication:不是 WEB 应用类型
上面列出了 Spring Boot 中常见的几种 @ConditionXxx 注解,他们都配合 @Conditional 注解与对应的 Condition 实现类一起使用,提供了非常丰富的 Condition 场景
Condition 在哪生效?
- 通过 @Component 注解(及派生注解)标注的 Bean
- @Configuration 标注的配置类中的 @Bean 标注的方法 Bean
普通 Bean
第一种情况是在 Spring 扫描指定路径下的 .class 文件解析成对应的 BeanDefinition(Bean 的前身)时,会根据 @Conditional 注解判断是否符合条件,如下:
1 | // ClassPathBeanDefinitionScanner.java |
配置类
第二种情况是 Spring 会对 配置类进行处理,扫描到带有 @Bean 注解的方法,尝试解析成 BeanDefinition(Bean 的前身)时,会根据 @Conditional 注解判断是否符合条件,如下:
1 | // ConfigurationClassBeanDefinitionReader.java |
上面只是简单的提一下,可以看到会通过 TrackedConditionEvaluator 计算器进行计算,判断是否满足条件
这里提一下,对于 @Bean 标注的方法,会得到 CGLIB 的提升,也就是返回的是一个代理对象,设置一个拦截器专门对 @Bean 方法进行拦截处理,通过依赖查找的方式从 IoC 容器中获取 Bean 对象,如果是单例 Bean,那么每次都是返回同一个对象,所以当主动调用这个方法时获取到的都是同一个对象。