跳到主要内容

Web 开发

本章将深入讲解 Spring Boot Web 开发的核心知识,包括 RESTful API 设计、请求处理、参数验证、异常处理等内容。

RESTful API 设计​

REST 架构风格​

REST(Representational State Transfer)是一种软件架构风格,强调资源的状态转移。

RESTful 设计原则:

原则说明
统一接口使用标准的 HTTP 方法操作资源
无状态每个请求包含所有必要信息
可缓存响应应标明是否可缓存
分层系统客户端无需知道连接的是哪一层

HTTP 方法映射​

HTTP 方法操作示例 URL说明
GET查询/api/users获取用户列表
GET查询/api/users/{id}获取单个用户
POST创建/api/users创建新用户
PUT更新/api/users/{id}更新用户(完整)
PATCH更新/api/users/{id}更新用户(部分)
DELETE删除/api/users/{id}删除用户

RESTful 控制器示例​

@RestController
@RequestMapping("/api/users")
public class UserController {

@Autowired
private UserService userService;

// 获取用户列表
@GetMapping
public Result<List<User>> list() {
return Result.success(userService.findAll());
}

// 获取单个用户
@GetMapping("/{id}")
public Result<User> getById(@PathVariable Long id) {
return Result.success(userService.findById(id));
}

// 创建用户
@PostMapping
public Result<User> create(@RequestBody @Valid UserDTO userDTO) {
return Result.success(userService.save(userDTO));
}

// 更新用户
@PutMapping("/{id}")
public Result<User> update(@PathVariable Long id, @RequestBody @Valid UserDTO userDTO) {
return Result.success(userService.update(id, userDTO));
}

// 删除用户
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
userService.deleteById(id);
return Result.success();
}
}

请求处理​

获取请求参数​

@RequestParam:查询参数​

@GetMapping("/search")
public Result<List<User>> search(
@RequestParam(required = false) String name,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return Result.success(userService.search(name, page, size));
}

请求示例:GET /api/users/search?name=张三&page=0&size=10

属性说明:

属性说明
value / name参数名
required是否必需(默认 true)
defaultValue默认值

@PathVariable:路径变量​

@GetMapping("/{id}")
public Result<User> getById(@PathVariable Long id) {
return Result.success(userService.findById(id));
}

// 多个路径变量
@GetMapping("/{userId}/orders/{orderId}")
public Result<Order> getOrder(
@PathVariable Long userId,
@PathVariable Long orderId
) {
return Result.success(orderService.findByUserIdAndOrderId(userId, orderId));
}

@RequestBody:请求体​

@PostMapping
public Result<User> create(@RequestBody @Valid UserDTO userDTO) {
return Result.success(userService.save(userDTO));
}

请求示例:

POST /api/users
Content-Type: application/json

{
"name": "张三",
"email": "zhangsan@example.com",
"age": 25
}

@RequestHeader:请求头​

@GetMapping("/info")
public Result<Map<String, String>> getInfo(
@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value = "X-Token", required = false) String token
) {
Map<String, String> info = new HashMap<>();
info.put("userAgent", userAgent);
info.put("token", token);
return Result.success(info);
}

@CookieValue:Cookie 值​

@GetMapping("/session")
public Result<String> getSession(@CookieValue("JSESSIONID") String sessionId) {
return Result.success(sessionId);
}

请求方法映射​

@RestController
@RequestMapping("/api")
public class MethodController {

// GET 请求
@GetMapping("/resource")
public String get() {
return "GET 请求";
}

// POST 请求
@PostMapping("/resource")
public String post(@RequestBody String body) {
return "POST 请求: " + body;
}

// PUT 请求
@PutMapping("/resource/{id}")
public String put(@PathVariable Long id, @RequestBody String body) {
return "PUT 请求: " + id + ", " + body;
}

// PATCH 请求
@PatchMapping("/resource/{id}")
public String patch(@PathVariable Long id, @RequestBody String body) {
return "PATCH 请求: " + id + ", " + body;
}

// DELETE 请求
@DeleteMapping("/resource/{id}")
public String delete(@PathVariable Long id) {
return "DELETE 请求: " + id;
}

// 多方法映射
@RequestMapping(value = "/multi", method = {RequestMethod.GET, RequestMethod.POST})
public String multiMethod() {
return "GET 或 POST 请求";
}
}

响应处理​

统一响应格式​

定义统一的响应结构:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {

private Integer code; // 状态码
private String message; // 提示信息
private T data; // 数据

// 成功响应
public static <T> Result<T> success() {
return new Result<>(200, "success", null);
}

public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data);
}

public static <T> Result<T> success(String message, T data) {
return new Result<>(200, message, data);
}

// 失败响应
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}

public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
}

响应示例:

{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
}

分页响应​

@Data
public class PageResult<T> {

private List<T> list; // 数据列表
private Long total; // 总记录数
private Integer page; // 当前页
private Integer size; // 每页大小
private Integer totalPages; // 总页数

public static <T> PageResult<T> of(List<T> list, Long total, Integer page, Integer size) {
PageResult<T> result = new PageResult<>();
result.setList(list);
result.setTotal(total);
result.setPage(page);
result.setSize(size);
result.setTotalPages((int) Math.ceil((double) total / size));
return result;
}
}

