该接口是4.1之后新增的,主要是在一个@ResponseBody标识或返回值类型是ResponseEntity的controller方法执行之后、在消息转换器处理之前自定义响应(自定义controller方法的返回值)
实现类注册方式:
直接在RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver中注册
在实现类上使用@ControllerAdvice注解
ResponseBody源码如下:
package org.springframework.web.servlet.mvc.method.annotation;
public interface ResponseBodyAdvice {
// 判断该组件是否支持controller方法返回值类型、所选择的消息转换器
boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType);
// 该方法在消息转换器被选择之后、消息转换器写方法之前被调用
T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,
Class extends HttpMessageConverter>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
我们可以通过查看谁调用了该方法,了解下其具体执行过程:
调用该方法的类是:ResponseBodyAdviceChain,该类主要用于执行所有实现了ResponseBody接口的list集合,同时该类也是4.1之后新增的。
invoke方法中用到了ControllerAdviceBean类,该类封装了一些Spring管理的@ControllerAdvice bean的信息,但不要求它实例化。
ResponseBodyAdviceChain源码如下:
package org.springframework.web.servlet.mvc.method.annotation;
class ResponseBodyAdviceChain {
private final List
使用示例:
假如我们有这样一个需求,后台统一返回某种结构的json,但是controller方法返回值不固定,可以是void或string等,但都通过@ResponseBody标识。
实现思路:
1. 自定义响应结构
public class CommonResponseResult {
private int code;
private String msg;
private Object data;
public CommonResponseResult() {
this(200, "success", null);
}
public CommonResponseResult(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
get、set略
}
2. 自定义JSON转换器,包装controller方法返回值
使用fastjson转换json,这里只实现了写方法(Object->JSON);也可以参照fastjson的默认实现的消息转换器,FastJsonHttpMessageConverter。
public class JsonHttpMessageConverter extends AbstractHttpMessageConverter {
@Override
protected boolean supports(Class> clazz) {
return true;
}
@Override
protected Object readInternal(Class extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO 暂未实现
return null;
}
@Override
protected void writeInternal(Object t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
ByteArrayOutputStream outnew = new ByteArrayOutputStream();
try {
HttpHeaders headers = outputMessage.getHeaders();
CommonResponseResult responseResult = new CommonResponseResult();
responseResult.setData(t);
int len = JSON.writeJSONString(outnew, responseResult, SerializerFeature.WriteMapNullValue);
headers.setContentLength(len);
outnew.writeTo(outputMessage.getBody());
} catch (JSONException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
} finally {
outnew.close();
}
}
}
springmvc配置文件中注册自定义的JSON消息转换器
3. 对controller方法返回void值进行处理
beforeBodyWrite方法用于对返回值的处理,这里直接返回Object实例,防止返回值为null。因为controller方法返回值是void,通过SpringMVC参数解析器解析后当做null处理,若是null则不会进入定义的消息转换器,所以这里简单处理一下,确保进入我们自定义的JSON消息转换器中。(可以debug调试,对比一下void和非void返回值的处理情况)
@ControllerAdvice
public class VoidResponseBodyAdvice implements ResponseBodyAdvice {
// 判断返回值类型是否是void
@Override
public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {
return returnType.getMethod().getReturnType().equals(Void.TYPE);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class extends HttpMessageConverter>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
// void值直接返回Object实例
return new Object();
}
}
springmvc配置文件中扫描VoidResponseBodyAdvice
4. 测试controller
@RequestMapping("/returnvoid")
@ResponseBody
public void returnVoid() {
...
}
@RequestMapping("/list")
@ResponseBody
public List userList() {
List userList = userService.listAll();
return list;
}
测试结果
返回值为void时,结果为:{"code":200,"data":{},"msg":"success"}
通过debug可以看到在进入消息转换器前,先调用了自定义的ResponseBodyAdvice的beforeBodyWrite方法。
最后我们简单看一下SpringMVC对返回值的处理
在抽象类AbstractMessageConverterMethodProcessor中处理返回值,包括选择合适的消息转换器(通过canWrite方法判断,主要是调用消息转换的supports方法和抽象中的canWrite(MediaType mediaType)方法,满足其一即可);若该消息转换器可以处理,则通过ResponseBodyAdviceChain的实例(adviceChain)执行invoke方法,以获取到返回值,这部分源码在本文一开始处。以上面的例子来解释一下:Controller返回值是void,通过我们自定义的ResponseBodyAdvice对返回值做处理,也就是返回一个Object实例,对应adviceChain.invoke(...)方法返回了该实例,因为返回值不为null,所以调用消息转换器的写方法;若不处理,则直接返回null,则不会调用。
AbstractMessageConverterMethodProcessor源码如下(删减了一些其他方法,可以查看源码):
package org.springframework.web.servlet.mvc.method.annotation;
// 处理方法返回值,通过消息转换器写入reponse
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
private final ContentNegotiationManager contentNegotiationManager;
private final ResponseBodyAdviceChain adviceChain;
...
// 根据给定的返回值写入指定的web request
protected void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
// 根据给定的值写入指定的outputMessage
// returnValue是返回值实例,若返回的是void,则为null
protected void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
// 获取返回值的Class,具体可参考源码
Class> returnValueClass = getReturnValueType(returnValue, returnType);
// 实际的request
HttpServletRequest servletRequest = inputMessage.getServletRequest();
List requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
List producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
Set compatibleMediaTypes = new LinkedHashSet();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
if (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
List mediaTypes = new ArrayList(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
// 选择的MIME
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// 遍历定义的消息转换器
for (HttpMessageConverter> messageConverter : this.messageConverters) {
// 调用消息转换器的canWrite方法,判断是否可以处理该返回值类型
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
// 在调用消息转化器写方法前对controller方法的返回值做一些处理,如返回值是void的可以通过自定义的ResponseBodyAdvice返回一个非空的值
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class>) messageConverter.getClass(), inputMessage, outputMessage);
// 返回值不为null,调用消息转换器的写方法
if (returnValue != null) {
((HttpMessageConverter) messageConverter).write(returnValue, selectedMediaType, outputMessage);
}
return;
}
}
}
if (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
...
}