【JavaWeb手册004】Spring Boot,约定大于配置的现代艺术
"如果说Spring框架把我们从'工坊'带入了'工厂',那么Spring Boot就是将这座工厂升级为'全自动化智能生产线'。它告诉我们:'既然有通用的最佳实践,为什么还要重复配置?'"
4.1 Spring Boot的核心理念:自动配置的魔法
传统Spring应用的配置负担:
java
// 传统Spring MVC配置类
@Configuration
@EnableWebMvc
@ComponentScan("com.example")
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
// 更多配置...
}
Spring Boot的简洁之道:
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 仅此而已!自动配置会处理所有默认设置
Spring Boot自动配置原理图:
Spring Boot自动配置机制:
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ @SpringBoot │ -> │ EnableAuto │ -> │ AutoConfiguration│
│ Application │ │ Configuration │ │ 导入器 │
└─────────────────┘ └─────────┬────────┘ └─────────┬────────┘
│ │
┌─────────────────┐ ┌─────────▼────────┐ ┌─────────▼────────┐
│ 条件注解检查 │ -> │ 创建默认Bean │ -> │ 应用属性配置 │
│ @Conditional* │ │ 实例 │ │ @Configuration │
└─────────────────┘ └──────────────────┘ └──────────────────┘
4.2 自动配置的深度解析
Spring Boot的自动配置基于一系列条件注解,这些注解决定了在什么情况下应该自动配置某个功能。
核心条件注解:
| 注解 | 作用 | 示例 |
|---|---|---|
@ConditionalOnClass |
类路径下存在指定类时生效 | @ConditionalOnClass(DataSource.class) |
@ConditionalOnMissingBean |
容器中不存在指定Bean时生效 | @ConditionalOnMissingBean(DataSource.class) |
@ConditionalOnProperty |
配置属性满足条件时生效 | @ConditionalOnProperty(name="spring.datasource.url") |
@ConditionalOnWebApplication |
是Web应用时生效 | @ConditionalOnWebApplication |
@ConditionalOnExpression |
SpEL表达式为true时生效 | @ConditionalOnExpression("${app.feature.enabled:false}") |
自动配置源码窥探:
java
// spring-boot-autoconfigure 中的 DataSourceAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class })
protected static class PooledDataSourceConfiguration {
}
}
4.3 起步依赖:依赖管理的革命
Spring Boot通过起步依赖(Starter)解决了传统的"依赖地狱"问题。
传统依赖管理 vs Spring Boot起步依赖:
xml
<!-- 传统方式:需要手动管理每个依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0</version>
</dependency>
<!-- 更多依赖... -->
</dependencies>
<!-- Spring Boot方式:一个起步依赖搞定 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
常用起步依赖对照表:
| 功能 | 起步依赖 | 包含的主要组件 |
|---|---|---|
| Web开发 | spring-boot-starter-web |
Spring MVC, Tomcat, Jackson |
| 数据访问 | spring-boot-starter-data-jpa |
Spring Data JPA, Hibernate |
| 安全 | spring-boot-starter-security |
Spring Security |
| 测试 | spring-boot-starter-test |
JUnit, Mockito, Spring Test |
| 消息队列 | spring-boot-starter-amqp |
Spring AMQP, RabbitMQ |
| 缓存 | spring-boot-starter-cache |
Spring Cache Abstraction |
4.4 内嵌容器:部署方式的革命
Spring Boot内嵌容器的设计彻底改变了Java Web应用的部署方式。
部署方式演进:
java
// 传统部署:打包为WAR,部署到外部Tomcat
// 1. 打包:mvn package → myapp.war
// 2. 部署:复制到Tomcat的webapps目录
// 3. 启动:bin/startup.sh
// Spring Boot部署:可执行JAR,内嵌Tomcat
// 1. 打包:mvn package → myapp.jar
// 2. 运行:java -jar myapp.jar
// 3. 完成!
内嵌容器的工作原理:
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 这行代码背后发生了什么?
SpringApplication.run(Application.class, args);
}
}
// SpringApplication.run()的简化版实现逻辑:
public class SpringApplication {
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
// 1. 创建Spring应用上下文
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext();
// 2. 准备环境配置
ConfigurableEnvironment environment = prepareEnvironment();
// 3. 创建并启动内嵌Web服务器
WebServer webServer = createWebServer();
webServer.start();
// 4. 刷新Spring上下文,加载所有Bean
context.refresh();
return context;
}
private WebServer createWebServer() {
// 根据类路径自动选择Tomcat、Jetty或Undertow
if (ClassUtils.isPresent("org.apache.catalina.startup.Tomcat", null)) {
return new TomcatWebServer();
} else if (ClassUtils.isPresent("org.eclipse.jetty.server.Server", null)) {
return new JettyWebServer();
} else {
return new UndertowWebServer();
}
}
}
4.5 外部化配置:灵活的配置管理
Spring Boot提供了强大的外部化配置支持,让应用配置更加灵活。
配置源优先级(从高到低):
1. 命令行参数 (--server.port=9090)
2. Java系统属性 (System.getProperties())
3. 操作系统环境变量
4. 应用外的配置文件 (application-{profile}.yml)
5. 应用内的配置文件 (application-{profile}.yml)
6. 应用外的默认配置 (application.yml)
7. 应用内的默认配置 (application.yml)
8. @Configuration类上的@PropertySource
9. SpringApplication.setDefaultProperties()
多环境配置示例:
yaml
# application.yml (默认配置)
spring:
application:
name: my-app
server:
port: 8080
logging:
level:
root: INFO
---
# 开发环境配置
spring:
profiles: dev
datasource:
url: jdbc:h2:mem:testdb
username: sa
password:
logging:
level:
com.example: DEBUG
---
# 生产环境配置
spring:
profiles: prod
datasource:
url: jdbc:mysql://prod-db:3306/mydb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
server:
port: 80
management:
endpoints:
web:
exposure:
include: health,info,metrics
配置属性绑定:
java
// 自定义配置类
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank
private String name;
private String version = "1.0.0";
private Security security = new Security();
// 嵌套配置
public static class Security {
private boolean enabled = true;
private String secretKey;
// getters and setters
}
// getters and setters
}
// 在代码中使用
@RestController
public class ConfigController {
private final AppProperties appProperties;
public ConfigController(AppProperties appProperties) {
this.appProperties = appProperties;
}
@GetMapping("/config")
public AppProperties getConfig() {
return appProperties;
}
}
4.6 Spring Boot Actuator:生产就绪特性
Actuator为Spring Boot应用提供了生产级别的监控和管理功能。
启用Actuator:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
常用监控端点:
yaml
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,env,beans
endpoint:
health:
show-details: always
metrics:
enabled: true
自定义健康检查:
java
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Health health() {
try (Connection conn = dataSource.getConnection()) {
// 执行简单的数据库检查
if (conn.isValid(1000)) {
return Health.up()
.withDetail("database", "Connected successfully")
.build();
} else {
return Health.down()
.withDetail("database", "Connection failed")
.build();
}
} catch (Exception e) {
return Health.down(e)
.withDetail("database", "Connection error: " + e.getMessage())
.build();
}
}
}
// 自定义信息端点
@Component
public class AppInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
Map<String, Object> details = new HashMap<>();
details.put("author", "Spring Boot Explorer");
details.put("website", "https://example.com");
details.put("startupTime", new Date());
builder.withDetails(details);
}
}
4.7 自定义Starter:创建自己的自动配置
理解如何创建自定义Starter,能让我们深入掌握Spring Boot的自动配置机制。
自定义Starter项目结构:
my-spring-boot-starter/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── starter/
│ │ ├── MyService.java
│ │ ├── MyServiceProperties.java
│ │ └── MyServiceAutoConfiguration.java
│ └── resources/
│ └── META-INF/
│ └── spring.factories
└── pom.xml
核心代码实现:
java
// 配置属性类
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
private String prefix = "Hello";
private String suffix = "!";
// getters and setters
}
// 业务服务类
public class MyService {
private final MyServiceProperties properties;
public MyService(MyServiceProperties properties) {
this.properties = properties;
}
public String greet(String name) {
return properties.getPrefix() + " " + name + properties.getSuffix();
}
}
// 自动配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyServiceProperties properties) {
return new MyService(properties);
}
}
// META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.MyServiceAutoConfiguration
使用自定义Starter:
yaml
# application.yml
my:
service:
prefix: "Welcome"
suffix: "!!!"
java
@RestController
public class GreetingController {
private final MyService myService;
public GreetingController(MyService myService) {
this.myService = myService;
}
@GetMapping("/greet/{name}")
public String greet(@PathVariable String name) {
return myService.greet(name);
}
}
总结
Spring Boot通过自动配置、起步依赖、内嵌容器等特性,实现了Java开发的又一次范式转移:
- 约定大于配置:基于最佳实践的智能默认值
- 开箱即用:起步依赖提供完整的功能栈
- 生产就绪:Actuator提供完整的监控管理
- 灵活配置:多环境、外部化配置支持
Spring Boot设计哲学演进:
传统Spring → "配置是一切" → 显式配置每个Bean
↓
Spring Boot → "约定优于配置" → 智能默认值 + 按需覆盖
"Spring Boot不是要取代Spring,而是要让Spring更好用。它把开发人员从繁琐的配置中解放出来,让我们能够专注于创造业务价值。"
思考题与答案
思考题1:Spring Boot的自动配置是如何工作的?
答案:
Spring Boot自动配置的核心机制基于以下几个关键组件:
@EnableAutoConfiguration:这是自动配置的入口注解spring.factories文件 :在META-INF目录下声明自动配置类- 条件注解:决定是否应该应用某个自动配置
详细工作流程:
java
// 1. Spring Boot启动时扫描META-INF/spring.factories
// org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
// com.example.MyAutoConfiguration
// 2. 加载所有自动配置类
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(DataSource dataSource) {
return new MyService(dataSource);
}
}
// 3. 条件评估过程:
// - 类路径是否有DataSource类? (@ConditionalOnClass)
// - 配置中是否设置了datasource.url? (@ConditionalOnProperty)
// - 容器中是否已存在MyService实例? (@ConditionalOnMissingBean)
// - 所有条件满足时才创建Bean
思考题2:Spring Boot如何实现"约定大于配置"?
答案:
Spring Boot通过以下几个方面实现"约定大于配置":
-
智能默认值:
yaml# 如果没有配置,使用这些默认值 server: port: 8080 # 默认端口 servlet: context-path: / # 默认上下文路径 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test -
自动依赖管理:
xml<!-- 起步依赖自动管理版本 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 不需要指定版本 --> </dependency> -
条件化配置:
java// 只有当类路径有Security相关类时才配置安全 @ConditionalOnClass(SecurityFilterChain.class) @Configuration public class SecurityAutoConfiguration { // 自动配置Spring Security } -
配置属性提示:
java// 在IDE中提供配置属性的代码提示 @ConfigurationProperties(prefix = "app.mail") public class MailProperties { private String host = "smtp.example.com"; private int port = 587; // IDE会提示app.mail.host和app.mail.port }
思考题3:如何在Spring Boot中自定义错误处理?
答案:
Spring Boot提供了多种方式来自定义错误处理:
1. 自定义错误页面:
java
@Controller
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if (statusCode == 404) {
return "error/404";
} else if (statusCode == 500) {
return "error/500";
}
}
return "error/generic";
}
}
2. 全局异常处理:
java
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_SERVER_ERROR",
"An unexpected error occurred"
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"USER_NOT_FOUND",
ex.getMessage()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
}
// 自定义错误响应类
public class ErrorResponse {
private String code;
private String message;
private LocalDateTime timestamp;
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
this.timestamp = LocalDateTime.now();
}
// getters
}
3. 自定义错误属性:
java
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest,
ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
// 添加自定义字段
errorAttributes.put("timestamp", LocalDateTime.now());
errorAttributes.put("success", false);
// 移除敏感信息
errorAttributes.remove("trace");
return errorAttributes;
}
}
(下一章预告:我们将深入MyBatis,探索它如何优雅地解决数据库访问问题,以及它与Spring Boot的完美集成。我们将看到SQL是如何重新获得尊重的。)