ResponseEntity​

对于需要设置响应头或状态码的场景:

@GetMapping("/{id}")
public ResponseEntity<User> getById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.header("X-Custom-Header", "value")
.body(user);
}

@PostMapping
public ResponseEntity<User> create(@RequestBody UserDTO userDTO) {
User user = userService.save(userDTO);
// 返回 201 Created 状态码和 Location 头
return ResponseEntity
.created(URI.create("/api/users/" + user.getId()))
.body(user);
}

参数验证​

添加验证依赖​

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

验证注解​

注解作用示例
@NotNull不能为 null@NotNull
@NotBlank不能为空字符串@NotBlank
@NotEmpty不能为空集合/字符串@NotEmpty
@Size字符串/集合长度范围@Size(min = 2, max = 10)
@Min / @Max数值最小/最大值@Min(0) @Max(100)
@Email邮箱格式@Email
@Pattern正则表达式@Pattern(regexp = "^1[3-9]\\d{9}$")
@Past / @Future过去/未来日期@Past
@Digits数字位数@Digits(integer = 5, fraction = 2)

使用验证​

验证请求体​

@Data
public class UserDTO {

@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20个字符之间")
private String name;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄必须大于0")
@Max(value = 150, message = "年龄必须小于150")
private Integer age;

@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}

@RestController
public class UserController {

@PostMapping
public Result<User> create(@RequestBody @Valid UserDTO userDTO) {
return Result.success(userService.save(userDTO));
}
}

验证路径参数和查询参数​

@GetMapping("/{id}")
public Result<User> getById(
@PathVariable @Min(1) Long id,
@RequestParam @NotBlank String name
) {
return Result.success(userService.findById(id));
}

注意:需要在类级别添加 @Validated 注解:

@RestController
@Validated
public class UserController {
// ...
}

嵌套验证​

@Data
public class OrderDTO {

@NotBlank(message = "订单号不能为空")
private String orderNo;

@Valid // 启用嵌套验证
@NotNull(message = "收货地址不能为空")
private AddressDTO address;
}

@Data
public class AddressDTO {

@NotBlank(message = "省不能为空")
private String province;

@NotBlank(message = "市不能为空")
private String city;

@NotBlank(message = "详细地址不能为空")
private String detail;
}

自定义验证器​

自定义注解​

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {

String message() default "手机号格式不正确";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

自定义验证器​

public class PhoneValidator implements ConstraintValidator<Phone, String> {

private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // 空值交给 @NotBlank 处理
}
return PHONE_PATTERN.matcher(value).matches();
}
}

使用自定义验证​

@Data
public class UserDTO {

@Phone(message = "请输入正确的手机号")
private String phone;
}

异常处理​

全局异常处理器​

@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

/**
* 处理参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(400, message);
}

/**
* 处理约束违反异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
return Result.error(400, message);
}

/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}

/**
* 处理资源未找到异常
*/
@ExceptionHandler(EntityNotFoundException.class)
public Result<Void> handleEntityNotFoundException(EntityNotFoundException e) {
return Result.error(404, e.getMessage());
}

/**
* 处理其他异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
logger.error("系统异常", e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}

自定义业务异常​

@Getter
public class BusinessException extends RuntimeException {

private final Integer code;

public BusinessException(String message) {
super(message);
this.code = 500;
}

public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
}

// 使用示例
public User findById(Long id) {
User user = userRepository.findById(id).orElse(null);
if (user == null) {
throw new BusinessException(404, "用户不存在");
}
return user;
}

错误码枚举​

@Getter
@AllArgsConstructor
public enum ErrorCode {

SUCCESS(200, "成功"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源不存在"),
INTERNAL_ERROR(500, "服务器内部错误");

private final Integer code;
private final String message;
}

跨域处理​

方式一:@CrossOrigin 注解​

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*", maxAge = 3600)
public class ApiController {
// ...
}

方式二:全局配置​

@Configuration
public class CorsConfig {

@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();

// 允许的域名
config.addAllowedOriginPattern("*");

// 允许的请求头
config.addAllowedHeader("*");

// 允许的请求方法
config.addAllowedMethod("*");

// 允许携带 Cookie
config.setAllowCredentials(true);

// 预检请求缓存时间
config.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

return new CorsFilter(source);
}
}

方式三:WebMvcConfigurer​

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}

文件上传下载​

文件上传​

@RestController
@RequestMapping("/api/files")
public class FileController {

@Value("${file.upload-dir:./uploads}")
private String uploadDir;

/**
* 单文件上传
*/
@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
throw new BusinessException("请选择要上传的文件");
}

// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String filename = UUID.randomUUID().toString() + extension;

// 创建上传目录
Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}

// 保存文件
Path filePath = uploadPath.resolve(filename);
file.transferTo(filePath);

return Result.success(filename);
}

/**
* 多文件上传
*/
@PostMapping("/uploads")
public Result<List<String>> uploads(@RequestParam("files") MultipartFile[] files) throws IOException {
List<String> filenames = new ArrayList<>();

for (MultipartFile file : files) {
if (!file.isEmpty()) {
String filename = saveFile(file);
filenames.add(filename);
}
}

return Result.success(filenames);
}

