spring-boot-starter-web 是 Spring Boot 生态中用于快速构建 Web 应用程序的核心启动器。作为 Spring Boot 自动配置理念的典型代表,它通过约定优于配置的方式,极大简化了传统 Spring MVC 应用的搭建过程。这个 starter 不仅仅是一个简单的依赖集合,更是 Spring Boot 对 Web 开发最佳实践的封装。
在实际企业级开发中,spring-boot-starter-web 主要解决以下痛点:
典型使用场景包括:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
groovy复制implementation 'org.springframework.boot:spring-boot-starter-web'
注意:在 Spring Boot 2.x+ 版本中,通常不需要显式指定版本号,因为 Spring Boot 的 BOM(Bill of Materials)已经管理了所有 starter 的版本兼容性。
通过 mvn dependency:tree 命令可以看到,spring-boot-starter-web 主要包含以下关键依赖:
spring-boot-starter
spring-boot-starter-json
spring-boot-starter-tomcat
spring-webmvc
spring-web
这些依赖共同构成了一个完整的 Web 应用运行环境。理解这个依赖树对于后续的定制和问题排查非常重要。
Spring Boot 的自动配置是其核心价值所在,理解这一机制对于高级使用和问题排查至关重要。
当引入 spring-boot-starter-web 后,以下自动配置类会生效:
WebMvcAutoConfiguration
HttpMessageConvertersAutoConfiguration
ServletWebServerFactoryAutoConfiguration
这些自动配置类通过 @Conditional 系列注解实现条件化配置,例如:
@ConditionalOnClass:当类路径存在特定类时生效@ConditionalOnMissingBean:当容器中不存在指定 Bean 时生效Spring Boot 会自动注册并配置 DispatcherServlet,关键配置项包括:
/(可通过 server.servlet.context-path 修改)DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAMEspring.mvc.* 属性进行配置典型配置示例(application.yml):
yaml复制spring:
mvc:
servlet:
load-on-startup: 1
async:
request-timeout: 30s
Spring Boot 默认会注册以下 HttpMessageConverter:
这些转换器会根据请求的 Content-Type 和 Accept 头自动选择合适的转换器处理请求和响应。
默认内嵌 Tomcat 的关键配置参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
| server.port | 8080 | 服务监听端口 |
| server.tomcat.max-threads | 200 | 最大工作线程数 |
| server.tomcat.min-spare-threads | 10 | 最小空闲线程数 |
| server.tomcat.connection-timeout | 60000ms | 连接超时时间 |
| server.tomcat.max-connections | 8192 | 最大连接数 |
生产环境推荐配置(application.yml):
yaml复制server:
port: 8080
tomcat:
max-threads: 500
min-spare-threads: 50
connection-timeout: 5000ms
max-connections: 10000
accept-count: 100
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
性能对比:Undertow 通常在高并发场景下表现优于 Tomcat 和 Jetty,特别是在内存占用方面有显著优势。
符合 REST 架构风格的 API 设计应遵循以下原则:
资源导向:
/users 而非 /getUsers)统一接口:
无状态:
可缓存:
java复制@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
// 实现获取所有用户的逻辑
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
// 实现根据ID获取用户的逻辑
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
// 实现创建用户的逻辑
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
// 实现更新用户的逻辑
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
// 实现删除用户的逻辑
}
}
java复制@GetMapping
public ResponseEntity<Page<User>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id,asc") String[] sort) {
// 实现分页查询逻辑
}
java复制@GetMapping("/{id}")
public EntityModel<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
return EntityModel.of(user,
linkTo(methodOn(UserController.class).getUserById(id)).withSelfRel(),
linkTo(methodOn(UserController.class).getAllUsers()).withRel("users"));
}
java复制@GetMapping("/async")
public CompletableFuture<String> asyncProcessing() {
return CompletableFuture.supplyAsync(() -> {
// 长时间运行的任务
return "Result";
});
}
yaml复制spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps: false
write-null-map-values: false
deserialization:
fail-on-unknown-properties: false
java复制@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.featuresToDisable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
builder.modules(new JavaTimeModule());
};
}
}
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(new Jdk8Module());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
Spring Boot 会依次从以下位置查找静态资源:
/META-INF/resources//resources//static//public/优先级从高到低,找到第一个匹配的资源即返回。
yaml复制spring:
resources:
static-locations: classpath:/custom-static/,file:/opt/static/
cache:
period: 3600
cache-control: public, max-age=3600
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
java复制public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (!validateToken(token)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid token");
return false;
}
return true;
}
// 其他方法...
}
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login", "/api/public/**");
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/**");
}
}
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
java复制@RestController
@RequestMapping("/api/products")
public class ProductController {
@CrossOrigin(origins = "https://example.com")
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
// ...
}
}
java复制@PostMapping("/upload/chunk")
public ResponseEntity<String> uploadChunk(
@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("identifier") String identifier) {
// 创建临时目录存储分块
String tempDir = uploadDir + "/temp/" + identifier;
File chunkFile = new File(tempDir, chunkNumber + ".part");
// 保存分块
file.transferTo(chunkFile);
// 如果是最后一个分块,合并文件
if (chunkNumber == totalChunks - 1) {
mergeFiles(tempDir, identifier);
}
return ResponseEntity.ok("Chunk uploaded");
}
java复制@GetMapping("/download/large")
public ResponseEntity<StreamingResponseBody> downloadLargeFile() {
File file = new File("/path/to/large/file");
StreamingResponseBody stream = out -> {
try (InputStream in = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(stream);
}
连接池配置:
缓存策略:
异步处理:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
关键指标监控:
自定义健康检查:
java复制@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// 实现自定义健康检查逻辑
return Health.up().withDetail("detail", "value").build();
}
}
基础安全防护:
Spring Security 集成:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
问题:端口冲突
code复制***************************
APPLICATION FAILED TO START
***************************
Description:
Embedded servlet container failed to start. Port 8080 was already in use.
解决方案:
lsof -i :8080 或 netstat -ano | findstr 8080server.port=8081问题:循环引用导致栈溢出
code复制com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)
解决方案:
@JsonIgnore 忽略一方@JsonManagedReference 和 @JsonBackReference问题:上传大文件失败
code复制org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException:
the request was rejected because its size (10485761) exceeds the configured maximum (10485760)
解决方案:
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
Jakarta EE 9+:
javax.* 变为 jakarta.*依赖变化:
配置变化:
逐步迁移策略:
依赖检查:
测试策略: