常见的http状态码 + ResponseEntity
常见的http状态码
@ResponseStatus(HttpStatus.CREATED)
是 Spring Framework 中的注解,用于指定 HTTP 响应状态码。
1. 基本说明
HttpStatus.CREATED
对应 HTTP 状态码201
- 表示请求成功且创建了新的资源
- 通常用于 POST 请求的处理方法上
2. 使用场景和示例
基本使用
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED) // 将返回 201 状态码
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
}
带有详细响应的使用
@RestController
@RequestMapping("/api/products")
public class ProductController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product savedProduct = productService.save(product);
// 创建资源的 URI
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedProduct.getId())
.toUri();
// 返回 201 状态码和资源位置
return ResponseEntity
.created(location)
.body(savedProduct);
}
}
3. 常用的 HTTP 状态码注解
// 成功相关
@ResponseStatus(HttpStatus.OK) // 200 - 请求成功,返回资源或结果
@ResponseStatus(HttpStatus.CREATED) // 201 - 资源创建成功
@ResponseStatus(HttpStatus.NO_CONTENT) // 204 - 成功但无返回内容
// 客户端错误
@ResponseStatus(HttpStatus.BAD_REQUEST) // 400 - 请求格式错误,客户端请求无效
@ResponseStatus(HttpStatus.UNAUTHORIZED) // 401 - 未授权,需身份验证
@ResponseStatus(HttpStatus.FORBIDDEN) // 403 - 禁止访问
@ResponseStatus(HttpStatus.NOT_FOUND) // 404 - 资源未找到
@ResponseStatus(HttpStatus.CONFLICT) // 409 - 资源冲突
// 服务器错误
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 500 - 服务器内部错误
如果需要更灵活地设置响应内容(如返回自定义的响应体或头部信息),可以使用 ResponseEntity,而不是 @ResponseStatus
@ResponseStatus
是一种简洁的方式,用于指定固定的 HTTP 状态码,但它的功能相对有限。如果需要更灵活地设置响应内容(如自定义响应体、响应头、动态状态码等),可以使用 ResponseEntity
。
1. 为什么选择 ResponseEntity
?
ResponseEntity
是 Spring 提供的一个强大的工具,用于构建 HTTP 响应。它允许你:
- 动态设置 HTTP 状态码
- 自定义响应头
- 返回复杂的响应体
- 更灵活地处理不同的场景
2. ResponseEntity
的基本用法
示例 1: 返回自定义状态码和响应体
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody UserRequest userRequest) {
userService.createUser(userRequest);
return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
}
ResponseEntity.status(HttpStatus.CREATED)
:设置 HTTP 状态码为201 Created
。.body("User created successfully")
:设置响应体为自定义的字符串。
示例 2: 返回带有响应头的信息
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody UserRequest userRequest) {
userService.createUser(userRequest);
// 设置自定义响应头
HttpHeaders headers = new HttpHeaders();
headers.add("Custom-Header", "CustomHeaderValue");
return ResponseEntity.status(HttpStatus.CREATED)
.headers(headers)
.body("User created successfully");
}
HttpHeaders
:用于设置响应头。.headers(headers)
:将自定义的响应头添加到响应中。
示例 3: 返回带有资源位置的响应
在 RESTful API 中,创建资源后通常会返回 Location
头,指向新创建资源的 URI。
@PostMapping("/users")
public ResponseEntity<Void> createUser(@RequestBody UserRequest userRequest) {
Long userId = userService.createUser(userRequest);
// 构造资源的 URI
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(userId)
.toUri();
// 返回 201 Created 和 Location 头
return ResponseEntity.created(location).build();
}
ServletUriComponentsBuilder
:用于动态构建资源的 URI。.created(location)
:设置状态码为201 Created
,并添加Location
头。
示例 4: 动态返回不同的状态码
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody UserRequest userRequest) {
if (userService.isDuplicate(userRequest)) {
return ResponseEntity.status(HttpStatus.CONFLICT).body("User already exists");
}
userService.createUser(userRequest);
return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
}
- 如果用户已存在,返回
409 Conflict
。 - 如果创建成功,返回
201 Created
。
3. 与 @ResponseStatus
的对比
特性 | @ResponseStatus | ResponseEntity |
---|---|---|
设置状态码 | 固定状态码,不能动态设置 | 可以动态设置状态码 |
自定义响应体 | 不支持 | 支持返回任意类型的响应体 |
自定义响应头 | 不支持 | 支持通过 HttpHeaders 设置响应头 |
适用场景 | 简单场景,状态码固定 | 复杂场景,需要动态响应 |
代码简洁性 | 更简洁 | 代码稍显冗长 |
4. 综合示例:结合异常处理
自定义异常类
@ResponseStatus(HttpStatus.NOT_FOUND) // 默认返回 404
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
使用 ResponseEntity
处理异常
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
return ResponseEntity.ok(user); // 返回 200 和用户信息
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse("USER_NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}
@ExceptionHandler
:捕获异常并返回自定义的响应。ErrorResponse
:自定义的错误响应体。
5. 总结
@ResponseStatus
:适合简单场景,状态码固定,代码简洁。ResponseEntity
:适合复杂场景,允许动态设置状态码、响应头和响应体。
如果你的需求需要动态响应内容(如返回资源位置、错误信息、或自定义头部信息),ResponseEntity
是更灵活的选择。通过使用 ResponseEntity
,我们可以更精确地控制 HTTP 响应的各个方面,使 API 更加灵活和专业。这对于构建复杂的 RESTful API 特别有用,因为它允许我们根据不同的业务场景返回不同的响应。
@RestController/
@ResponseBody + ResponseEntity
使用 @RestController
或 @ResponseBody
返回对象时,Spring 会自动将返回的对象序列化为 JSON 并写入响应体,@RestController
或 @ResponseBody
返回的对象会自动设置状态码为 200 OK
。 缺点:无法直接设置响应头或动态状态码。使用 @RestController 或 @ResponseBody 返回对象 如果需要返回特定的 HTTP 状态码,可以结合 ResponseEntity 使用。
使用 @RestController
或 @ResponseBody
直接返回对象时的主要缺点是无法直接设置响应头或动态状态码。以下是这些限制的详细说明和解决方案:
1. 基本限制示例
@RestController
@RequestMapping("/api/users")
public class UserController {
// 直接返回对象的方式
@GetMapping("/{id}")
@ResponseBody
public User getUser(@PathVariable Long id) {
return userService.findById(id); // 只能返回 200 OK
}
// 无法直接设置响应头
@PostMapping
@ResponseBody
public User createUser(@RequestBody User user) {
return userService.save(user); // 应该返回 201 Created,但只能是 200 OK
}
}
2. 主要限制
-
状态码限制:
- 默认总是返回 200 OK
- 无法动态设置状态码
- 无法返回创建成功(201)等特定状态码
-
响应头限制:
- 无法添加自定义响应头
- 无法设置 Content-Disposition 等特殊头部
- 无法控制缓存相关头部
-
错误处理限制:
- 无法直接返回错误状态码
- 需要依赖全局异常处理
3. 解决方案
方案一:使用 ResponseEntity
@RestController
@RequestMapping("/api/users")
public class UserController {
// 使用 ResponseEntity 返回特定状态码
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(savedUser);
}
// 设置自定义响应头
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(user -> ResponseEntity.ok()
.header("X-Custom-Header", "value")
.body(user))
.orElse(ResponseEntity.notFound().build());
}
}
方案二:使用 HttpServletResponse
@RestController
@RequestMapping("/api/users")
public class UserController {
// 注入 HttpServletResponse
@PostMapping("/manual")
@ResponseBody
public User createUserManual(
@RequestBody User user,
HttpServletResponse response) {
User savedUser = userService.save(user);
response.setStatus(HttpServletResponse.SC_CREATED);
response.setHeader("X-Custom-Header", "value");
return savedUser;
}
}
方案三:使用统一响应包装类
// 统一响应包装类
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private Map<String, String> headers = new HashMap<>();
// 构造方法和静态工厂方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data);
}
public static <T> ApiResponse<T> created(T data) {
return new ApiResponse<>(201, "Created", data);
}
}
// 使用响应包装类的控制器
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ApiResponse<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return ApiResponse.created(savedUser);
}
}
// 响应拦截器
@Component
public class ApiResponseInterceptor implements ResponseBodyAdvice<ApiResponse<?>> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return ApiResponse.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public ApiResponse<?> beforeBodyWrite(ApiResponse<?> body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (body != null) {
response.setStatusCode(HttpStatus.valueOf(body.getCode()));
body.getHeaders().forEach((key, value) ->
response.getHeaders().add(key, value));
}
return body;
}
}
方案四:使用 AOP 拦截
@Aspect
@Component
public class ResponseHeaderAspect {
@Around("@annotation(responseHeaders)")
public Object addResponseHeaders(ProceedingJoinPoint joinPoint,
ResponseHeaders responseHeaders) throws Throwable {
Object result = joinPoint.proceed();
HttpServletResponse response =
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
if (response != null) {
for (Header header : responseHeaders.value()) {
response.setHeader(header.name(), header.value());
}
}
return result;
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseHeaders {
Header[] value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Header {
String name();
String value();
}
// 使用示例
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
@ResponseHeaders({
@Header(name = "X-Custom-Header", value = "value"),
@Header(name = "X-Created-By", value = "system")
})
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
方案五:使用过滤器
@Component
public class ResponseModifierFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
ContentCachingResponseWrapper responseWrapper =
new ContentCachingResponseWrapper(httpResponse);
chain.doFilter(request, responseWrapper);
// 修改响应
if (isUserCreationEndpoint(request)) {
responseWrapper.setStatus(HttpServletResponse.SC_CREATED);
responseWrapper.setHeader("X-Custom-Header", "value");
}
responseWrapper.copyBodyToResponse();
}
}
特性 | @RestController / @ResponseBody | ResponseEntity | HttpServletResponse |
---|---|---|---|
默认状态码 | 固定为 200 OK | 可动态设置 | 可动态设置 |
自定义响应头 | 不支持 | 支持 | 支持 |
动态状态码 | 不支持 | 支持 | 支持 |
代码复杂性 | 简单 | 中等 | 较高 |
推荐场景 | 简单的 RESTful API | 需要动态状态码或自定义头部的场景 | 特殊场景(如文件流、低级操作) |
详细解释如何使用 @RestController
或 @ResponseBody
结合 ResponseEntity
来返回对象和特定的 HTTP 状态码。
1. 基本用法
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok) // 返回 200 OK
.orElse(ResponseEntity.notFound().build()); // 返回 404 Not Found
}
}
2. 统一响应格式
// 统一响应格式
public class ApiResponse<T> {
private int code; // 状态码
private String message; // 消息
private T data; // 数据
private LocalDateTime timestamp = LocalDateTime.now(); // 时间戳
// 成功静态方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data);
}
// 错误静态方法
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
// 构造方法、getter和setter
}
3. CRUD 操作示例
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
// 构造注入
public UserController(UserService userService) {
this.userService = userService;
}
// 创建用户
@PostMapping
public ResponseEntity<ApiResponse<User>> createUser(@RequestBody @Valid User user) {
User savedUser = userService.save(user);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success(savedUser));
}
// 获取用户
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<User>> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(user -> ResponseEntity.ok(ApiResponse.success(user)))
.orElse(ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error(404, "User not found")));
}
// 更新用户
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<User>> updateUser(
@PathVariable Long id,
@RequestBody @Valid User user) {
return userService.findById(id)
.map(existingUser -> {
User updatedUser = userService.update(id, user);
return ResponseEntity.ok(ApiResponse.success(updatedUser));
})
.orElse(ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error(404, "User not found")));
}
// 删除用户
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable Long id) {
if (userService.existsById(id)) {
userService.deleteById(id);
return ResponseEntity.ok(ApiResponse.success(null));
}
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error(404, "User not found"));
}
// 获取用户列表(分页)
@GetMapping
public ResponseEntity<ApiResponse<Page<User>>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Page<User> users = userService.findAll(PageRequest.of(page, size));
return ResponseEntity.ok(ApiResponse.success(users));
}
}
4. 异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理验证错误
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationErrors(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error(400, "Validation failed", errors));
}
// 处理业务逻辑异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException ex) {
return ResponseEntity
.status(ex.getStatus())
.body(ApiResponse.error(ex.getStatus().value(), ex.getMessage()));
}
// 处理其他未知异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleUnknownException(Exception ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(500, "Internal server error"));
}
}
5. 自定义业务异常
public class BusinessException extends RuntimeException {
private final HttpStatus status;
public BusinessException(String message, HttpStatus status) {
super(message);
this.status = status;
}
public HttpStatus getStatus() {
return status;
}
}
6. 带条件的响应示例
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// 创建订单
@PostMapping
public ResponseEntity<ApiResponse<Order>> createOrder(@RequestBody @Valid OrderRequest request) {
// 检查库存
if (!inventoryService.checkStock(request)) {
return ResponseEntity
.status(HttpStatus.CONFLICT)
.body(ApiResponse.error(409, "Insufficient stock"));
}
// 检查用户信用
if (!userService.checkCredit(request.getUserId())) {
return ResponseEntity
.status(HttpStatus.PAYMENT_REQUIRED)
.body(ApiResponse.error(402, "Insufficient credit"));
}
// 创建订单
Order order = orderService.createOrder(request);
// 如果是加急订单,添加特殊响应头
if (request.isUrgent()) {
return ResponseEntity
.status(HttpStatus.CREATED)
.header("X-Priority", "High")
.body(ApiResponse.success(order));
}
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success(order));
}
}
7. 文件上传响应示例
@RestController
@RequestMapping("/api/files")
public class FileController {
@PostMapping("/upload")
public ResponseEntity<ApiResponse<FileInfo>> uploadFile(
@RequestParam("file") MultipartFile file) {
try {
FileInfo savedFile = fileService.store(file);
return ResponseEntity
.status(HttpStatus.CREATED)
.header("Location", "/api/files/" + savedFile.getId())
.body(ApiResponse.success(savedFile));
} catch (IOException e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(500, "Failed to upload file"));
}
}
@GetMapping("/download/{id}")
public ResponseEntity<Resource> downloadFile(@PathVariable String id) {
Resource file = fileService.loadAsResource(id);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"")
.body(file);
}
}
8. 返回流式数据
如果需要返回流式数据(如文件下载),可以结合 ResponseEntity
和 InputStreamResource
。
示例:返回文件下载
@RestController
@RequestMapping("/files")
public class FileController {
@GetMapping("/download/{filename}")
public ResponseEntity<InputStreamResource> downloadFile(@PathVariable String filename) throws IOException {
File file = fileService.getFile(filename);
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName())
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
}
-
响应:
-
状态码:
200 OK
-
响应头:
Content-Disposition: attachment; filename=myfile.txt
-
响应体:文件内容
-
9. 使用建议
-
统一响应格式:
- 使用统一的响应封装类(如
ApiResponse
) - 包含必要的状态码、消息和数据字段
- 添加时间戳便于调试和日志追踪
- 使用统一的响应封装类(如
-
适当的状态码:
- 200: 成功获取资源
- 201: 成功创建资源
- 204: 成功处理但无返回内容
- 400: 请求参数错误
- 401: 未认证
- 403: 无权限
- 404: 资源不存在
- 409: 资源冲突
- 500: 服务器错误
-
异常处理:
- 使用全局异常处理器统一处理异常
- 区分业务异常和系统异常
- 提供清晰的错误信息
-
响应头设置:
- 根据需要设置自定义响应头
- 文件操作时设置适当的 Content-Type 和 Content-Disposition
-
验证处理:
- 使用
@Valid
注解进行请求验证 - 提供详细的验证错误信息
- 使用
通过这种方式,可以构建出统一、规范的 API 响应,便于客户端处理和调试。
10. 最佳实践建议
- 优先使用
@RestController
或@ResponseBody
:- 如果不需要动态设置状态码或响应头,直接返回对象即可,简单高效。
- 使用
ResponseEntity
解决局限性:- 当需要动态设置状态码或自定义响应头时,推荐使用
ResponseEntity
,它是 Spring 提供的标准解决方案,代码优雅且易于维护。
- 当需要动态设置状态码或自定义响应头时,推荐使用
- 仅在特殊场景下使用
HttpServletResponse
:- 如果需要直接操作底层响应流(如文件下载、流式数据),可以使用
HttpServletResponse
。
- 如果需要直接操作底层响应流(如文件下载、流式数据),可以使用
-
优先使用 ResponseEntity:
- 最清晰和直接的解决方案
- 提供完整的响应控制
- 类型安全且易于维护
-
统一响应格式:
- 定义统一的响应包装类
- 使用响应拦截器统一处理
- 便于维护和扩展
-
分层处理:
- 控制器层:业务逻辑和基本响应
- 拦截器层:通用响应头和状态码
- 过滤器层:全局响应修改
-
异常处理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<String>> handleException(Exception ex) {
ApiResponse<String> response = ApiResponse.error(
500,
"Internal Server Error",
ex.getMessage()
);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(response);
}
}
- 文档和注释:
/**
* 用户控制器
* 注意:直接返回对象时无法设置状态码和响应头
* 建议使用 ResponseEntity 或其他替代方案
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
// 控制器方法...
}
通过以上方案,可以克服 @RestController
和 @ResponseBody
的限制,实现更灵活的响应控制。选择合适的方案取决于具体需求和项目架构。