/**
* 文件下载
*/
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> download(@PathVariable String filename) throws IOException {
Path filePath = Paths.get(uploadDir).resolve(filename);
Resource resource = new FileSystemResource(filePath);

if (!resource.exists()) {
throw new BusinessException(404, "文件不存在");
}

// 设置响应头
String contentType = Files.probeContentType(filePath);
String disposition = "attachment; filename=\"" + URLEncoder.encode(filename, "UTF-8") + "\"";

return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, disposition)
.body(resource);
}
}

文件上传配置​

spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB # 单个文件最大大小
max-request-size: 100MB # 总请求最大大小
file-size-threshold: 0 # 超过此大小的文件写入临时目录

拦截器​

创建拦截器​

@Component
public class AuthInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 请求预处理
String token = request.getHeader("Authorization");

if (token == null || !validateToken(token)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"未授权\"}");
return false;
}

return true; // 继续执行
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 请求处理后处理
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求完成后处理
}
}

注册拦截器​

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Autowired
private AuthInterceptor authInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns( // 排除路径
"/api/auth/login",
"/api/auth/register",
"/api/public/**"
);
}
}

HTTP 客户端​

在微服务架构中,服务间调用是常见需求。Spring Boot 提供了多种 HTTP 客户端选择,其中 RestClient 是 Spring 6.1 引入的现代同步 HTTP 客户端,也是官方推荐的替代 RestTemplate 的方案。

RestClient 概述​

RestClient 是一个功能强大的同步 HTTP 客户端,提供流畅的 API 风格。相比传统的 RestTemplate,它有以下优势:

特性RestClientRestTemplate
API 风格流畅、函数式模板方法模式
类型安全更好的泛型支持较弱
可读性链式调用,更直观方法调用较多
推荐程度官方推荐维护模式(不推荐新项目使用)

RestClient 基本使用​

