一句话定位:"约定优于配置"是 Spring Boot 的灵魂设计哲学——框架通过内置的合理默认值,让开发者只在偏离约定时才写配置,从而将配置量从"全部手写"压缩到"仅覆盖例外"。
定义与作用
Convention over Configuration(约定优于配置) 是一种软件设计范式,主张框架应为常见场景提供默认的、合理的约定,开发者无需为这些默认行为显式编写配置,仅在需要偏离约定时才进行自定义。
它解决的痛点:
| 传统SSM场景 | 需要手动配置的内容 | Spring Boot 的约定 |
|---|---|---|
| 数据源 | 手动写 DataSource Bean,指定连接池类型、驱动类、URL、用户名、密码 | 类路径存在 spring-jdbc + mysql-connector 时,自动创建 HikariCP 连接池,只需在 application.yml 填写连接参数 |
| 事务管理 | 配置 DataSourceTransactionManager Bean,声明 <tx:annotation-driven> | 自动检测到 DataSource 时,自动创建 DataSourceTransactionManager,并开启 @Transactional 支持 |
| Web MVC | 配置 InternalResourceViewResolver、RequestMappingHandlerMapping、静态资源映射 | 自动配置 Spring MVC 全套组件,默认静态资源映射到 /static 和 /public |
| 日志 | 引入 log4j 或 logback,手写 logback.xml | 默认使用 logback + SLF4J,默认日志格式和级别已就绪,只需在 application.yml 调整 logging.level |
| 服务器部署 | 配置外部 Tomcat,调整 server.xml,打包 WAR | 内嵌 Tomcat 自动启动,默认端口 8080,直接 java -jar 运行 |
在飞翔科技的学生成绩管理系统中,小崔使用 Spring Boot 后,配置代码量从传统 SSM 的 200+ 行 XML 减少到不到 20 行 YAML。
适用位置与常用属性
"约定优于配置"不是注解,而是贯穿 Spring Boot 的设计原则。它体现在以下具体机制中:
| 体现机制 | 约定内容 | 如何覆盖约定 |
|---|---|---|
| 组件扫描 | 默认扫描 @SpringBootApplication 所在包及其子包 | scanBasePackages 属性 |
| 配置文件 | 默认读取 classpath:application.yml | spring.config.location 指定其他路径 |
| 数据源 | 默认连接池 HikariCP,默认超时 30 秒 | spring.datasource.hikari.* |
| 服务器 | 默认内嵌 Tomcat,端口 8080 | server.port、server.servlet.context-path |
| 日志 | 默认 logback,INFO 级别,标准格式 | logging.level.*、logging.pattern.console |
| 静态资源 | 默认映射 /static、/public、/resources、/META-INF/resources | spring.mvc.static-path-pattern |
| 错误页面 | 默认 /error 映射,Whitelabel 错误页 | 自定义 ErrorController 或 ErrorAttributes |
| 日期格式 | 默认 Jackson 日期序列化为时间戳 | spring.jackson.date-format |
核心原理
从"全配置"到"仅覆盖例外"的范式转换
约定的层级体系
层级解读:
- 硬编码默认值:Spring Boot 源码中定义了大量常量(如
server.port=8080),这是最低优先级的默认值。 - 自动配置类:根据 classpath 检测,自动注册 Bean 并应用默认值(如
HikariDataSource)。自动配置可以覆盖硬编码默认值。 - application.yml:开发者通过配置文件覆盖自动配置的默认值,这是日常开发中最常用的覆盖层。
- 命令行/环境变量:在生产环境中,运维人员通过环境变量或启动参数注入最终配置,优先级最高,覆盖一切。
白歌常说:"约定不是限制,而是帮你省掉 80% 的决策疲劳。当你需要偏离时,Spring Boot 给了你精确的手术刀。"
完整示例
场景说明
飞翔科技的学生成绩管理系统需要连接 MySQL 数据库 student_db。小崔对比了两种方案:传统 Spring 的"全配置" vs Spring Boot 的"约定覆盖"。
操作前:传统Spring的"全配置"方案
// 操作前:传统Java Config方式,需要手动声明每一个Bean
package com.feixiang.student.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/student_db?useUnicode=true&characterEncoding=utf-8");
config.setUsername("root");
config.setPassword("root");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
config.setMaximumPoolSize(10);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
return new HikariDataSource(config);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
问题:
- 每个新项目都要写几乎一样的
DataSourceConfig。 - HikariCP 参数硬编码,不同环境(dev/test/prod)需要多个配置类或复杂的 Profile 切换。
- 连接池选型一旦变更(如从 HikariCP 切换到 Druid),需要改源码重新编译。
使用Spring Boot约定的完整代码
application.yml(仅写偏离约定的项):
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 10
connection-timeout: 30000
主启动类:
package com.feixiang.student;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StudentManagementApplication {
public static void main(String[] args) {
SpringApplication.run(StudentManagementApplication.class, args);
}
}
业务代码直接使用 JdbcTemplate:
package com.feixiang.student.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
@Autowired
private JdbcTemplate jdbcTemplate;
public String queryScore(String studentId) {
String sql = "SELECT score FROM student WHERE id = ?";
Integer score = jdbcTemplate.queryForObject(sql, Integer.class, studentId);
return "学生 " + studentId + " 的成绩为:" + score;
}
}
注意:这里没有
DataSourceConfig.java,没有@Bean方法声明DataSource,没有JdbcTemplateBean 定义。Spring Boot 的自动配置已全部完成。
操作后运行结果及分析
启动应用后,控制台关键日志:
2024-05-20 09:20:15.456 INFO 12345 --- [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-05-20 09:20:15.678 INFO 12345 --- [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed
2024-05-20 09:20:15.789 INFO 12345 --- [main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2024-05-20 09:20:15.890 INFO 12345 --- [main] c.f.s.StudentManagementApplication : Started in 2.123 seconds
分析:
- HikariPool 自动启动:
DataSourceAutoConfiguration检测到spring-jdbc和HikariCP在类路径,自动创建HikariDataSource并注入application.yml参数。 - JdbcTemplate 自动可用:
JdbcTemplateAutoConfiguration自动创建JdbcTemplateBean,开发者直接@Autowired使用。 - 事务管理器自动就绪:
DataSourceTransactionManagerAutoConfiguration自动创建事务管理器,支持@Transactional(无需手动@EnableTransactionManagement,Spring Boot 自动开启)。 - 配置量对比:传统方案约 60 行 Java Config + 数行 properties;Spring Boot 方案仅 10 行 YAML。
易错场景与面试考点
易错场景:误以为"约定优于配置"等于"完全不需要配置"
小崔曾犯过这样的错误:他以为 Spring Boot 什么都能自动猜出来,结果数据库连接报错。
错误示范:
# 错误:不写数据库配置,指望框架自动找到数据库
spring:
application:
name: student-management
启动报错:
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
原因分析:Spring Boot 的"约定"只覆盖通用基础设施的默认值(如连接池类型、日志格式、MVC 组件),但业务相关的连接参数(数据库地址、用户名、密码)不可能被猜出来。当开发者不配置 spring.datasource.url 时,Spring Boot 尝试寻找内嵌数据库(如 H2),如果也找不到,则启动失败。
正确做法:区分"框架能猜的"和"必须告诉它的"。
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
面试考点
Q:"约定优于配置"在 Spring Boot 中具体体现在哪些方面?举三个例子。
① 组件扫描:默认扫描启动类所在包及子包,无需手动指定
basePackages;② 数据源:类路径存在spring-jdbc+ 连接驱动时,自动创建 HikariCP 连接池,无需手写DataSourceBean;③ 服务器:默认内嵌 Tomcat,端口 8080,无需web.xml和外部服务器部署。此外还包括日志默认使用 logback、静态资源默认映射到/static等。
Q:Spring Boot 的约定被覆盖时,优先级顺序是怎样的?
从高到低:命令行参数 > 环境变量 >
SPRING_APPLICATION_JSON> 系统属性 > 各位置的application.yml/application.properties>@PropertySource> SpringApplication 默认属性 > 源码硬编码默认值。共约 17 级,高优先级覆盖低优先级。
Q:如果团队约定使用 Druid 连接池而非 HikariCP,Spring Boot 还能保持"约定优于配置"吗?
可以。Spring Boot 的约定是可替换的。引入
druid-spring-boot-starter后,该 Starter 中的自动配置类(如DruidDataSourceAutoConfiguration)通过@ConditionalOnMissingBean(DataSource.class)检测到没有自定义DataSource,就会创建 Druid 连接池。开发者只需引入依赖并在application.yml中配置spring.datasource.druid.*,无需手写DataSourceBean。这体现了"约定是可插拔的",而非"强制绑定"。