RequestBodyAdvice和ResponseBodyAdvice原理详解
创始人
2025-05-28 06:57:36
0

一、源码解析

这是spring 4.2新加的两个接口

1、RequestBodyAdvice类

public interface RequestBodyAdvice {boolean supports(MethodParameter methodParameter, Type targetType,Class> converterType);HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class> converterType) throws IOException;Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class> converterType);@NullableObject handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class> converterType);
}

查看一下谁调用了这个接口的这些方法,可以看到AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters()方法调用了这个接口的方法。

protected  Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {MediaType contentType;boolean noContentType = false;try {contentType = inputMessage.getHeaders().getContentType();}catch (InvalidMediaTypeException ex) {throw new HttpMediaTypeNotSupportedException(ex.getMessage());}if (contentType == null) {noContentType = true;contentType = MediaType.APPLICATION_OCTET_STREAM;}Class contextClass = parameter.getContainingClass();Class targetClass = (targetType instanceof Class ? (Class) targetType : null);if (targetClass == null) {ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);targetClass = (Class) resolvableType.resolve();}HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);Object body = NO_VALUE;EmptyBodyCheckingHttpInputMessage message = null;try {message = new EmptyBodyCheckingHttpInputMessage(inputMessage);for (HttpMessageConverter converter : this.messageConverters) {Class> converterType = (Class>) converter.getClass();GenericHttpMessageConverter genericConverter =(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter) converter : null);if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :(targetClass != null && converter.canRead(targetClass, contentType))) {if (message.hasBody()) {"RequestBodyAdvice方法调用"HttpInputMessage msgToUse =getAdvice().beforeBodyRead(message, parameter, targetType, converterType);body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :((HttpMessageConverter) converter).read(targetClass, msgToUse));body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);}else {body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);}break;}}}catch (IOException ex) {throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);}finally {if (message != null && message.hasBody()) {closeStreamIfNecessary(message.getBody());}}if (body == NO_VALUE) {if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||(noContentType && !message.hasBody())) {return null;}throw new HttpMediaTypeNotSupportedException(contentType,getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));}MediaType selectedContentType = contentType;Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(theBody, !traceOn);return "Read \"" + selectedContentType + "\" to [" + formatted + "]";});return body;
}
// 
private final RequestResponseBodyAdviceChain advice;
RequestResponseBodyAdviceChain getAdvice() {return this.advice;
}

可以看到这接口的方法,主要是在HttpMessageConverter处理request body的前后做一些处理和body为空的时候做处理。

从这个可以看出,我们可以在使用这些HandlerMethodArgumentResolver的时候,我们能对request body进行前处理和解析后处理。

2、ResponseBodyAdvice类

ublic interface ResponseBodyAdvice {boolean supports(MethodParameter returnType, Class> converterType);@NullableT beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,Class> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response);}

调用解析:

 AbstractMessageConverterMethodProcessor.class

protected  void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {Object body;Class valueType;Type targetType;if (value instanceof CharSequence) {body = value.toString();valueType = String.class;targetType = String.class;}else {body = value;valueType = getReturnValueType(body, returnType);targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());}if (isResourceType(value, returnType)) {outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&outputMessage.getServletResponse().getStatus() == 200) {Resource resource = (Resource) value;try {List httpRanges = inputMessage.getHeaders().getRange();outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());body = HttpRange.toResourceRegions(httpRanges, resource);valueType = body.getClass();targetType = RESOURCE_REGION_LIST_TYPE;}catch (IllegalArgumentException ex) {outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());}}}MediaType selectedMediaType = null;MediaType contentType = outputMessage.getHeaders().getContentType();boolean isContentTypePreset = contentType != null && contentType.isConcrete();if (isContentTypePreset) {if (logger.isDebugEnabled()) {logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;}else {HttpServletRequest request = inputMessage.getServletRequest();// 处理请求协商类型List acceptableTypes = getAcceptableMediaTypes(request);List producibleTypes = getProducibleMediaTypes(request, valueType, targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List mediaTypesToUse = new ArrayList<>();for (MediaType requestedType : acceptableTypes) {for (MediaType producibleType : producibleTypes) {if (requestedType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));}}}if (mediaTypesToUse.isEmpty()) {if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}if (logger.isDebugEnabled()) {logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}return;}MediaType.sortBySpecificityAndQuality(mediaTypesToUse);for (MediaType mediaType : mediaTypesToUse) {if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;}else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}}if (logger.isDebugEnabled()) {logger.debug("Using '" + selectedMediaType + "', given " +acceptableTypes + " and supported " + producibleTypes);}}if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();for (HttpMessageConverter converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {// 调用ResponseBodyAdvicebody = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,(Class>) converter.getClass(),inputMessage, outputMessage);if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);}else {((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}return;}}}if (body != null) {Set producibleMediaTypes =(Set) inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);}
}

 此可以对@ResponseBody的返回结果在输出到响应之前做处理。通过泛型,指定需要被“拦截”的响应体对象类型。该接口的实现会在 ​​Controller​​ 方法返回数据,并且匹配到了 ​​HttpMessageConverter​​ 之后,​​HttpMessageConverter​​ 进行序列化之前执行。可以通过覆写 ​​beforeBodyWrite​​ 来统一的对响应体进行修改。

二、为什么说要加上@ControllerAdvice注解,且实现RequestBodyAdvice或ResponseBodyAdvice接口

 现在来分析为什么需要实现RequestBodyAdvice接口的同时要加上ControllerAdvice注解。

 1、为什么要实现RequestBodyAdvice或ResponseBodyAdvice接口?简单来说,要想执行afterBodyRead方法,必须实现ResponseBodyAdvice接口。RequestResponseBodyAdviceChain的afterBodyRead方法:调用getMatchingAdvice方法,获取RequestBodyAdvice类型的advice其中:class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice

public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,Type targetType, Class> converterType) throws IOException {
// 此advice就是我们定义的类for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {//如果supports方法的返回值为true,则执行RequestBodyAdvice的afterBodyRead方法if (advice.supports(parameter, targetType, converterType)) {request = advice.beforeBodyRead(request, parameter, targetType, converterType);}}return request;
}