Spring Boot 自动配置了 RestClient.Builder,推荐注入使用:

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class UserService {

private final RestClient restClient;

// 注入自动配置的 Builder
public UserService(RestClient.Builder restClientBuilder) {
this.restClient = restClientBuilder
.baseUrl("https://api.example.com")
.build();
}

/**
* GET 请求示例
*/
public User getUser(Long id) {
return restClient.get()
.uri("/users/{id}", id)
.retrieve()
.body(User.class);
}

/**
* 带请求头的 GET 请求
*/
public User getUserWithAuth(Long id, String token) {
return restClient.get()
.uri("/users/{id}", id)
.header("Authorization", "Bearer " + token)
.retrieve()
.body(User.class);
}

/**
* POST 请求示例
*/
public User createUser(UserDTO userDTO) {
return restClient.post()
.uri("/users")
.contentType(MediaType.APPLICATION_JSON)
.body(userDTO)
.retrieve()
.body(User.class);
}

/**
* PUT 请求示例
*/
public User updateUser(Long id, UserDTO userDTO) {
return restClient.put()
.uri("/users/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(userDTO)
.retrieve()
.body(User.class);
}

/**
* DELETE 请求示例
*/
public void deleteUser(Long id) {
restClient.delete()
.uri("/users/{id}", id)
.retrieve()
.toBodilessEntity(); // 忽略响应体
}
}

解释:

  • RestClient.Builder 是原型作用域,每次注入都是新实例
  • retrieve() 方法触发实际请求
  • body() 方法将响应体反序列化为指定类型
  • toBodilessEntity() 用于忽略响应体

处理响应和错误​

获取完整响应信息​

import org.springframework.http.ResponseEntity;

public User getUserWithResponse(Long id) {
ResponseEntity<User> response = restClient.get()
.uri("/users/{id}", id)
.retrieve()
.toEntity(User.class);

// 获取响应信息
int statusCode = response.getStatusCode().value();
HttpHeaders headers = response.getHeaders();
User user = response.getBody();

return user;
}

错误处理​

import org.springframework.web.client.RestClientResponseException;

public User getUserSafe(Long id) {
try {
return restClient.get()
.uri("/users/{id}", id)
.retrieve()
.body(User.class);
} catch (RestClientResponseException e) {
// 服务器返回错误状态码(4xx、5xx)
int statusCode = e.getStatusCode().value();
String responseBody = e.getResponseBodyAsString();
log.error("请求失败,状态码: {}, 响应: {}", statusCode, responseBody);
throw new BusinessException("获取用户失败: " + e.getMessage());
}
}

// 自定义错误处理
public User getUserWithHandler(Long id) {
return restClient.get()
.uri("/users/{id}", id)
.retrieve()
.onStatus(status -> status.is4xxClientError(), (request, response) -> {
// 4xx 错误处理
throw new BusinessException("客户端错误: " + response.getStatusCode());
})
.onStatus(status -> status.is5xxServerError(), (request, response) -> {
// 5xx 错误处理
throw new BusinessException("服务器错误: " + response.getStatusCode());
})
.body(User.class);
}

处理列表响应​

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public List<User> getUsers() {
// 方式一:使用数组
User[] users = restClient.get()
.uri("/users")
.retrieve()
.body(User[].class);
return Arrays.asList(users);

// 方式二:使用 ParameterizedTypeReference(推荐)
List<User> userList = restClient.get()
.uri("/users")
.retrieve()
.body(new ParameterizedTypeReference<List<User>>() {});
return userList;
}

请求定制​

使用 UriBuilder 构建 URI​

import org.springframework.web.util.UriBuilder;

public List<User> searchUsers(String name, Integer age, String email) {
return restClient.get()
.uri(uriBuilder -> uriBuilder
.path("/users/search")
.queryParam("name", name)
.queryParam("age", age)
.queryParamIfPresent("email", Optional.ofNullable(email))
.build())
.retrieve()
.body(new ParameterizedTypeReference<List<User>>() {});
}

添加请求拦截器​

@Configuration
public class RestClientConfig {

@Bean
public RestClient restClient(RestClient.Builder builder) {
return builder
.baseUrl("https://api.example.com")
.defaultHeader("User-Agent", "MyApp/1.0")
.defaultHeader("Accept", "application/json")
.requestInterceptor((request, body, execution) -> {
// 记录请求日志
log.info("请求: {} {}", request.getMethod(), request.getURI());
long start = System.currentTimeMillis();

try {
ClientHttpResponse response = execution.execute(request, body);
long duration = System.currentTimeMillis() - start;
log.info("响应: {}, 耗时: {}ms", response.getStatusCode(), duration);
return response;
} catch (IOException e) {
log.error("请求失败", e);
throw e;
}
})
.build();
}
}

设置超时​

import java.time.Duration;

@Configuration
public class RestClientConfig {

@Bean
public RestClient restClient(RestClient.Builder builder) {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULT
.withConnectTimeout(Duration.ofSeconds(5))
.withReadTimeout(Duration.ofSeconds(30));

return builder
.baseUrl("https://api.example.com")
.requestFactory(ClientHttpRequestFactoryBuilder.detect().build(settings))
.build();
}
}

SSL 配置​

当需要调用使用自签名证书或自定义证书的 HTTPS 服务时,可以使用 SSL Bundles:

# application.yml
spring:
ssl:
bundle:
jks:
mybundle:
key:
alias: "myalias"
location: "classpath:keystore.jks"
password: "secret"
options:
protocol: "TLS"
import org.springframework.boot.autoconfigure.web.client.RestClientSsl;
import org.springframework.boot.ssl.SslBundles;

@Service
public class SecureApiService {

private final RestClient restClient;

// 方式一:使用 RestClientSsl
public SecureApiService(RestClient.Builder builder, RestClientSsl ssl) {
this.restClient = builder
.baseUrl("https://secure-api.example.com")
.apply(ssl.fromBundle("mybundle"))
.build();
}

// 方式二:使用 SslBundles
public SecureApiService(RestClient.Builder builder, SslBundles sslBundles) {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULT
.withSslBundle(sslBundles.getBundle("mybundle"))
.withReadTimeout(Duration.ofMinutes(2));

this.restClient = builder
.baseUrl("https://secure-api.example.com")
.requestFactory(ClientHttpRequestFactoryBuilder.detect().build(settings))
.build();
}
}

全局 HTTP 客户端配置​

Spring Boot 支持全局配置 HTTP 客户端属性:

spring:
http:
client:
connect-timeout: 5s # 连接超时
read-timeout: 30s # 读取超时
redirects: follow # 重定向策略: follow, dont-follow
factory: jdk # 指定 HTTP 客户端: jdk, jetty, httpcomponents, reactor

支持的 HTTP 客户端(按优先级):

客户端依赖说明
Apache HttpClienthttpclient5功能丰富,推荐生产环境使用
Jetty HttpClientjetty-client轻量级
Reactor Nettyreactor-netty-http支持 HTTP/2
JDK HttpClientJava 11+ 内置无额外依赖
Simple JDKJava 内置功能有限,不推荐

RestClient vs RestTemplate​

如果已有项目使用 RestTemplate,可以逐步迁移:

// RestTemplate 方式(不推荐新项目使用)
User user = restTemplate.getForObject("/users/{id}", User.class, id);

// RestClient 方式(推荐)
User user = restClient.get()
.uri("/users/{id}", id)
.retrieve()
.body(User.class);

迁移建议:

  1. 新项目直接使用 RestClient
  2. 旧项目可以共存,逐步替换
  3. RestTemplate 处于维护模式,不会添加新功能

WebClient(响应式客户端)​

如果需要非阻塞的响应式客户端,可以使用 WebClient:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class ReactiveUserService {

private final WebClient webClient;

public ReactiveUserService(WebClient.Builder builder) {
this.webClient = builder
.baseUrl("https://api.example.com")
.build();
}

public Mono<User> getUser(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(User.class);
}

public Flux<User> getAllUsers() {
return webClient.get()
.uri("/users")
.retrieve()
.bodyToFlux(User.class);
}
}

选择建议:

场景推荐客户端
传统 Spring MVC 应用RestClient
响应式 Spring WebFlux 应用WebClient
高并发、非阻塞需求WebClient
简单的同步调用RestClient

WebSocket 实时通信​

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,适用于实时聊天、消息推送、股票行情等场景。Spring Boot 提供了完善的 WebSocket 支持。

添加依赖​

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocket 配置类​

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker // 启用 WebSocket 消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

/**
* 配置消息代理
* - enableSimpleBroker: 启用简单的内存消息代理,用于向客户端推送消息
* - setApplicationDestinationPrefixes: 设置应用目的地前缀
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 启用简单的内存消息代理,客户端订阅以 /topic 开头的目的地
config.enableSimpleBroker("/topic");
// 客户端发送消息的目的地前缀
config.setApplicationDestinationPrefixes("/app");
}

/**
* 注册 STOMP 端点
* - 客户端通过此端点建立 WebSocket 连接
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws") // WebSocket 端点路径
.setAllowedOriginPatterns("*") // 允许跨域
.withSockJS(); // 启用 SockJS 降级支持
}
}

配置说明:

配置项说明
/topic消息代理前缀,用于广播消息
/app应用目的地前缀,控制器处理的消息
/wsWebSocket 连接端点
withSockJS()启用 SockJS 降级,兼容不支持 WebSocket 的浏览器

消息模型​

/**
* 接收的消息
*/
@Data
public class ChatMessage {
private String from; // 发送者
private String content; // 消息内容
private String type; // 消息类型:CHAT, JOIN, LEAVE
}

/**
* 响应的消息
*/
@Data
@AllArgsConstructor
public class ChatResponse {
private String from;
private String content;
private LocalDateTime timestamp;
}

WebSocket 控制器​

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

/**
* 处理聊天消息
* @MessageMapping: 客户端发送消息的目的地(/app/chat.sendMessage)
* @SendTo: 处理后发送到的目的地(/topic/public)
*/
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatResponse sendMessage(@Payload ChatMessage message) {
return new ChatResponse(
message.getFrom(),
message.getContent(),
LocalDateTime.now()
);
}

/**
* 处理用户加入
*/
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatResponse addUser(@Payload ChatMessage message,
SimpMessageHeaderAccessor headerAccessor) {
// 将用户名存入 WebSocket Session
headerAccessor.getSessionAttributes().put("username", message.getFrom());

return new ChatResponse(
"系统",
message.getFrom() + " 加入了聊天",
LocalDateTime.now()
);
}
}

