SpringMVC - @ResponseBodyAdvice

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mytt_10566/article/details/81159052

该接口是4.1之后新增的,主要是在一个@ResponseBody标识或返回值类型是ResponseEntity的controller方法执行之后、在消息转换器处理之前自定义响应(自定义controller方法的返回值)

实现类注册方式:

  • 直接在RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver中注册

  • 在实现类上使用@ControllerAdvice注解

ResponseBody源码如下:

package org.springframework.web.servlet.mvc.method.annotation;

public interface ResponseBodyAdvice<T> {

    // 判断该组件是否支持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<Object> advice;

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

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

    // 实际调用ResponseBodyAdvice实现类的方法,主要遍历所有注册的advice,进行相关的处理
    @SuppressWarnings("unchecked")
    public <T> T invoke(T body, MethodParameter returnType,
            MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> 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<T> typedAdvice = (ResponseBodyAdvice<T>) 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<Object> {

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

<mvc:annotation-driven>
    <!-- 不使用默认的消息转换器 -->
    <mvc:message-converters register-defaults="false">
        <!-- 配置Spring的转换器 -->
        <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
        <!-- 配置fastjson中实现HttpMessageConverter接口的转换器 -->
    	<!-- <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> -->

        <bean id="fastJsonHttpMessageConverter" class="com.momo.springmvc.common.messageconverter.JsonHttpMessageConverter">
            <!-- 加入支持的媒体类型,返回contentType -->
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html;charset=UTF-8</value>
                    <value>application/json;charset=UTF-8</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

3. 对controller方法返回void值进行处理

beforeBodyWrite方法用于对返回值的处理,这里直接返回Object实例,防止返回值为null。因为controller方法返回值是void,通过SpringMVC参数解析器解析后当做null处理,若是null则不会进入定义的消息转换器,所以这里简单处理一下,确保进入我们自定义的JSON消息转换器中。(可以debug调试,对比一下void和非void返回值的处理情况)

@ControllerAdvice
public class VoidResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    // 判断返回值类型是否是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

<context:component-scan base-package="com.momo.springmvc.common"/>

4. 测试controller

@RequestMapping("/returnvoid")
@ResponseBody
public void returnVoid() {
    ...
}

@RequestMapping("/list")
@ResponseBody
public List<User> userList() {
    List<User> 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 <T> 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 <T> 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<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
		List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);

		Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
		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<MediaType> mediaTypes = new ArrayList<MediaType>(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<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
					// 返回值不为null,调用消息转换器的写方法
					if (returnValue != null) {
						((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
					}
					return;
				}
			}
		}

		if (returnValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

	...

}

猜你喜欢

转载自blog.csdn.net/mytt_10566/article/details/81159052
今日推荐