系列导航:本文是《Spring框架第一性原理》系列的第4篇
- 第1篇:为什么我们需要Spring框架?
- 第2篇:IoC容器:从手动new到自动装配的演进
- 第3篇:AOP:从代码重复到面向切面编程
- 第4篇:Spring Boot:约定优于配置的威力(本文)
引子:配置地狱的噩梦
场景重现:创建一个Spring Web应用(传统方式)
想象你是2013年的Java开发者,接到任务:创建一个简单的RESTful API服务。使用传统Spring框架,你需要经历以下"噩梦":
第一步:手动配置依赖(pom.xml,约50行)
<!-- 1. Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- 2. Jackson JSON依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.9</version>
</dependency>
<!-- 3. Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 4. 日志依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 问题:-->
<!-- ❌ 依赖繁多:手动引入10+个依赖 -->
<!-- ❌ 版本冲突:spring-core必须和spring-webmvc版本一致 -->
<!-- ❌ 依赖遗漏:忘记jackson导致JSON序列化失败 -->
第二步:配置web.xml(约30行)
<!-- src/main/webapp/WEB-INF/web.xml -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 1. 配置Spring上下文监听器 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- 2. 配置Spring上下文配置文件位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 3. 配置DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 4. 配置Servlet映射 -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 5. 配置字符编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
<!-- 问题:-->
<!-- ❌ XML冗长:30行配置只为启动Web应用 -->
<!-- ❌ 路径硬编码:配置文件路径写死 -->
<!-- ❌ 易出错:一个标签写错就启动失败 -->
第三步:配置applicationContext.xml(约40行)
<!-- src/main/webapp/WEB-INF/applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 1. 启用注解扫描 -->
<context:component-scan base-package="com.example">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 2. 加载properties文件 -->
<context:property-placeholder location="classpath:application.properties"/>
<!-- 3. 配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maximumPoolSize" value="20"/>
</bean>
<!-- 4. 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 5. 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 6. 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
<!-- 问题:-->
<!-- ❌ 配置繁琐:每个Bean都要显式配置 -->
<!-- ❌ 命名空间复杂:xsi:schemaLocation写错就无法启动 -->
<!-- ❌ 重复配置:每个项目都要写相同的配置 -->
第四步:配置dispatcher-servlet.xml(约30行)
<!-- src/main/webapp/WEB-INF/dispatcher-servlet.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 1. 扫描Controller -->
<context:component-scan base-package="com.example.controller"/>
<!-- 2. 启用Spring MVC注解 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 配置JSON转换器 -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 3. 配置静态资源处理 -->
<mvc:default-servlet-handler/>
<!-- 4. 配置拦截器 -->
<mvc:interceptors>
<bean class="com.example.interceptor.LogInterceptor"/>
</mvc:interceptors>
</beans>
第五步:配置应用(application.properties)
# 数据库配置
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
# 日志配置
logging.level.root=INFO
logging.level.com.example=DEBUG
第六步:编写业务代码
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
}
// Service
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public User getUser(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new Object[]{id},
new BeanPropertyRowMapper<>(User.class)
);
}
}
第七步:部署到Tomcat
# 1. 打包成WAR文件
mvn clean package
# 2. 将WAR文件复制到Tomcat webapps目录
cp target/myapp.war /usr/local/tomcat/webapps/
# 3. 启动Tomcat
/usr/local/tomcat/bin/startup.sh
# 4. 访问应用
curl http://localhost:8080/myapp/api/users/1
统计:传统Spring Web应用的复杂度
| 维度 | 数量/行数 | 说明 |
|---|---|---|
| 配置文件数量 | 5个 | pom.xml、web.xml、applicationContext.xml、dispatcher-servlet.xml、application.properties |
| 配置代码行数 | 约200行 | 纯配置代码,不含业务逻辑 |
| 依赖数量 | 15+个 | 需要手动管理版本 |
| 外部容器依赖 | Tomcat | 必须部署到Servlet容器 |
| 启动时间 | 约30秒 | Tomcat启动 + Spring初始化 |
| 学习曲线 | 陡峭 | XML配置、命名空间、Schema |
问题总结
传统Spring Web应用的五大痛点:
1. 配置地狱(Configuration Hell)
├─ XML配置文件冗长(200+行)
├─ 命名空间复杂(记不住schemaLocation)
├─ 配置分散(5个配置文件)
└─ 重复配置(每个项目都要写)
2. 依赖管理噩梦(Dependency Hell)
├─ 依赖繁多(15+个Maven依赖)
├─ 版本冲突(Spring各模块版本必须一致)
├─ 依赖遗漏(忘记引入导致运行时错误)
└─ 传递依赖混乱(A依赖B,B依赖C的不同版本)
3. 容器部署复杂
├─ 必须依赖外部容器(Tomcat、Jetty)
├─ 打包成WAR文件
├─ 部署步骤繁琐
└─ 环境不一致(开发环境 vs 生产环境)
4. 启动速度慢
├─ Tomcat启动耗时
├─ Spring容器初始化耗时
└─ 总启动时间:30秒+
5. 学习成本高
├─ XML配置语法
├─ Spring命名空间
├─ 容器部署知识
└─ 调试困难(配置错误定位难)
对比:Spring Boot的"魔法"
同样的需求,Spring Boot只需要这样:
第一步:创建项目(使用Spring Initializr)
访问 https://start.spring.io/,选择:
- Project: Maven
- Language: Java
- Spring Boot: 2.7.x
- Dependencies: Spring Web, Spring Data JDBC, MySQL Driver
点击"Generate",下载项目压缩包,解压即可。
耗时:30秒
第二步:配置应用(application.yml)
# application.yml(仅10行)
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
server:
port: 8080
logging:
level:
com.example: DEBUG
第三步:编写业务代码(完全相同)
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
}
// Service
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public User getUser(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new Object[]{id},
new BeanPropertyRowMapper<>(User.class)
);
}
}
// 启动类(新增,只需3行)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第四步:启动应用
# 方式1:IDE直接运行main方法
# 方式2:命令行启动
mvn spring-boot:run
# 方式3:打包成JAR,独立运行
mvn clean package
java -jar target/myapp.jar
# 访问应用
curl http://localhost:8080/api/users/1
# 启动时间:3-5秒
对比表格:传统Spring vs Spring Boot
| 维度 | 传统Spring | Spring Boot | 差异 |
|---|---|---|---|
| 配置文件数量 | 5个 | 1个(application.yml) | 减少80% |
| 配置代码行数 | 约200行 | 约10行 | 减少95% |
| 依赖管理 | 手动引入15+个依赖 | spring-boot-starter-web(1个) | 自动管理 |
| 外部容器 | 必须(Tomcat等) | 可选(内嵌Tomcat) | 独立运行 |
| 打包方式 | WAR文件 | JAR文件 | 更简单 |
| 启动时间 | 约30秒 | 3-5秒 | 快6-10倍 |
| 部署步骤 | 复杂(4步) | 简单(1步:java -jar) | 简化 |
| 学习成本 | 高(XML、容器) | 低(约定优于配置) | 降低70% |
| 开发效率 | 低 | 高 | 提升5倍+ |
Spring Boot的"零配置"体验
Spring Boot如何做到零配置?
1. 自动配置(Auto-Configuration)
├─ 检测到spring-boot-starter-web → 自动配置DispatcherServlet
├─ 检测到MySQL驱动 → 自动配置DataSource
├─ 检测到JdbcTemplate → 自动配置事务管理器
└─ 所有配置自动完成,开发者无需关心
2. Starter机制
├─ spring-boot-starter-web → 自动引入15+个依赖
├─ 依赖版本自动管理(Spring Boot统一管理)
├─ 依赖传递自动处理
└─ 一个Starter = 一套完整解决方案
3. 内嵌容器
├─ Tomcat内嵌在应用中
├─ 打包成可执行JAR
├─ java -jar一键启动
└─ 环境一致(开发 = 生产)
4. 约定优于配置(Convention Over Configuration)
├─ 默认端口:8080
├─ 默认配置文件:application.yml/properties
├─ 默认静态资源目录:static/
└─ 开发者只需关注业务逻辑
第一性原理:为什么Spring Boot能做到这些?
核心问题拆解
传统Spring的配置复杂度本质上源于三个问题:
问题1:依赖管理复杂
└─ 根本原因:需要手动声明每个依赖及其版本
└─ Spring Boot解决方案:Starter机制(依赖聚合 + 版本管理)
问题2:配置繁琐
└─ 根本原因:需要显式配置每个Bean和组件
└─ Spring Boot解决方案:自动配置(条件装配 + 约定优于配置)
问题3:部署复杂
└─ 根本原因:依赖外部容器(Tomcat等)
└─ Spring Boot解决方案:内嵌容器(可执行JAR)
Spring Boot的三大核心机制
机制1:Starter机制(依赖管理的抽象)
问题:传统Spring需要手动引入众多依赖,容易遗漏和版本冲突
Starter的本质:依赖的聚合 + 版本的统一管理
<!-- 传统Spring:手动引入15+个依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- ...还有13个依赖... -->
<!-- Spring Boot:只需1个Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本由spring-boot-starter-parent统一管理,无需指定 -->
</dependency>
Starter的内部结构:
<!-- spring-boot-starter-web的pom.xml内容 -->
<dependencies>
<!-- 1. Spring Boot核心Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 2. Spring Boot JSON Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<!-- 3. Spring Boot Tomcat Starter(内嵌容器)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!-- 4. Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
<!-- Starter机制的价值:-->
<!-- ✅ 依赖聚合:1个Starter = 完整的依赖集合 -->
<!-- ✅ 版本管理:Spring Boot统一管理所有依赖版本 -->
<!-- ✅ 开箱即用:引入Starter后立即可用 -->
版本管理的秘密:
<!-- 1. 项目继承spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.10</version>
</parent>
<!-- 2. spring-boot-starter-parent继承spring-boot-dependencies -->
<!-- spring-boot-dependencies定义了300+个依赖的版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.25</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.25</version>
</dependency>
<!-- ...还有298个依赖... -->
</dependencies>
</dependencyManagement>
<!-- 3. 开发者只需引入Starter,无需指定版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 版本自动继承,无需指定 -->
</dependency>
<!-- 结果:-->
<!-- ✅ 无版本冲突:Spring Boot保证所有依赖版本兼容 -->
<!-- ✅ 简化配置:开发者无需关心版本号 -->
<!-- ✅ 统一升级:升级Spring Boot版本,所有依赖自动升级 -->
机制2:自动配置(配置的自动化)
问题:传统Spring需要显式配置每个Bean(DataSource、JdbcTemplate、TransactionManager等)
自动配置的本质:条件判断 + Bean自动注册
// 传统Spring:手动配置DataSource
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
}
// Spring Boot:自动配置DataSource
// 开发者什么都不用写,Spring Boot自动完成
// 只需在application.yml配置数据库连接信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
自动配置的实现原理:
// Spring Boot的DataSource自动配置类
@Configuration
@ConditionalOnClass(DataSource.class) // 条件1:类路径存在DataSource类
@ConditionalOnMissingBean(DataSource.class) // 条件2:容器中不存在DataSource Bean
@EnableConfigurationProperties(DataSourceProperties.class) // 读取配置文件
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
// 根据配置文件创建DataSource
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(properties.getUrl());
ds.setUsername(properties.getUsername());
ds.setPassword(properties.getPassword());
return ds;
}
}
// 自动配置的工作流程:
1. 检测条件:类路径是否存在DataSource类?
└─ 是 → 继续
└─ 否 → 跳过此自动配置
2. 检测条件:容器中是否已存在DataSource Bean?
└─ 否 → 自动创建
└─ 是 → 跳过(尊重开发者自定义)
3. 读取配置:从application.yml读取spring.datasource.*配置
4. 创建Bean:根据配置自动创建DataSource Bean
// 结果:
// ✅ 开发者无需写配置类
// ✅ 配置外部化(application.yml)
// ✅ 支持自定义(开发者可以覆盖)
条件装配的完整体系:
// Spring Boot提供的条件注解
@ConditionalOnClass // 类路径存在指定类
@ConditionalOnMissingClass // 类路径不存在指定类
@ConditionalOnBean // 容器存在指定Bean
@ConditionalOnMissingBean // 容器不存在指定Bean
@ConditionalOnProperty // 配置文件存在指定属性
@ConditionalOnResource // 类路径存在指定资源文件
@ConditionalOnWebApplication // Web应用环境
@ConditionalOnNotWebApplication // 非Web应用环境
@ConditionalOnExpression // SpEL表达式为true
@ConditionalOnJava // Java版本匹配
// 示例:只有在Web环境且存在Tomcat类时才配置
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public class EmbeddedTomcatConfiguration {
// 配置内嵌Tomcat
}
机制3:内嵌容器(部署的简化)
问题:传统Spring必须部署到外部容器(Tomcat、Jetty等)
内嵌容器的本质:将容器作为应用的一部分打包
// Spring Boot启动流程
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
// SpringApplication.run()内部做了什么?
public ConfigurableApplicationContext run(String... args) {
// 1. 创建Spring容器
context = createApplicationContext();
// 2. 刷新容器(加载Bean)
refreshContext(context);
// 3. 启动内嵌Tomcat(自动配置)
// TomcatServletWebServerFactory.getWebServer()
// └─ 创建Tomcat实例
// └─ 配置端口、上下文路径
// └─ 注册DispatcherServlet
// └─ 启动Tomcat
return context;
}
// 结果:
// ✅ 无需外部容器:Tomcat嵌入在应用中
// ✅ 打包成JAR:可执行JAR(java -jar启动)
// ✅ 环境一致:开发环境和生产环境完全相同
内嵌容器的实现原理:
// 1. Spring Boot检测到spring-boot-starter-web
// → 自动配置内嵌Tomcat
// 2. 自动配置类:ServletWebServerFactoryAutoConfiguration
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(ServletRequest.class)
public class ServletWebServerFactoryAutoConfiguration {
@Configuration
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
}
// 3. TomcatServletWebServerFactory创建Tomcat实例
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 创建Tomcat实例
Tomcat tomcat = new Tomcat();
// 配置端口
Connector connector = new Connector();
connector.setPort(getPort()); // 默认8080
tomcat.getService().addConnector(connector);
// 配置上下文
Context context = tomcat.addContext("", getDocumentRoot());
// 注册DispatcherServlet
Wrapper dispatcherServlet = context.createWrapper();
dispatcherServlet.setName("dispatcherServlet");
dispatcherServlet.setServletClass(DispatcherServlet.class.getName());
context.addChild(dispatcherServlet);
// 启动Tomcat
tomcat.start();
return new TomcatWebServer(tomcat);
}
// 结果:
// ✅ Tomcat作为应用的一部分启动
// ✅ 无需部署到外部容器
// ✅ 应用和容器打包在一起
深度剖析:@SpringBootApplication注解
一个注解,三大功能
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
这个看似简单的注解,背后是三个注解的组合:
// @SpringBootApplication源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 功能1:标识这是Spring Boot配置类
@EnableAutoConfiguration // 功能2:启用自动配置
@ComponentScan // 功能3:启用组件扫描
public @interface SpringBootApplication {
// ...
}
功能1:@SpringBootConfiguration
// @SpringBootConfiguration源码
@Configuration
public @interface SpringBootConfiguration {
// 本质上就是@Configuration
// 标识这是一个Spring配置类
}
// 等价于
@Configuration
public class Application {
// 可以在这里定义Bean
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
功能2:@EnableAutoConfiguration(核心)
这是Spring Boot自动配置的核心,让我们深入剖析:
// @EnableAutoConfiguration源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 作用1:自动配置包
@Import(AutoConfigurationImportSelector.class) // 作用2:导入自动配置选择器
public @interface EnableAutoConfiguration {
// ...
}
作用1:@AutoConfigurationPackage
// @AutoConfigurationPackage源码
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
// 将主配置类所在包及其子包注册到Spring容器
}
// Registrar的作用:
static class Registrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取主配置类所在包
// 例如:com.example.Application → com.example
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
}
// 结果:
// ✅ Spring Boot知道从哪里扫描组件
// ✅ 默认扫描主配置类所在包及子包
// ✅ 无需手动配置@ComponentScan的basePackages
作用2:AutoConfigurationImportSelector(自动配置的核心)
这是Spring Boot自动配置的灵魂,让我们逐步拆解:
// AutoConfigurationImportSelector的核心方法
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 检查自动配置是否启用
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 2. 加载自动配置类(核心)
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(annotationMetadata);
// 3. 返回要导入的自动配置类名称
return StringUtils.toStringArray(
autoConfigurationEntry.getConfigurations()
);
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
AnnotationMetadata annotationMetadata) {
// 1. 加载所有自动配置类(从spring.factories文件)
List<String> configurations = getCandidateConfigurations(annotationMetadata);
// 例如:加载到130+个自动配置类
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 排除不需要的(通过exclude属性)
Set<String> exclusions = getExclusions(annotationMetadata);
configurations.removeAll(exclusions);
// 4. 过滤(通过条件注解@Conditional*)
configurations = getConfigurationClassFilter().filter(configurations);
// 例如:130+个 → 30+个(只有满足条件的才会被加载)
// 5. 触发自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
关键:从哪里加载自动配置类?
// getCandidateConfigurations方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata) {
// 从META-INF/spring.factories文件加载
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), // EnableAutoConfiguration.class
getBeanClassLoader()
);
return configurations;
}
// SpringFactoriesLoader.loadFactoryNames的作用:
// 扫描所有jar包的META-INF/spring.factories文件
// 读取org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项
spring.factories文件示例:
# spring-boot-autoconfigure-2.7.10.jar中的spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
# ...还有100+个自动配置类
自动配置的完整流程图:
@SpringBootApplication
↓
@EnableAutoConfiguration
↓
AutoConfigurationImportSelector
↓
扫描所有jar包的META-INF/spring.factories
↓
读取EnableAutoConfiguration配置项(130+个自动配置类)
↓
去重、排除
↓
过滤(通过@Conditional*条件注解)
↓
最终导入30+个自动配置类(满足条件的)
↓
每个自动配置类创建对应的Bean
↓
应用启动成功,所有组件自动配置完成
示例:DataSourceAutoConfiguration
↓
@ConditionalOnClass(DataSource.class) // 检查类路径是否存在DataSource
↓ 是
@ConditionalOnMissingBean(DataSource.class) // 检查容器是否已存在DataSource Bean
↓ 否(不存在)
@EnableConfigurationProperties(DataSourceProperties.class) // 读取spring.datasource.*配置
↓
创建DataSource Bean(根据配置)
↓
注册到Spring容器
功能3:@ComponentScan
// @ComponentScan默认行为
@ComponentScan
public class Application {
// 默认扫描主配置类所在包及其子包
// 例如:com.example.Application
// → 扫描 com.example 及其子包
}
// 可以自定义扫描范围
@SpringBootApplication
@ComponentScan(basePackages = {"com.example", "com.other"})
public class Application {
// 扫描多个包
}
自动配置的条件装配体系
条件注解的作用
Spring Boot的自动配置依赖于一套完整的条件注解体系,只有满足条件的配置才会生效。
常用条件注解详解
1. @ConditionalOnClass / @ConditionalOnMissingClass
// 示例:只有当类路径存在DataSource类时才配置
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
// 使用场景:
// ✅ 引入了spring-boot-starter-jdbc → 类路径存在DataSource → 自动配置生效
// ❌ 没有引入JDBC相关依赖 → 类路径不存在DataSource → 自动配置跳过
// @ConditionalOnMissingClass(反向条件)
@Configuration
@ConditionalOnMissingClass("com.zaxxer.hikari.HikariDataSource")
public class BasicDataSourceConfiguration {
// 只有当HikariDataSource不存在时才使用BasicDataSource
}
2. @ConditionalOnBean / @ConditionalOnMissingBean
// 示例:只有当容器中不存在DataSource Bean时才自动配置
@Configuration
@ConditionalOnMissingBean(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
// 使用场景:
// 场景A:开发者没有自定义DataSource
// → 容器中不存在DataSource Bean
// → 自动配置生效,创建默认DataSource
// 场景B:开发者自定义了DataSource
@Configuration
public class MyDataSourceConfig {
@Bean
public DataSource dataSource() {
// 自定义DataSource
return new MyCustomDataSource();
}
}
// → 容器中已存在DataSource Bean
// → 自动配置跳过,尊重开发者自定义
// 结果:
// ✅ 支持自定义覆盖
// ✅ 默认提供标准实现
3. @ConditionalOnProperty
// 示例:只有当配置文件中存在指定属性时才生效
@Configuration
@ConditionalOnProperty(
name = "spring.datasource.enabled", // 属性名
havingValue = "true", // 期望值
matchIfMissing = true // 属性不存在时的默认行为
)
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
// 使用场景:
// 场景A:application.yml中配置了spring.datasource.enabled=true
// → 自动配置生效
// 场景B:application.yml中配置了spring.datasource.enabled=false
// → 自动配置跳过
// 场景C:application.yml中没有配置spring.datasource.enabled
// → matchIfMissing=true → 自动配置生效(默认行为)
// 实战案例:控制功能开关
@Configuration
@ConditionalOnProperty(name = "feature.cache.enabled", havingValue = "true")
public class CacheAutoConfiguration {
// 只有在配置文件中开启缓存功能时才自动配置
}
4. @ConditionalOnWebApplication / @ConditionalOnNotWebApplication
// 示例:只有在Web应用环境才配置
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
public class WebMvcAutoConfiguration {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}
// 使用场景:
// ✅ Web应用(引入了spring-boot-starter-web)→ 自动配置生效
// ❌ 非Web应用(如批处理任务)→ 自动配置跳过
// @ConditionalOnNotWebApplication(反向条件)
@Configuration
@ConditionalOnNotWebApplication
public class BatchAutoConfiguration {
// 只有在非Web应用才配置批处理相关Bean
}
5. @ConditionalOnResource
// 示例:只有当类路径存在指定资源文件时才配置
@Configuration
@ConditionalOnResource(resources = "classpath:custom-config.xml")
public class CustomConfigAutoConfiguration {
@Bean
public CustomConfig customConfig() {
// 加载custom-config.xml文件
return new CustomConfig();
}
}
// 使用场景:
// ✅ 类路径存在custom-config.xml → 自动配置生效
// ❌ 类路径不存在custom-config.xml → 自动配置跳过
6. @ConditionalOnExpression
// 示例:使用SpEL表达式控制条件
@Configuration
@ConditionalOnExpression("${spring.datasource.enabled:true} && '${spring.profiles.active}' == 'prod'")
public class ProductionDataSourceConfiguration {
// 只有在生产环境且数据源启用时才配置
}
// 使用场景:
// ✅ 复杂条件判断
// ✅ 组合多个属性
// ❌ 性能略低(SpEL表达式解析)
7. @ConditionalOnJava
// 示例:只有在指定Java版本才配置
@Configuration
@ConditionalOnJava(JavaVersion.EIGHT)
public class Java8Configuration {
// 只有在Java 8环境才配置
}
@Configuration
@ConditionalOnJava(range = Range.EQUAL_OR_NEWER, value = JavaVersion.ELEVEN)
public class Java11Configuration {
// 只有在Java 11及以上版本才配置
}
条件注解的组合使用
// 实战案例:Redis自动配置
@Configuration
@ConditionalOnClass(RedisOperations.class) // 条件1:类路径存在Redis类
@EnableConfigurationProperties(RedisProperties.class) // 读取配置
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 条件2:容器中不存在redisTemplate
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) // 条件3:存在唯一的连接工厂
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Configuration
@ConditionalOnClass(StringRedisTemplate.class) // 条件4:类路径存在StringRedisTemplate
protected static class StringRedisTemplateConfiguration {
@Bean
@ConditionalOnMissingBean // 条件5:容器中不存在StringRedisTemplate
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
}
// 条件判断流程:
1. 检查类路径是否存在RedisOperations → 是 → 继续
2. 检查容器中是否已存在redisTemplate → 否 → 创建
3. 检查是否存在唯一的RedisConnectionFactory → 是 → 继续
4. 创建RedisTemplate Bean
5. 检查类路径是否存在StringRedisTemplate → 是 → 继续
6. 检查容器中是否已存在StringRedisTemplate → 否 → 创建
7. 创建StringRedisTemplate Bean
// 结果:
// ✅ 自动配置Redis相关Bean
// ✅ 支持开发者自定义覆盖
// ✅ 只有在满足所有条件时才配置
配置优先级与外部化配置
配置文件的加载顺序
Spring Boot支持多种配置方式,优先级从高到低:
优先级(从高到低):
1. 命令行参数
java -jar app.jar --server.port=9000
2. Java系统属性(System.getProperties())
java -Dserver.port=9000 -jar app.jar
3. 操作系统环境变量
export SERVER_PORT=9000
4. application-{profile}.yml(外部配置文件)
/config/application-prod.yml(jar包同级config目录)
/application-prod.yml(jar包同级目录)
5. application-{profile}.yml(内部配置文件)
classpath:/config/application-prod.yml
classpath:/application-prod.yml
6. application.yml(外部配置文件)
/config/application.yml
/application.yml
7. application.yml(内部配置文件)
classpath:/config/application.yml
classpath:/application.yml
8. @PropertySource指定的配置文件
9. 默认值(SpringApplication.setDefaultProperties)
配置文件示例
# application.yml(默认配置)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
logging:
level:
root: INFO
# application-dev.yml(开发环境配置)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb_dev
username: root
password: dev123456
logging:
level:
root: DEBUG
com.example: TRACE
# application-prod.yml(生产环境配置)
server:
port: 80
spring:
datasource:
url: jdbc:mysql://prod-db:3306/mydb_prod
username: app_user
password: ${DB_PASSWORD} # 从环境变量读取
logging:
level:
root: WARN
com.example: INFO
激活Profile
# 方式1:命令行参数
java -jar app.jar --spring.profiles.active=prod
# 方式2:环境变量
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar
# 方式3:配置文件
# application.yml
spring:
profiles:
active: prod
配置优先级实战示例
# application.yml
server:
port: 8080 # 默认端口
# application-prod.yml
server:
port: 80 # 生产环境端口
# 场景1:没有任何额外配置
java -jar app.jar
# 结果:端口=8080(application.yml)
# 场景2:激活prod环境
java -jar app.jar --spring.profiles.active=prod
# 结果:端口=80(application-prod.yml覆盖application.yml)
# 场景3:命令行指定端口
java -jar app.jar --spring.profiles.active=prod --server.port=9000
# 结果:端口=9000(命令行参数优先级最高)
# 场景4:环境变量 + 命令行
export SERVER_PORT=7000
java -jar app.jar --server.port=9000
# 结果:端口=9000(命令行参数优先级高于环境变量)
实战:手写一个自定义Starter
让我们通过手写一个Starter来深入理解Spring Boot的Starter机制。
需求:自定义短信发送Starter
目标:创建spring-boot-starter-sms,让开发者只需引入依赖和配置,即可使用短信发送功能。
第一步:创建Starter项目结构
spring-boot-starter-sms/
├── pom.xml
└── src/main/java/com/example/sms/
├── SmsProperties.java # 配置属性类
├── SmsService.java # 核心服务类
└── SmsAutoConfiguration.java # 自动配置类
└── src/main/resources/
└── META-INF/
└── spring.factories # 自动配置注册文件
第二步:pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-boot-starter-sms</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Spring Boot SMS Starter</name>
<description>Spring Boot Starter for SMS</description>
<dependencies>
<!-- 1. Spring Boot自动配置依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.7.10</version>
</dependency>
<!-- 2. 配置元数据生成(可选,用于IDE提示)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.10</version>
<optional>true</optional>
</dependency>
<!-- 3. 短信SDK依赖(示例:阿里云短信)-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.6.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.2.1</version>
</dependency>
</dependencies>
</project>
第三步:配置属性类(SmsProperties)
package com.example.sms;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 短信配置属性类
*
* 对应配置文件中的sms.*配置项
*/
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
/**
* 是否启用短信功能
*/
private boolean enabled = true;
/**
* 短信服务商(aliyun、tencent等)
*/
private String provider = "aliyun";
/**
* AccessKey ID
*/
private String accessKeyId;
/**
* AccessKey Secret
*/
private String accessKeySecret;
/**
* 短信签名
*/
private String signName;
/**
* 短信模板编码
*/
private String templateCode;
/**
* 区域(如cn-hangzhou)
*/
private String regionId = "cn-hangzhou";
/**
* 连接超时时间(毫秒)
*/
private int connectTimeout = 10000;
/**
* 读取超时时间(毫秒)
*/
private int readTimeout = 10000;
// getters and setters
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getProvider() {
return provider;
}
public void setProvider(String provider) {
this.provider = provider;
}
public String getAccessKeyId() {
return accessKeyId;
}
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public String getAccessKeySecret() {
return accessKeySecret;
}
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
public String getSignName() {
return signName;
}
public void setSignName(String signName) {
this.signName = signName;
}
public String getTemplateCode() {
return templateCode;
}
public void setTemplateCode(String templateCode) {
this.templateCode = templateCode;
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
public int getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
}
第四步:核心服务类(SmsService)
package com.example.sms;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* 短信服务类
*/
public class SmsService {
private static final Logger log = LoggerFactory.getLogger(SmsService.class);
private final SmsProperties properties;
private final IAcsClient acsClient;
public SmsService(SmsProperties properties) {
this.properties = properties;
// 初始化阿里云短信客户端
IClientProfile profile = DefaultProfile.getProfile(
properties.getRegionId(),
properties.getAccessKeyId(),
properties.getAccessKeySecret()
);
this.acsClient = new DefaultAcsClient(profile);
log.info("SmsService initialized with provider: {}", properties.getProvider());
}
/**
* 发送短信
*
* @param phone 手机号
* @param params 模板参数
* @return 是否发送成功
*/
public boolean sendSms(String phone, Map<String, String> params) {
if (!properties.isEnabled()) {
log.warn("SMS service is disabled");
return false;
}
try {
// 构建请求
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(phone);
request.setSignName(properties.getSignName());
request.setTemplateCode(properties.getTemplateCode());
// 模板参数(JSON格式)
if (params != null && !params.isEmpty()) {
StringBuilder json = new StringBuilder("{");
params.forEach((key, value) -> {
json.append("\"").append(key).append("\":\"").append(value).append("\",");
});
json.deleteCharAt(json.length() - 1); // 删除最后一个逗号
json.append("}");
request.setTemplateParam(json.toString());
}
// 发送短信
SendSmsResponse response = acsClient.getAcsResponse(request);
if ("OK".equals(response.getCode())) {
log.info("SMS sent successfully to {}", phone);
return true;
} else {
log.error("Failed to send SMS to {}: {} - {}",
phone, response.getCode(), response.getMessage());
return false;
}
} catch (Exception e) {
log.error("Error sending SMS to {}", phone, e);
return false;
}
}
/**
* 发送验证码短信(简化方法)
*
* @param phone 手机号
* @param code 验证码
* @return 是否发送成功
*/
public boolean sendVerificationCode(String phone, String code) {
return sendSms(phone, Map.of("code", code));
}
}
第五步:自动配置类(SmsAutoConfiguration)
package com.example.sms;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 短信自动配置类
*
* 条件:
* 1. 类路径存在IAcsClient类(引入了阿里云SDK)
* 2. 配置文件中sms.enabled=true(默认为true)
* 3. 容器中不存在SmsService Bean(支持自定义)
*/
@Configuration
@ConditionalOnClass(name = "com.aliyuncs.IAcsClient") // 条件1:类路径存在阿里云SDK
@ConditionalOnProperty(
prefix = "sms",
name = "enabled",
havingValue = "true",
matchIfMissing = true // 配置不存在时默认为true
) // 条件2:配置启用
@EnableConfigurationProperties(SmsProperties.class) // 启用配置属性
public class SmsAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(SmsAutoConfiguration.class);
@Bean
@ConditionalOnMissingBean // 条件3:容器中不存在SmsService(支持自定义)
public SmsService smsService(SmsProperties properties) {
log.info("Auto-configuring SmsService with properties: {}", properties);
return new SmsService(properties);
}
}
第六步:注册自动配置类(spring.factories)
# src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.sms.SmsAutoConfiguration
第七步:配置元数据(可选,用于IDE提示)
// src/main/resources/META-INF/spring-configuration-metadata.json
{
"groups": [
{
"name": "sms",
"type": "com.example.sms.SmsProperties",
"sourceType": "com.example.sms.SmsProperties"
}
],
"properties": [
{
"name": "sms.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable SMS service.",
"defaultValue": true
},
{
"name": "sms.provider",
"type": "java.lang.String",
"description": "SMS provider (aliyun, tencent, etc.).",
"defaultValue": "aliyun"
},
{
"name": "sms.access-key-id",
"type": "java.lang.String",
"description": "AccessKey ID for SMS service."
},
{
"name": "sms.access-key-secret",
"type": "java.lang.String",
"description": "AccessKey Secret for SMS service."
},
{
"name": "sms.sign-name",
"type": "java.lang.String",
"description": "SMS signature name."
},
{
"name": "sms.template-code",
"type": "java.lang.String",
"description": "SMS template code."
},
{
"name": "sms.region-id",
"type": "java.lang.String",
"description": "Region ID (e.g., cn-hangzhou).",
"defaultValue": "cn-hangzhou"
},
{
"name": "sms.connect-timeout",
"type": "java.lang.Integer",
"description": "Connect timeout in milliseconds.",
"defaultValue": 10000
},
{
"name": "sms.read-timeout",
"type": "java.lang.Integer",
"description": "Read timeout in milliseconds.",
"defaultValue": 10000
}
]
}
第八步:发布Starter
# 1. 打包
mvn clean package
# 2. 安装到本地Maven仓库(测试用)
mvn clean install
# 3. 发布到Maven中央仓库(生产用)
mvn clean deploy
第九步:使用自定义Starter
1. 引入依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>spring-boot-starter-sms</artifactId>
<version>1.0.0</version>
</dependency>
2. 配置application.yml
sms:
enabled: true
provider: aliyun
access-key-id: YOUR_ACCESS_KEY_ID
access-key-secret: YOUR_ACCESS_KEY_SECRET
sign-name: 你的签名
template-code: SMS_123456789
region-id: cn-hangzhou
connect-timeout: 10000
read-timeout: 10000
3. 使用SmsService
@RestController
@RequestMapping("/api/sms")
public class SmsController {
@Autowired
private SmsService smsService; // 自动注入
@PostMapping("/send-code")
public String sendVerificationCode(@RequestParam String phone) {
// 生成6位验证码
String code = String.format("%06d", (int)(Math.random() * 1000000));
// 发送短信
boolean success = smsService.sendVerificationCode(phone, code);
if (success) {
return "验证码发送成功";
} else {
return "验证码发送失败";
}
}
}
4. 启动应用
# 启动应用
mvn spring-boot:run
# 测试接口
curl -X POST http://localhost:8080/api/sms/send-code?phone=13800138000
# 输出:
# 验证码发送成功
5. 日志输出
2025-11-03 18:00:00.123 INFO --- [main] c.e.sms.SmsAutoConfiguration : Auto-configuring SmsService with properties: SmsProperties{enabled=true, provider='aliyun', ...}
2025-11-03 18:00:00.456 INFO --- [main] c.e.sms.SmsService : SmsService initialized with provider: aliyun
2025-11-03 18:00:01.789 INFO --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http)
2025-11-03 18:00:01.890 INFO --- [main] c.e.Application : Started Application in 2.345 seconds
2025-11-03 18:05:30.123 INFO --- [http-nio-8080-exec-1] c.e.sms.SmsService : SMS sent successfully to 13800138000
Starter工作流程总结
用户引入spring-boot-starter-sms
↓
Spring Boot启动
↓
扫描所有jar包的META-INF/spring.factories
↓
发现SmsAutoConfiguration
↓
检查条件1:类路径存在IAcsClient?
↓ 是
检查条件2:sms.enabled=true?
↓ 是(默认true)
检查条件3:容器中不存在SmsService?
↓ 是(开发者未自定义)
加载SmsProperties(读取sms.*配置)
↓
创建SmsService Bean
↓
注册到Spring容器
↓
应用启动成功
↓
开发者@Autowired注入SmsService
↓
调用smsService.sendVerificationCode()
↓
短信发送成功
关键点:
✅ 开发者只需引入依赖+配置,无需写任何Spring配置代码
✅ Starter内部完成所有配置工作
✅ 支持自定义覆盖(通过@ConditionalOnMissingBean)
✅ 配置外部化(application.yml)
✅ IDE智能提示(spring-configuration-metadata.json)
Spring Boot最佳实践
1. Starter命名规范
官方Starter:spring-boot-starter-*
├─ spring-boot-starter-web
├─ spring-boot-starter-data-jpa
└─ spring-boot-starter-redis
第三方Starter:*-spring-boot-starter
├─ mybatis-spring-boot-starter
├─ druid-spring-boot-starter
└─ sms-spring-boot-starter(我们自定义的)
命名原则:
✅ 官方Starter:前缀spring-boot-starter
✅ 第三方Starter:后缀spring-boot-starter
❌ 不要违反命名规范
2. 配置属性前缀规范
# ✅ 推荐:使用短小、有意义的前缀
sms:
enabled: true
provider: aliyun
# ✅ 推荐:多级配置使用层级结构
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
# ❌ 不推荐:前缀过长
com.example.project.sms:
enabled: true
# ❌ 不推荐:配置扁平化(不使用层级)
sms-enabled: true
sms-provider: aliyun
3. 条件注解使用建议
// ✅ 推荐:使用@ConditionalOnMissingBean支持自定义
@Bean
@ConditionalOnMissingBean
public SmsService smsService(SmsProperties properties) {
return new SmsService(properties);
}
// ✅ 推荐:使用@ConditionalOnProperty控制功能开关
@Configuration
@ConditionalOnProperty(name = "sms.enabled", havingValue = "true", matchIfMissing = true)
public class SmsAutoConfiguration {
// ...
}
// ✅ 推荐:组合使用多个条件注解
@Configuration
@ConditionalOnClass(IAcsClient.class)
@ConditionalOnProperty(name = "sms.enabled", havingValue = "true")
@ConditionalOnMissingBean(SmsService.class)
public class SmsAutoConfiguration {
// ...
}
// ❌ 不推荐:条件过于宽松(可能导致误配置)
@Configuration
public class SmsAutoConfiguration {
// 没有任何条件,总是会配置
}
4. 配置类设计建议
// ✅ 推荐:配置类分离关注点
@Configuration
public class DataSourceAutoConfiguration {
// 只负责DataSource相关配置
}
@Configuration
public class JdbcTemplateAutoConfiguration {
// 只负责JdbcTemplate相关配置
}
// ❌ 不推荐:所有配置都放在一个类
@Configuration
public class DatabaseAutoConfiguration {
// 混杂了DataSource、JdbcTemplate、事务管理器等
}
5. 日志输出规范
// ✅ 推荐:关键步骤输出INFO日志
public SmsService(SmsProperties properties) {
this.properties = properties;
// ...
log.info("SmsService initialized with provider: {}", properties.getProvider());
}
// ✅ 推荐:配置详情输出DEBUG日志
@Bean
public SmsService smsService(SmsProperties properties) {
log.debug("Creating SmsService with properties: {}", properties);
return new SmsService(properties);
}
// ✅ 推荐:错误情况输出ERROR日志
public boolean sendSms(String phone, Map<String, String> params) {
try {
// ...
} catch (Exception e) {
log.error("Error sending SMS to {}", phone, e);
return false;
}
}
// ❌ 不推荐:过度日志(影响性能)
public boolean sendSms(String phone, Map<String, String> params) {
log.info("开始发送短信");
log.info("手机号: {}", phone);
log.info("参数: {}", params);
log.info("构建请求");
// ...每一步都输出日志
}
总结:Spring Boot的核心价值
从配置地狱到约定优于配置
传统Spring的痛点:
├─ 配置文件冗长(200+行XML)
├─ 依赖管理复杂(15+个依赖,版本冲突)
├─ 部署繁琐(WAR包、外部容器)
├─ 启动缓慢(30秒+)
└─ 学习成本高(XML、容器、部署)
Spring Boot的解决方案:
├─ 自动配置:零配置启动
├─ Starter机制:一键引入完整依赖
├─ 内嵌容器:独立运行JAR
├─ 快速启动:3-5秒
└─ 约定优于配置:降低学习成本
核心价值:
✅ 开发效率提升5倍+
✅ 配置代码减少95%
✅ 部署复杂度降低80%
✅ 学习成本降低70%
Spring Boot三大核心机制总结
1. Starter机制(依赖管理)
问题:手动管理依赖繁琐、容易出错
解决:
└─ 依赖聚合:1个Starter = 完整依赖集合
└─ 版本管理:Spring Boot统一管理版本
└─ 开箱即用:引入即可使用
2. 自动配置(配置自动化)
问题:显式配置每个Bean繁琐、重复
解决:
└─ 条件装配:按需配置(@Conditional*)
└─ 约定优于配置:默认值 + 可覆盖
└─ 配置外部化:application.yml
3. 内嵌容器(部署简化)
问题:依赖外部容器、部署复杂
解决:
└─ 容器内嵌:Tomcat打包进应用
└─ 可执行JAR:java -jar一键启动
└─ 环境一致:开发 = 生产
第一性原理思考
Spring Boot的本质:
└─ 问题:Spring配置复杂
└─ 根本原因1:依赖管理手动化
└─ 解决方案:Starter机制
└─ 根本原因2:Bean配置显式化
└─ 解决方案:自动配置
└─ 根本原因3:部署依赖外部容器
└─ 解决方案:内嵌容器
约定优于配置的哲学:
└─ 提供合理的默认值(80%场景适用)
└─ 支持自定义覆盖(20%特殊场景)
└─ 降低决策负担(开发者专注业务)
给从业者的建议
L1(初级):会用Spring Boot
├─ 掌握常用Starter(web、data-jpa、redis)
├─ 理解application.yml配置
├─ 能独立搭建Spring Boot项目
└─ 熟悉常用注解(@RestController、@Service)
L2(中级):理解原理
├─ 理解自动配置机制
├─ 掌握条件注解体系
├─ 能阅读spring.factories文件
├─ 能自定义Starter
└─ 掌握配置优先级
L3(高级):深度定制
├─ 阅读Spring Boot源码
├─ 能设计复杂的自动配置
├─ 掌握SpringApplication启动流程
├─ 能优化启动性能
└─ 能解决复杂配置问题
学习路径:
从使用入手 → 理解原理 → 深度定制
结语
Spring Boot通过"约定优于配置"的设计哲学,将开发者从繁琐的配置工作中解放出来,让我们能够专注于业务逻辑的实现。
核心要点回顾:
- Starter机制:依赖管理的抽象,一键引入完整依赖
- 自动配置:条件装配 + 约定优于配置,零配置启动
- 内嵌容器:独立运行JAR,简化部署
- 配置优先级:命令行 > 环境变量 > 配置文件,灵活配置
- 自定义Starter:通过spring.factories + 自动配置类,扩展Spring Boot
关键收获:
- 从第一性原理理解Spring Boot的设计动机
- 掌握自动配置的实现原理和条件注解体系
- 学会手写自定义Starter,扩展Spring Boot能力
- 理解约定优于配置的设计哲学
下一篇预告: 第5篇:Spring Cloud微服务架构
- 从单体应用到微服务的演进
- Spring Cloud组件体系深度剖析
- 微服务拆分的第一性原理
系列文章:
- 为什么我们需要Spring框架?
- IoC容器:从手动new到自动装配的演进
- AOP:从代码重复到面向切面编程
- Spring Boot:约定优于配置的威力(本文)
- Spring Cloud:从单体到微服务的架构演进(待发布)
- Spring源码深度解析(待发布)
🤔 思考题:
- 如果让你设计一个自定义Starter,你会选择什么功能?
- Spring Boot的自动配置是如何做到"约定优于配置,但不强制"的?
- 为什么内嵌容器能显著提升开发效率?
欢迎在评论区分享你的思考和经验!
📚 推荐阅读:
- Spring Boot官方文档
- Spring Boot源码
- 《Spring Boot编程思想》- 小马哥