注解说明:

注解作用
@MessageMapping映射 WebSocket 消息到处理方法
@SendTo将方法返回值发送到指定目的地
@Payload绑定消息体到参数
@DestinationVariable绑定目的地变量(类似 @PathVariable)

向特定用户发送消息​

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

@Service
public class NotificationService {

@Autowired
private SimpMessagingTemplate messagingTemplate;

/**
* 向特定用户发送消息
* @param userId 用户ID
* @param message 消息内容
*/
public void sendToUser(String userId, Object message) {
// 发送到 /user/{userId}/queue/notifications
messagingTemplate.convertAndSendToUser(
userId,
"/queue/notifications",
message
);
}

/**
* 向所有订阅者广播消息
*/
public void broadcast(Object message) {
messagingTemplate.convertAndSend("/topic/notifications", message);
}
}

配置用户目的地​

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue"); // 添加 /queue 支持点对点消息
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user"); // 用户目的地前缀
}

// ... 其他配置
}

前端连接示例(JavaScript)​

// 使用 STOMP.js 连接 WebSocket
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';

// 建立连接
const socket = new SockJS('http://localhost:8080/ws');
const stompClient = Stomp.over(socket);

// 连接成功回调
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);

// 订阅公共消息
stompClient.subscribe('/topic/public', function(message) {
const data = JSON.parse(message.body);
console.log('收到消息:', data);
});

// 订阅个人消息(需要认证)
stompClient.subscribe('/user/queue/notifications', function(message) {
const data = JSON.parse(message.body);
console.log('收到个人消息:', data);
});
});

// 发送消息
function sendMessage(from, content) {
stompClient.send('/app/chat.sendMessage', {}, JSON.stringify({
from: from,
content: content,
type: 'CHAT'
}));
}

WebSocket 安全配置​

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("http://localhost:*")
.withSockJS();
}
}

WebSocket 认证拦截器:

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.Map;

@Component
public class AuthHandshakeInterceptor implements HandshakeInterceptor {

@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
// 从请求中获取认证信息
if (request instanceof ServletServerHttpRequest servletRequest) {
String token = servletRequest.getServletRequest().getParameter("token");
// 验证 token 并获取用户信息
if (token != null && validateToken(token)) {
attributes.put("user", getUserFromToken(token));
return true;
}
}
return false;
}

@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
}
}

SSE 服务器推送​

Server-Sent Events(SSE)是一种服务器向客户端推送数据的技术,与 WebSocket 不同,SSE 是单向通信(仅服务器到客户端),适合实时通知、数据监控等场景。

SSE vs WebSocket 对比​

特性SSEWebSocket
通信方向单向(服务器→客户端)双向
协议HTTPWebSocket 协议
断线重连浏览器自动重连需要手动实现
数据格式文本(UTF-8)文本和二进制
浏览器支持IE 不支持广泛支持
适用场景实时通知、数据监控聊天、游戏、协作编辑

