SpringMVC - @ResponseBodyAdvice

该接口是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> converterType);

    // 该方法在消息转换器被选择之后、消息转换器写方法之前被调用
    T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,
            Class> 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 advice;

    // 构造方法,初始化所有实现ResponseBodyAdvice的类,这些类需要被注册为bean,否则无法添加
    public ResponseBodyAdviceChain(List advice) {
        this.advice = advice;
    }

    public boolean hasAdvice() {
        return !CollectionUtils.isEmpty(this.advice);
    }

    // 实际调用ResponseBodyAdvice实现类的方法,主要遍历所有注册的advice,进行相关的处理
    @SuppressWarnings("unchecked")
    public  T invoke(T body, MethodParameter returnType,
            MediaType selectedContentType, Class> selectedConverterType,
            ServerHttpRequest request, ServerHttpResponse response) {
        if (this.advice != null) {
            for (Object advice : this.advice) {
                if (advice instanceof ControllerAdviceBean) {
                    ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
                    if (!adviceBean.isApplicableToBeanType(returnType.getContainingClass())) {
                        continue;
                    }
                    advice = adviceBean.resolveBean();
                }
                if (advice instanceof ResponseBodyAdvice) {
                    ResponseBodyAdvice typedAdvice = (ResponseBodyAdvice) advice;
                    // 判断该ResponseBodyAdvice是否支持controller返回值类型、所选择的消息转换器
                    if (typedAdvice.supports(returnType, selectedConverterType)) {
                        // 调用beforeBodyWrite方法,在执行消息转换器写方法之前做一些处理
                        body = typedAdvice.beforeBodyWrite(body, returnType,
                                selectedContentType, selectedConverterType, request, response);
                    }
                } else {
                    throw new IllegalStateException("Expected ResponseBodyAdvice: " + advice);
                }
            }
        }
        return body;
    }
} 
  

 

使用示例:

假如我们有这样一个需求,后台统一返回某种结构的json,但是controller方法返回值不固定,可以是void或string等,但都通过@ResponseBody标识。

实现思路:

  • 自定义响应结构
  • 自定义JSON消息转换器
  • 自定义对void返回值的处理

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 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消息转换器


    
    
        
        
        
        
        
        
    	

        
            
            
                
                    text/html;charset=UTF-8
                    application/json;charset=UTF-8
                
            
        
    

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> converterType) {
        return returnType.getMethod().getReturnType().equals(Void.TYPE);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class> 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);
		}
	}

	...

}

 

你可能感兴趣的:(springmvc)