写在前面的话
前几篇博文,大致了解了SpringMVC
请求流程中的参数与返回值的源码分析,后续的几篇博文,会将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC
带来的定制和扩展能力。
本篇文章先介绍一下 ResponseBodyAdvice 相关内容。
相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《学会 SpringMVC 系列 · 剖析初始化》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
ResponseBodyAdvice
技术说明
0、ResponseBodyAdvice 是 Spring Framework 的 Web 模块中的一个接口,它允许你在将响应体写入 HTTP 响应之前拦截和修改它。它提供了一种全局定制响应处理逻辑的方式,适用于 Spring MVC 或 Spring WebFlux 应用程序。
1、ResponseBodyAdvice 可以在注解 @ResponseBody 将返回值处理成相应格式之前操作返回值,实现这个接口即可完成相应操作,可用于对response 数据的一些统一封装或者加密等操作。
2、ResponseBodyAdvice 接口和 RequestBodyAdvice 接口类似,RequestBodyAdvice 是请求到Controller 之前拦截,做相应的处理操作,而ResponseBodyAdvice 是对Controller返回的{@code @ResponseBody}or a {@code ResponseEntity} 后,{@code HttpMessageConverter} 类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。
3、实现 ResponseBodyAdvice 接口,需要重写其 supports 和 beforeBodyWrite 方法。
1)supports方法:判断是否要执行beforeBodyWrite方法,true为执行,false不执行。通过该方法可以选择哪些类或那些方法的response要进行处理,其他的不进行处理。
2)beforeBodyWrite方法:对response方法进行具体操作处理。
public interface ResponseBodyAdvice<T> {
/**
* 1、选择是否执行 beforeBodyWrite 方法,返回 true 执行,false 不执行
* 2、通过 supports 方法,可以选择对哪些类或方法的 Response 进行处理
* @param returnType:返回类型
* @param converterType:转换器
* @return :返回 true 则下面的 beforeBodyWrite 执行,否则不执行
*/
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
/**
* 对 Response 处理的具体执行方法
* @param body:响应对象(response)中的响应体
* @param returnType:控制器方法的返回类型
* @param selectedContentType:通过内容协商选择的内容类型
* @param selectedConverterType:选择写入响应的转换器类型
* @param request:当前请求
* @param response:当前响应
* @return :返回传入的主体或修改过的(可能是新的)主体
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
基础示例
@ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 根据返回类型和转换器类型检查是否应用此建议
// 你可以在这里放置任何条件
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
// 在将响应体写入输出流之前修改它
// 你可以在这里检查或修改 'body' 对象
return body;
}
}
总结:ResponseBodyAdvice 接口允许在执行 @ResponseBody 或 ResponseEntity 控制器方法之后,但在使用 HttpMessageConverter 写入响应体之前自定义响应,进行功能增强。通常用于加密,签名,统一数据格式等。
知识拓展
【知识扩展1:与 HandlerMethodReturnValueHandler 区别】
sbdemo4 项目的返回值包装,使用自定义HandlerMethodReturnValueHandler实现,那两个都能实现,有什么区别呢?先参考一下下方GPT的回答。
HandlerMethodReturnValueHandler 和 ResponseBodyAdvice 都是 Spring MVC 中用于处理控制器方法返回值
的扩展点,但它们的功能和使用方式有所不同。
HandlerMethodReturnValueHandler:
功能:HandlerMethodReturnValueHandler 用于处理控制器方法的返回值,并决定如何将返回值转换为响应。它负责控制器方法返回值的处理过程,例如将返回值转换为特定的响应格式(JSON、XML等)、对返回值进行处理、将返回值写入响应等。
使用方式:你可以通过实现 HandlerMethodReturnValueHandler 接口来定义自定义的返回值处理器,并将其注册到 Spring MVC 的配置中。你可以通过配置 WebMvcConfigurer 的 addReturnValueHandlers 方法来注册自定义的返回值处理器。
ResponseBodyAdvice:
功能:ResponseBodyAdvice 用于在将响应返回给客户端之前对响应进行自定义处理。它允许你在响应体写入之前对响应进行修改、加密、压缩等操作。它不负责决定如何将控制器方法的返回值转换为响应,而是在返回值已经被转换为响应体后进行处理。
使用方式:你可以通过实现 ResponseBodyAdvice 接口来定义全局性的响应体处理逻辑,并将其注册到 Spring MVC 的配置中。你可以通过 @ControllerAdvice 或 @RestControllerAdvice 注解来标记全局的响应体处理器。
总的来说,HandlerMethodReturnValueHandler 更加底层,负责控制器方法返回值的处理过程;
而 ResponseBodyAdvice 更加高层,用于在将响应返回给客户端之前对响应进行全局性的自定义处理。
通常情况下,你可以根据具体的需求选择合适的扩展点。
因为 SpringBoot 默认的 ResponseBody 的处理程序就是 HandlerMethodReturnValueHandler(具体是RequestResponseBodyMethodProcessor),所以我们自定义的通常 HandlerMethod 正常无法生效,非要使用HandlerMethod,那么只能替换掉默认的(放到第一个),如果只是想对Controller的所有返回值进行封装,产生上面的效果,使用ResponseBodyAdvice会更加简单一些,总之,改动不会那么大,尽量不要影响框架默认的实现。
【知识扩展2:关于返回值处理总结】
首先想到拦截器,HandlerInterceptor,其 postHandle 方法通常用于在控制器方法执行之后、视图渲染之前执行一些自定义逻辑,它并不直接提供获取返回值的功能。因为在 postHandle 方法被调用时,控制器方法的返回值已经被用于生成响应,拦截器只能对请求和响应进行处理,无法直接获取返回值。
要想处理返回值,可以有如下做法:
1、使用ResponseBodyAdvice:ResponseBodyAdvice 是一个用于全局性响应体处理的接口,在控制器方法返回值转换为响应体之前被调用,你可以在 beforeBodyWrite 方法中获取控制器方法的返回值。处理返回的数据在传递给HttpMessageConverter之前。
2、使用AOP切面:通过定义一个切面,在切面的方法中获取控制器方法的返回值,并进行相应的处理。通过切面拦截控制器方法的执行,你可以在方法执行完毕后获取返回值。
3、在拦截器的 postHandle 方法中,将返回值存储到请求属性中,然后在其他地方从请求属性中获取。这样虽然是间接的方式,但也可以实现获取返回值的目的。
public class MyInterceptor extends HandlerInterceptorAdapter {
private ThreadLocal<Object> returnValueThreadLocal = new ThreadLocal<>();
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在处理器执行后调用,但在视图渲染前调用
System.out.println("Post-handle method is called");
// 在这里获取控制器方法的返回值,并存储到ThreadLocal变量中
Object returnValue = modelAndView != null ? modelAndView.getModel().get("handlerReturnValue") : null;
returnValueThreadLocal.set(returnValue);
}
// 提供一个公共方法,供其他组件调用获取返回值
public Object getControllerReturnValue() {
return returnValueThreadLocal.get();
}
}
【知识扩展3:@ControllerAdvice】
ResponseBodyAdvice 自定义使用过程中,加上了@ControllerAdvice注解,有什么用?
顾名思义,@ControllerAdvice就是@Controller 的增强版。@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用。
这里不展开介绍,框架的 GlobalExceptionHandler 全局异常处理就是用了这个方式。
1、在使用ResponseBodyAdvice时,通常需要将其标注为@ControllerAdvice注解的类,该注解用于定义全局的控制器增强,可以对所有的Controller进行统一的处理,当我们在@ControllerAdvice注解的类中实现ResponseBodyAdvice接口时,就可以对所有Controller方法返回的响应体进行统一处理;
2、经测试,不添加@ControllerAdvice注解,或仅使用@Component注解,功能都是无效的;
【知识扩展4:@ControllerAdvice 和 @RestControllerAdvice 的区别】
@ControllerAdvice 和 @RestControllerAdvice 都是 Spring 中用于定义全局异常处理、数据绑定、模型数据处理的注解。两者的主要区别在于它们如何处理控制器的返回值。
@ControllerAdvice 是一个更通用的注解,用于为控制器提供全局的异常处理、数据绑定等功能。它适用于处理所有类型的控制器,包括返回视图名称的控制器。使用这个注解时,返回的对象通常是一个视图名称,或者是包含视图名称和模型数据的 ModelAndView 对象。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception ex) {
return "error"; // 返回视图名称
}
}
@RestControllerAdvice 是 @ControllerAdvice 的一个特化版本,专门用于 RESTful Web 服务。它与 @ControllerAdvice 的区别在于,它隐式地在类的每个方法上添加了 @ResponseBody 注解。因此,返回的对象会自动序列化为 JSON 或 XML 等格式,写入响应体。
@RestControllerAdvice
public class GlobalRestExceptionHandler {
@ExceptionHandler(Exception.class)
public ErrorResponse handleException(Exception ex) {
return new ErrorResponse("Error occurred", ex.getMessage()); // 返回 JSON 对象
}
}
当 @ControllerAdvice
和 @RestControllerAdvice
用于实现 ResponseBodyAdvice
时,两者的行为是相同的,都会在响应体写入前提供一个拦截点,无论返回类型是 View
、ModelAndView
还是 JSON、XML 等。
源码知识回顾
本篇为 SpringMVC 源码分析系列文章,正片开始前,先总结回顾一下全流程。
【一次请求的主链路节点】
DispatcherServlet#doDispatch(入口方法)
DispatcherServlet#getHandler(根据path
找到对应的HandlerExecutionChain
)
DispatcherServlet#getHandlerAdapter(根据handle
找到对应的HandlerAdapter
)
HandlerExecutionChain#applyPreHandle(触发拦截器的前置逻辑)
AbstractHandlerMethodAdapter#handle(核心逻辑)
HandlerExecutionChain#applyPostHandle(触发拦截器的后置逻辑)
【核心handle方法的主链路节点】
RequestMappingHandlerAdapter#handleInternal(入口方法)
RequestMappingHandlerAdapter#invokeHandlerMethod(入口方法2)
ServletInvocableHandlerMethod#invokeAndHandle(入口方法3)
InvocableHandlerMethod#invokeForRequest(参数和实际执行的所在,3.1)
InvocableHandlerMethod#getMethodArgumentValues(参数处理,3.1.1)
InvocableHandlerMethod#doInvoke(实际执行,3.1.2)
HandlerMethodReturnValueHandlerComposite#handleReturnValue(返回处理,3.2)
【针对 @RequestBody 和 @ResponseBody 场景】
总结陈词
本篇博文继请求源码分析后,继续介绍了ResponseBodyAdvice
的用法,无独有偶,请求阶段也有对应的RequestBodyAdvice
,而且读取前和读取后,都可以自定义扩展,欲知后事如何,请听下回分解。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。