使用 SseEmitter​

Spring MVC 提供了 SseEmitter 类来实现 SSE:

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@RestController
@RequestMapping("/api/sse")
public class SseController {

// 存储所有连接的客户端
private final CopyOnWriteArraySet<SseEmitter> emitters = new CopyOnWriteArraySet<>();

/**
* 建立 SSE 连接
* 客户端通过此接口建立长连接,接收服务器推送的事件
*/
@GetMapping(path = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() {
// 创建 SseEmitter,设置超时时间(30分钟)
SseEmitter emitter = new SseEmitter(30 * 60 * 1000L);

// 添加到集合
emitters.add(emitter);

// 设置回调
emitter.onCompletion(() -> emitters.remove(emitter));
emitter.onTimeout(() -> emitters.remove(emitter));
emitter.onError(e -> emitters.remove(emitter));

// 发送初始连接成功消息
try {
emitter.send(SseEmitter.event()
.name("connect")
.data("连接成功"));
} catch (IOException e) {
emitter.completeWithError(e);
}

return emitter;
}

/**
* 向所有客户端推送消息
*/
public void broadcast(String message) {
for (SseEmitter emitter : emitters) {
try {
emitter.send(SseEmitter.event()
.name("message")
.data(message));
} catch (IOException e) {
// 发送失败,移除连接
emitters.remove(emitter);
}
}
}

/**
* 推送带ID的事件
*/
public void sendEvent(String eventId, Object data) {
for (SseEmitter emitter : emitters) {
try {
emitter.send(SseEmitter.event()
.id(eventId)
.name("update")
.data(data, MediaType.APPLICATION_JSON));
} catch (IOException e) {
emitters.remove(emitter);
}
}
}
}

SseEmitter 方法说明:

方法说明
send(Object)发送数据
send(SseEventBuilder)发送完整事件
complete()正常结束连接
completeWithError(Throwable)异常结束连接
onCompletion(Runnable)连接完成回调
onTimeout(Runnable)超时回调
onError(Consumer<Throwable>)错误回调

SSE 事件构建​

// 构建复杂的 SSE 事件
emitter.send(SseEmitter.event()
.id("123") // 事件ID
.name("notification") // 事件名称
.reconnectTime(5000) // 重连时间(毫秒)
.comment("这是一个注释") // 注释
.data("{\"message\":\"Hello\"}", MediaType.APPLICATION_JSON) // 数据
);

// 发送多行数据
emitter.send(SseEmitter.event()
.name("multi-data")
.data("第一行数据\n")
.data("第二行数据\n")
.data("第三行数据")
);

实时数据推送示例​

@Service
public class StockPriceService {

private final SseEmitter emitter = new SseEmitter();

/**
* 模拟股票价格实时推送
*/
@Scheduled(fixedRate = 1000) // 每秒推送一次
public void pushStockPrice() {
try {
StockPrice price = new StockPrice(
"AAPL",
Math.random() * 100 + 150,
LocalDateTime.now()
);

emitter.send(SseEmitter.event()
.name("stock-price")
.data(price, MediaType.APPLICATION_JSON));
} catch (IOException e) {
emitter.completeWithError(e);
}
}

public SseEmitter getEmitter() {
return emitter;
}
}

前端接收 SSE​

// 创建 EventSource 连接
const eventSource = new EventSource('/api/sse/connect');

// 监听连接打开
eventSource.addEventListener('connect', function(event) {
console.log('SSE 连接成功:', event.data);
});

// 监听消息事件
eventSource.addEventListener('message', function(event) {
console.log('收到消息:', event.data);
});

// 监听自定义事件
eventSource.addEventListener('stock-price', function(event) {
const data = JSON.parse(event.data);
console.log('股票价格更新:', data);
});

// 监听错误
eventSource.onerror = function(event) {
console.error('SSE 错误:', event);
// 浏览器会自动重连
};

// 关闭连接
function closeConnection() {
eventSource.close();
}

SSE 最佳实践​

1. 心跳机制:

@Scheduled(fixedRate = 30000)  // 每30秒发送心跳
public void sendHeartbeat() {
for (SseEmitter emitter : emitters) {
try {
emitter.send(SseEmitter.event()
.comment("heartbeat") // 注释作为心跳
);
} catch (IOException e) {
emitters.remove(emitter);
}
}
}

2. 连接管理:

@Component
public class SseConnectionManager {

private final ConcurrentHashMap<String, SseEmitter> emitters = new ConcurrentHashMap<>();

public SseEmitter createEmitter(String clientId) {
SseEmitter emitter = new SseEmitter(30 * 60 * 1000L);

emitter.onCompletion(() -> emitters.remove(clientId));
emitter.onTimeout(() -> emitters.remove(clientId));

emitters.put(clientId, emitter);
return emitter;
}

public void sendToClient(String clientId, Object data) {
SseEmitter emitter = emitters.get(clientId);
if (emitter != null) {
try {
emitter.send(data);
} catch (IOException e) {
emitters.remove(clientId);
}
}
}
}

异步请求处理​

在高并发场景下,异步请求处理可以显著提高服务器的吞吐量。Spring MVC 提供了多种异步处理方式。

异步处理原理​

┌─────────────────────────────────────────────────────────────────────┐
│ 异步请求处理流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 客户端请求 ──> Servlet线程(释放)──> 容器线程池 ──> 响应客户端 │
│ │ │ │
│ │ ▼ │
│ │ 异步任务执行 │
│ │ (不占用Servlet线程) │
│ │ │ │
│ └─────────────────────┘ │
│ │
│ 优势: │
│ - Servlet 线程快速释放,处理更多请求 │
│ - 长时间任务在独立线程执行,不阻塞容器 │
│ - 提高服务器并发处理能力 │
│ │
└─────────────────────────────────────────────────────────────────────┘

Callable 异步处理​

Callable 是最简单的异步处理方式,Spring 会将返回的 Callable 提交到线程池执行:

import java.util.concurrent.Callable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/async")
public class AsyncController {

/**
* 使用 Callable 进行异步处理
* Servlet 线程立即释放,Callable 在单独线程中执行
*/
@GetMapping("/callable")
public Callable<String> callableExample() {
// 返回 Callable,Servlet 线程立即释放
return () -> {
// 此代码在独立线程中执行
Thread.sleep(2000); // 模拟耗时操作
return "异步处理完成";
};
}

/**
* 带业务的 Callable 示例
*/
@GetMapping("/users/{id}")
public Callable<User> getUser(@PathVariable Long id) {
return () -> {
// 异步查询用户
return userService.findById(id);
};
}
}

解释:

