您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值
## 一、引言
在现代Web应用开发中,统一处理控制器返回值是一个常见的需求。SpringMVC框架提供了强大的全局异常处理和响应体控制机制,其中`@ControllerAdvice`注解和`ResponseBodyAdvice`接口是两个核心组件。本文将深入探讨如何利用这两个特性构建统一的响应处理体系。
## 二、@ControllerAdvice基础
### 2.1 基本概念与作用
`@ControllerAdvice`是Spring 3.2引入的注解,用于定义全局控制器增强器:
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
// 可指定包、类、注解等过滤条件
}
主要应用场景包括:
- 全局异常处理(@ExceptionHandler
)
- 全局数据绑定(@ModelAttribute
)
- 全局数据预处理(@InitBinder
)
@ControllerAdvice
public class GlobalControllerAdvice {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
return ResponseEntity.status(500).body(ex.getMessage());
}
}
ResponseBodyAdvice
接口定义:
public interface ResponseBodyAdvice<T> {
boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType);
@Nullable
T beforeBodyWrite(@Nullable T body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response);
}
工作流程:
1. 控制器方法执行完成
2. Spring检查是否存在匹配的ResponseBodyAdvice
3. 调用beforeBodyWrite
进行后处理
@ControllerAdvice
public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true; // 对所有返回值生效
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
return new ResponseResult<>(200, "success", body);
}
}
标准响应结构示例:
public class ResponseResult<T> {
private int code;
private String message;
private T data;
private long timestamp = System.currentTimeMillis();
// 构造方法、getter/setter省略
}
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
// 排除特定返回类型
private static final Set<Class<?>> EXCLUDE_CLASSES = Set.of(
ResponseResult.class,
ResponseEntity.class
);
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return !EXCLUDE_CLASSES.contains(returnType.getParameterType());
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// 处理String类型特殊转换
if (body instanceof String) {
return JSON.toJSONString(new ResponseResult<>(200, "success", body));
}
// 处理void返回类型
if (returnType.getParameterType() == void.class) {
return new ResponseResult<>(200, "success", null);
}
return new ResponseResult<>(200, "success", body);
}
}
@ExceptionHandler(BusinessException.class)
public ResponseResult<Void> handleBusinessException(BusinessException ex) {
return new ResponseResult<>(ex.getErrorCode(), ex.getMessage(), null);
}
@Override
public Object beforeBodyWrite(/* 参数省略 */) {
response.getHeaders().add("X-Response-Processed", "true");
// ...其他处理逻辑
}
if (body instanceof Page<?> page) {
return new ResponseResult<>(200, "success",
new PageResult<>(page.getContent(), page.getTotalElements()));
}
supports()
方法精确控制处理范围beforeBodyWrite
中避免耗时操作问题1:String类型转换异常
解决方案:
if (selectedConverterType == MappingJackson2HttpMessageConverter.class
&& body instanceof String) {
return body; // 跳过处理
}
问题2:文件下载被包装
解决方案:
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return !returnType.hasMethodAnnotation(GetMapping.class)
&& !returnType.getParameterType().equals(Resource.class);
}
@SpringBootTest
public class ResponseAdviceTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@BeforeEach
void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
void testNormalResponse() throws Exception {
mockMvc.perform(get("/api/user/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.username").exists());
}
}
@Bean
public OpenApiCustomiser openApiCustomiser() {
return openApi -> {
openApi.getComponents()
.addSchemas("ResponseResult",
new Schema<ResponseResult<?>>()
.addProperty("code", new IntegerSchema())
.addProperty("message", new StringSchema())
.addProperty("data", new ObjectSchema()));
};
}
@Override
public Object beforeBodyWrite(/* 参数省略 */) {
String version = request.getHeaders().getFirst("X-API-Version"));
if ("v2".equals(version)) {
return new ResponseResultV2(/* ... */);
}
// 默认处理...
}
// 响应体结构
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseResult<T> {
private int code;
private String message;
private T data;
private long timestamp = System.currentTimeMillis();
public static <T> ResponseResult<T> success(T data) {
return new ResponseResult<>(200, "success", data);
}
}
// 全局处理实现
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
private final ObjectMapper objectMapper;
// 排除列表
private static final Set<Class<?>> EXCLUDES = Set.of(
ResponseResult.class,
ResponseEntity.class,
Resource.class
);
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return !EXCLUDES.contains(returnType.getParameterType())
&& !returnType.hasMethodAnnotation(IgnoreResponseWrap.class);
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// 处理void返回
if (returnType.getParameterType() == void.class) {
return ResponseResult.success(null);
}
// 处理String特殊类型
if (body instanceof String) {
try {
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return objectMapper.writeValueAsString(ResponseResult.success(body));
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON processing error", e);
}
}
return ResponseResult.success(body);
}
}
// 自定义排除注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreResponseWrap {}
通过本文的详细讲解,相信读者已经掌握了在SpringMVC中使用@ControllerAdvice
和ResponseBodyAdvice
实现统一返回值处理的完整方案。这种模式不仅能提高开发效率,还能显著提升系统的可维护性和一致性。
“`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。