getMatchAdvice:获取RequestBodyAdvice类型的advice(此advice是我们定义的),如果不是RequestBodyAdvice类型就不会加到结果集,所以这就是我们实现RequestBodyAdvice的原因

private  List getMatchingAdvice(MethodParameter parameter, Class adviceType) {;//获取RequestBodyAdvice类型的advice(此advice是我们定义实现RequestBodyAdvice接口的类)List availableAdvice = getAdvice(adviceType);if (CollectionUtils.isEmpty(availableAdvice)) {return Collections.emptyList();}List result = new ArrayList<>(availableAdvice.size());for (Object advice : availableAdvice) {if (advice instanceof ControllerAdviceBean) {ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {continue;}// 返回的是我们定义的Advice,即根据Bean的名称从BeanFactory中获取Bean对象advice = adviceBean.resolveBean();}// 判断这个类是否是RequestBodyAdvice类型,如果不是就不会加到结果集,所以这就是我们实现RequestBodyAdvice的原因if (adviceType.isAssignableFrom(advice.getClass())) {result.add((A) advice);}}return result;
} 

getAdvice方法:

private List getAdvice(Class adviceType) {if (RequestBodyAdvice.class == adviceType) {return this.requestBodyAdvice;} else if (ResponseBodyAdvice.class == adviceType) {return this.responseBodyAdvice;} else {throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);}} 

resolveBean方法:this.beanOrName是string类型的,从beanFactory中再拿到对应的bean对象。