  1. 控制器返回 Callable 后,Servlet 线程立即释放
  2. Spring 将 Callable 提交到 AsyncTaskExecutor 执行
  3. 执行完成后,Spring 将结果写回响应

DeferredResult 异步处理​

DeferredResult 提供了更灵活的异步处理方式,可以在任意线程中设置结果:

import org.springframework.web.context.request.async.DeferredResult;
import java.util.concurrent.CompletableFuture;

@RestController
@RequestMapping("/api/async")
public class DeferredResultController {

@Autowired
private OrderService orderService;

/**
* 使用 DeferredResult 进行异步处理
* 适合需要在其他线程(如消息队列、定时任务)中设置结果的场景
*/
@GetMapping("/deferred")
public DeferredResult<String> deferredExample() {
// 创建 DeferredResult,设置超时时间
DeferredResult<String> deferredResult = new DeferredResult<>(5000L);

// 设置超时回调
deferredResult.onTimeout(() -> {
deferredResult.setErrorResult("请求超时");
});

// 设置完成回调
deferredResult.onCompletion(() -> {
System.out.println("请求处理完成");
});

// 在其他线程中设置结果
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
// 设置成功结果
deferredResult.setResult("异步处理完成");
} catch (InterruptedException e) {
// 设置错误结果
deferredResult.setErrorResult("处理失败");
}
});

return deferredResult;
}

/**
* 结合消息队列的示例
*/
@PostMapping("/orders")
public DeferredResult<Order> createOrder(@RequestBody OrderDTO orderDTO) {
DeferredResult<Order> deferredResult = new DeferredResult<>(30000L);

// 发送订单到消息队列
orderService.submitOrder(orderDTO, deferredResult);

return deferredResult;
}
}

// 订单服务
@Service
public class OrderService {

private final Map<String, DeferredResult<Order>> pendingOrders = new ConcurrentHashMap<>();

public void submitOrder(OrderDTO orderDTO, DeferredResult<Order> deferredResult) {
String orderId = UUID.randomUUID().toString();

// 保存 DeferredResult,等待处理结果
pendingOrders.put(orderId, deferredResult);

// 发送到消息队列
rabbitTemplate.convertAndSend("order.queue", orderDTO);
}

/**
* 消息队列消费者调用此方法设置结果
*/
public void completeOrder(String orderId, Order order) {
DeferredResult<Order> deferredResult = pendingOrders.remove(orderId);
if (deferredResult != null) {
deferredResult.setResult(order);
}
}
}

Callable vs DeferredResult:

特性CallableDeferredResult
执行方式Spring 线程池执行任意线程设置结果
适用场景简单异步计算复杂异步流程、消息队列
灵活性较低较高
超时处理需要手动处理内置超时支持
结果设置返回值调用 setResult()

ResponseBodyEmitter 流式响应​

ResponseBodyEmitter 用于向响应中多次写入数据,适合流式传输场景:

import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import java.io.IOException;

@RestController
@RequestMapping("/api/stream")
public class StreamController {

/**
* 使用 ResponseBodyEmitter 进行流式响应
* 可以多次发送数据
*/
@GetMapping("/events")
public ResponseBodyEmitter streamEvents() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter(60000L);

// 异步发送数据
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
// 发送数据
emitter.send("Event " + i + "\n");
Thread.sleep(1000);
}
// 完成发送
emitter.complete();
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e);
}
}).start();

return emitter;
}

/**
* 发送 JSON 数据
*/
@GetMapping("/json-stream")
public ResponseBodyEmitter streamJson() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();

new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
EventData data = new EventData(i, "Event " + i, LocalDateTime.now());
emitter.send(data, MediaType.APPLICATION_JSON);
Thread.sleep(500);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
}).start();

