SpringMVC中如何利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值

发布时间:2021-10-19 17:27:32 作者:柒染
来源:亿速云 阅读:147
# 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

2.2 基本用法示例

@ControllerAdvice
public class GlobalControllerAdvice {
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return ResponseEntity.status(500).body(ex.getMessage());
    }
}

三、ResponseBodyAdvice深入解析

3.1 接口定义与工作原理

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进行后处理

3.2 典型实现案例

@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);
    }
}

四、统一返回值封装实战

4.1 设计统一响应体

标准响应结构示例:

public class ResponseResult<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp = System.currentTimeMillis();
    
    // 构造方法、getter/setter省略
}

4.2 完整实现方案

@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);
    }
}

五、高级应用场景

5.1 结合异常处理

@ExceptionHandler(BusinessException.class)
public ResponseResult<Void> handleBusinessException(BusinessException ex) {
    return new ResponseResult<>(ex.getErrorCode(), ex.getMessage(), null);
}

5.2 动态修改响应头

@Override
public Object beforeBodyWrite(/* 参数省略 */) {
    response.getHeaders().add("X-Response-Processed", "true");
    // ...其他处理逻辑
}

5.3 分页结果特殊处理

if (body instanceof Page<?> page) {
    return new ResponseResult<>(200, "success", 
            new PageResult<>(page.getContent(), page.getTotalElements()));
}

六、性能优化与注意事项

6.1 性能考量

  1. 过滤不必要处理:通过supports()方法精确控制处理范围
  2. 缓存判断结果:对固定返回类型可缓存判断结果
  3. 避免复杂逻辑beforeBodyWrite中避免耗时操作

6.2 常见问题解决方案

问题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);
}

七、测试验证方案

7.1 单元测试示例

@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());
    }
}

7.2 集成测试要点

  1. 验证不同Content-Type的处理
  2. 测试异常场景的包装
  3. 验证特殊类型(如文件下载)是否被正确排除

八、扩展思考

8.1 与Swagger的集成

@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()));
    };
}

8.2 多版本API支持

@Override
public Object beforeBodyWrite(/* 参数省略 */) {
    String version = request.getHeaders().getFirst("X-API-Version"));
    if ("v2".equals(version)) {
        return new ResponseResultV2(/* ... */);
    }
    // 默认处理...
}

九、总结与最佳实践

9.1 核心优势总结

  1. 代码复用:避免每个控制器重复包装逻辑
  2. 一致性:保证全系统响应格式统一
  3. 可维护性:修改响应格式只需调整一处

9.2 推荐实践方案

  1. 明确排除规则:定义不需要包装的返回类型
  2. 版本控制:考虑API演进需求
  3. 完善文档:记录响应格式规范
  4. 监控告警:对异常响应码建立监控

附录:完整代码示例

// 响应体结构
@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中使用@ControllerAdviceResponseBodyAdvice实现统一返回值处理的完整方案。这种模式不仅能提高开发效率,还能显著提升系统的可维护性和一致性。 “`

推荐阅读:
  1. python中怎么处理多任务和返回值
  2. SpringMVC中怎么统一异常处理

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

springmvc @controlleradvice responsebodyadvice

上一篇:jsp到servlet后台服务通信过程的示例分析

下一篇:git如何只应用暂存区的部分文件

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》