public Object resolveBean() {if (this.resolvedBean == null) {Object resolvedBean = this.obtainBeanFactory().getBean((String)this.beanOrName); // obtainBeanFactory()返回BeanFactory对象if (!this.isSingleton) {return resolvedBean;}this.resolvedBean = resolvedBean;}return this.resolvedBean;}

2、为什么要加上@ControllerAdvice注解?简单说,只有加上@ControllerAdvice,才能找到ControllerAdviceBean。HandlerAdapter字面上的意思就是处理适配器,它的作用用一句话概括就是调用具体的方法对用户发来的请求来进行处理。当handlerMapping获取到执行请求的controller时,DispatcherServlte会根据controller对应的controller类型来调用相应的HandlerAdapter来进行处理。

​​RequestMappingHandlerAdapter​​就比较复杂了,可以说,该类是整个SpringMVC中最复杂的类了,却也是目前SpringMVC中使用到的频率最高的类。目前在SpringMVC的使用过程中,对请求的处理主要就是依赖​​RequestMappingHandlerMapping​​和​​RequestMappingHandlerAdapter​​类的配合使用。下面重点介绍下​​RequestMappingHandlerAdapter​​类

RequestMappingHandlerAdapter初始化流程:

​​RequestMappingHandlerAdapter​​实现了​​InitializingBean​​接口,Spring容器会自动调用其​​afterPropertiesSet​​方法。

public void afterPropertiesSet() {this.initControllerAdviceCache();  // 获取ControllerAdviceBean的集合List handlers;if (this.argumentResolvers == null) {"把获取的ControllerAdviceBean集合赋值到调用的对象中,调用RequestBodyAdvice的地方"        handlers = this.getDefaultArgumentResolvers();this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);}if (this.initBinderArgumentResolvers == null) {handlers = this.getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);}if (this.returnValueHandlers == null) {"把获取的ControllerAdviceBean集合赋值到调用的对象中,调用ResponseBodyAdvice的地方"   handlers = this.getDefaultReturnValueHandlers();this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers);}}
  • ​​argumentResolvers​​:用于给处理器方法设置参数
  • ​​initBinderArgumentResolvers​​:用于给注释了​​@InitBinder​​方法设置参数
  • ​​returnValueHandlers​​:用于对处理器返回值进行相应处理

(1) 获取@ControllerAdvice注解的类封装为ControllerAdviceBean的集合,遍历ControllerAdviceBean集合

提取没有@RequestMapping注解标注的,但有@ModelAttribute注解标注的方法

提取@InitBinder注解标注的方法

提取实现了RequestBodyAdvice或ResponseBodyAdvice接口的类

(2) 没有设置argumentResolvers则获取默认的HandlerMethodArgumentResolver

(3) 没有设置initBinderArgumentResolvers则获取默认的处理参数绑定的HandlerMethodArgumentResolver

(4) 没有设置returnValueHandlers则获取默认的HandlerMethodReturnValueHandler

RequestMappingHandlerAdapter的getDefaultReturnValueHandlers方法:初始化了RequestResponseBodyMethodProcessor

private List getDefaultArgumentResolvers() {List resolvers = new ArrayList<>(30);// Annotation-based argument resolutionresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ServletModelAttributeMethodProcessor(false));// requestResponseBodyAdvice 赋值resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));resolvers.add(new RequestHeaderMapMethodArgumentResolver());resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// Type-based argument resolutionresolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());resolvers.add(new MapMethodProcessor());resolvers.add(new ErrorsMethodArgumentResolver());resolvers.add(new SessionStatusMethodArgumentResolver());resolvers.add(new UriComponentsBuilderMethodArgumentResolver());if (KotlinDetector.isKotlinPresent()) {resolvers.add(new ContinuationHandlerMethodArgumentResolver());}// Custom argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// Catch-allresolvers.add(new PrincipalMethodArgumentResolver());resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;
}
private List getDefaultReturnValueHandlers() {List handlers = new ArrayList<>(20);// Single-purpose return value typeshandlers.add(new ModelAndViewMethodReturnValueHandler());handlers.add(new ModelMethodProcessor());handlers.add(new ViewMethodReturnValueHandler());handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));handlers.add(new StreamingResponseBodyReturnValueHandler());// requestResponseBodyAdvice赋值handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));handlers.add(new HttpHeadersReturnValueHandler());handlers.add(new CallableMethodReturnValueHandler());handlers.add(new DeferredResultMethodReturnValueHandler());handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));// Annotation-based return value typeshandlers.add(new ServletModelAttributeMethodProcessor(false));// requestResponseBodyAdvice赋值handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));// Multi-purpose return value typeshandlers.add(new ViewNameMethodReturnValueHandler());handlers.add(new MapMethodProcessor());// Custom return value typesif (getCustomReturnValueHandlers() != null) {handlers.addAll(getCustomReturnValueHandlers());}// Catch-allif (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));}else {handlers.add(new ServletModelAttributeMethodProcessor(true));}return handlers;
}

requestResponseBodyAdvice​​:用来保存实现了​​RequestBodyAdvice​​或​​ResponseBodyAdvice​​接口的类

RequestMappingHandlerAdapter类的initControllerAdviceCache方法:初始化List集合requestResponseBodyAdvice

private void initControllerAdviceCache() {if (getApplicationContext() == null) {return;}// 获取有@ControllerAdvice注解的Bean的集合List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());List requestResponseBodyAdviceBeans = new ArrayList<>();for (ControllerAdviceBean adviceBean : adviceBeans) {Class beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}Set attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);if (!attrMethods.isEmpty()) {this.modelAttributeAdviceCache.put(adviceBean, attrMethods);}Set binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);if (!binderMethods.isEmpty()) {this.initBinderAdviceCache.put(adviceBean, binderMethods);}if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean); // 将ControllerAdviceBean放入List集合中}}if (!requestResponseBodyAdviceBeans.isEmpty()) {// 将存有ControllerAdviceBean的集合存入requestResponseBodyAdvice集合中this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);}if (logger.isDebugEnabled()) {int modelSize = this.modelAttributeAdviceCache.size();int binderSize = this.initBinderAdviceCache.size();int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {logger.debug("ControllerAdvice beans: none");}else {logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");}}
} 

ControllerAdviceBean(有@ControllerAdvice注解的Bean)的findAnnotatedBeans方法:获得所有的ControllerAdvice类,之后封装为ControllerAdviceBean

public static List findAnnotatedBeans(ApplicationContext context) {List adviceBeans = new ArrayList<>();for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {if (!ScopedProxyUtils.isScopedTarget(name)) {ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);if (controllerAdvice != null) {// Use the @ControllerAdvice annotation found by findAnnotationOnBean()// in order to avoid a subsequent lookup of the same annotation.adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));}}}OrderComparator.sort(adviceBeans);return adviceBeans;
}

会从applicationContext中获取有ControllerAdvice注解的bean

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...