return emitter;
}
}

StreamingResponseBody 文件下载​

对于大文件下载,使用 StreamingResponseBody 可以避免一次性加载到内存:

import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.InputStream;
import java.io.OutputStream;

@RestController
@RequestMapping("/api/download")
public class DownloadController {

/**
* 使用 StreamingResponseBody 下载大文件
* 流式写入,不占用大量内存
*/
@GetMapping("/large-file")
public ResponseEntity<StreamingResponseBody> downloadLargeFile() {
StreamingResponseBody stream = outputStream -> {
try (InputStream inputStream = new FileInputStream("large-file.dat")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
};

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"large-file.dat\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(stream);
}

/**
* 动态生成 CSV 导出
*/
@GetMapping("/export")
public ResponseEntity<StreamingResponseBody> exportData() {
StreamingResponseBody stream = outputStream -> {
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) {
// 写入 CSV 头
writer.println("ID,Name,Email,CreatedAt");

// 分批查询并写入数据
int page = 0;
List<User> users;
do {
users = userRepository.findAll(PageRequest.of(page++, 1000)).getContent();
for (User user : users) {
writer.printf("%d,%s,%s,%s%n",
user.getId(),
user.getName(),
user.getEmail(),
user.getCreatedAt()
);
}
writer.flush();
} while (!users.isEmpty());
}
};

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"users.csv\"")
.contentType(MediaType.parseMediaType("text/csv;charset=UTF-8"))
.body(stream);
}
}

异步配置​

@Configuration
public class AsyncConfig implements WebMvcConfigurer {

/**
* 配置异步请求处理
*/
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 设置默认超时时间(30秒)
configurer.setDefaultTimeout(30000);

// 配置异步任务执行器
configurer.setTaskExecutor(mvcTaskExecutor());

// 注册 DeferredResult 拦截器
configurer.registerDeferredResultInterceptors(new DeferredResultInterceptor());

// 注册 Callable 拦截器
configurer.registerCallableInterceptors(new CallableInterceptor());
}

/**
* 自定义异步任务执行器
*/
@Bean
public AsyncTaskExecutor mvcTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("mvc-async-");
executor.initialize();
return executor;
}
}

异步请求拦截器​

import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;

@Component
public class AsyncRequestInterceptor implements CallableProcessingInterceptor, DeferredResultProcessingInterceptor {

@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) {
// 异步处理前调用(在 Servlet 线程中)
System.out.println("异步处理开始");
}

@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object result) {
// 异步处理完成后调用
System.out.println("异步处理完成,结果: " + result);
}

@Override
public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) {
// 错误处理
return "异步处理错误: " + t.getMessage();
}

@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) {
// 超时处理
return "请求超时";
}
}

异步处理最佳实践​

1. 合理配置线程池:

@Bean
public AsyncTaskExecutor mvcTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 根据 CPU 核心数和任务类型配置
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(500);
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}

2. 设置合理的超时时间:

// 全局超时配置
configurer.setDefaultTimeout(30000);

// 单个请求超时
DeferredResult<String> result = new DeferredResult<>(5000L);
result.onTimeout(() -> result.setErrorResult("请求超时"));

3. 错误处理:

DeferredResult<String> result = new DeferredResult<>();
result.onTimeout(() -> result.setErrorResult(
new ErrorResponse("TIMEOUT", "请求超时")));
result.onError(e -> result.setErrorResult(
new ErrorResponse("ERROR", "处理失败")));

4. 避免阻塞:

// 不推荐:在 Callable 中执行阻塞操作
return () -> {
Thread.sleep(10000); // 阻塞异步线程池
return result;
};

// 推荐:使用非阻塞 API
return () -> {
return CompletableFuture.supplyAsync(() -> {
// 使用独立线程池
});
};

小结​

本章我们学习了:

  1. RESTful API 设计:资源设计和 HTTP 方法映射
  2. 请求处理:各种参数获取方式
  3. 响应处理:统一响应格式和 ResponseEntity
  4. 参数验证:验证注解和自定义验证器
  5. 异常处理:全局异常处理器
  6. 跨域处理:多种跨域解决方案
  7. 文件上传下载:文件处理和配置
  8. 拦截器:请求拦截和预处理
  9. HTTP 客户端:RestClient 的使用、配置和错误处理
  10. WebSocket 实时通信:STOMP 消息、广播和点对点消息、安全配置
  11. SSE 服务器推送:SseEmitter 的使用、事件构建、实时数据推送
  12. 异步请求处理:Callable、DeferredResult、ResponseBodyEmitter、StreamingResponseBody 的使用场景和最佳实践

练习​

  1. 设计并实现一个完整的用户管理 RESTful API
  2. 为 API 添加参数验证
  3. 实现全局异常处理
  4. 配置 CORS 允许前端跨域访问
  5. 实现文件上传和下载功能
  6. 实现一个简单的聊天室(使用 WebSocket)
  7. 实现股票价格实时推送(使用 SSE)
  8. 实现异步长时间任务处理(使用 DeferredResult)
  9. 实现大文件流式下载(使用 StreamingResponseBody)