springMVC之RequestBodyAdvice和ResponseBodyAdvice

首先,关于这两个类怎么用,我看了一篇博客觉得还不错,Springboot中RequestBody-Advice与ResponseBodyAdvice的正确使用。这里我主要是想探究一下它们是如何工作的。

作用范围

RequestBodyAdvice

当使用HttpEntity作为请求参数或者使用@RequestBody / @RequestPart注解时,它允许你在请求消息体读取并将其转换成对象的前后作出处理。

ResponseBodyAdvice

当使用ResponseEntity作为响应参数或者使用@ResponseBody注解时,它允许你在响应消息体转换写入前作出处理。

简单使用
  1. 手动:我们可以分别实现这两个接口,通过RequestMappingHandlerAdapter注册相应的类。
  2. 自动:实现接口的同时,使用@ControllerAdvice注解。
接口定义
public interface RequestBodyAdvice {
	/**
	 * 返回false,表示不拦截
	 */
	boolean supports(MethodParameter methodParameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType);

	/**
	 * 在请求消息读取转换成对象之前执行
	 */
	HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;

	/**
	 * 在请求消息转换成对象之后执行
	 */
	Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

	/**
	 * 如果请求消息体为空时,执行
	 */
	@Nullable
	Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}
public interface ResponseBodyAdvice<T> {
	/**
	 * 响应类型以及消息转换体的类型是否支持
	 */
	boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

	/**
	 * 响应消息转换写入前执行
	 */
	@Nullable
	T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response);
}

我觉得正是因为它们的应用方式像切面通知一样,所以它们的名称中都包含了Advice(个人推测)。

源码解析

前面我简单的提到了他们的使用方式,其实使用@ControllerAdvice时,也是Reques-tMappingHandlerAdapter在工作,我们一起来看下源码。

	// RequestMappingHandlerAdapter初始化时
	public void afterPropertiesSet() {
		// ExceptionHandlerExceptionResolver初始化的时候执行的逻辑和这个类似,大家可以去瞅瞅
		initControllerAdviceCache();
		//...
	}
	private void initControllerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}
		// 在容器中找到所有使用@ControllerAdvice的bean
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			}
			// 找到所有使用@ModelAttribute但没有使用@RequestMapping的方法
			Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
			if (!attrMethods.isEmpty()) {
				this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
			}
			//  找到所有使用@InitBinder的方法
			Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
				this.initBinderAdviceCache.put(adviceBean, binderMethods);
			}
			// 如果实现了RequestBodyAdvice或者ResponseBodyAdvice接口
			if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
			}
		}
		// requestResponseBodyAdvice将在请求参数以及返回值解析器种使用到
		if (!requestResponseBodyAdviceBeans.isEmpty()) {
			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
		}
		// ...
	}

并不是所有的请求参数以及返回值解析器都会用到requestResponseBodyAdvice,目前使用到的解析器只有下面这3个。

  1. RequestResponseBodyMethodProcessor
  2. HttpEntityMethodProcessor
  3. RequestPartMethodArgumentResolver
    在这里插入图片描述
    它们有一个共同的父类AbstractMessageConverterMethodArgumentResolver,在它们解析请求参数时,都会调用到父类的方法readWithMessageConverters。我们一起来看看这个方法。
	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
		//...
		EmptyBodyCheckingHttpInputMessage message;
		try {
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) 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()) {
						// 消息读取转换成对象前
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						// 消息读取转换成对象后
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}else {
						// 无内容,将调用RequestBodyAdvice#handleEmptyBody
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}
		// ...
	}

而1和2除此之外,还继承了AbstractMessageConverterMethodProcessor,在它们解析响应消息的时候,会调用到父类中的writeWithMessageConverters方法。我们一起来看看这个方法。

// 这个方法内容挺长的,这里只截取本部分要讲的内容
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
	// ...
	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)) {
					// 在响应消息体转换写入前进行处理
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) 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;
				}
			}
		}
		// ....
}

源码中我们看到并不是直接调用RequestBodyAdvice或ResponseBodyAdvice的对应方法,而是通过getAdvice()获取到RequestResponseBodyAdviceChain进行方法调用,而RequestResponseBodyAdviceChain实现了这两个接口,并持有这两个类的实例列表,真正代码调用的仍然是这两个类。个人理解这里使用了代理模式,侧重点在于控制对对象的访问,这里我们来看一下其中某个方法的调用详情。

public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
		// 这里会取方法参数符合 ControllerAdvice条件的RequestBodyAdvice
		for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
			// 这里主要判断是否拦截,这是方法级别的
			if (advice.supports(parameter, targetType, converterType)) {
				request = advice.beforeBodyRead(request, parameter, targetType, converterType);
			}
		}
		return request;
	}
	private <A> List<A>  getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
		// 获取RequestBodyAdvice或ResponseBodyAdvice列表
		List<Object> availableAdvice = getAdvice(adviceType);
		if (CollectionUtils.isEmpty(availableAdvice)) {
			return Collections.emptyList();
		}
		List<A> result = new ArrayList<>(availableAdvice.size());
		for (Object advice : availableAdvice) {
			if (advice instanceof ControllerAdviceBean) {
				ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
				// 这里会判断方法参数是否满足ControllerAdvice注解中设置的条件
				if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
					continue;
				}
				advice = adviceBean.resolveBean();
			}
			// 这里就判断类死否实现了RequestBodyAdvice或ResponseBodyAdvice接口
			if (adviceType.isAssignableFrom(advice.getClass())) {
				result.add((A) advice);
			}
		}
		return result;
	}

通过上面代码分析,我们首先可以在RequestBodyAdvice和ResponseBodyAdvice的supp-orts方法中控制是否拦截,也可以利用ControllerAdvice注解设置条件进行拦截,我觉得简单的可以用ControllerAdvice,复杂的逻辑就在supports方法中编写。

可用的RequestBodyAdvice和ResponseBodyAdvice

spring提供了一个JsonViewRequestBodyAdvice和一个JsonViewResponseBodyAdvice,使用springBoot时,我们只要添加jackson的jar包就可以使用它们了。

 <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
 </dependency>
 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-databind</artifactId>
 </dependency>

springBoot自动配置WebMvcConfigurationSupport类构建RequestMappingHandlerAdapter时发现有上面两个jar包就会进行设置。

		static {
			//...
			jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
			//...
		}
		// 构建RequestMappingHandlerAdapter时,这里只截取了部分关键代码
		if (jackson2Present) {
			adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
			adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
		}

关于使用这两个advice,就要学会使用jackson包提供的注解@JsonView。这里是一篇有关这个注解的博客

我自己也写了简单的demo

@Data
@JsonView(User.class)
public class User {
    public interface UserInfo{};

    @JsonView(UserInfo.class)
    private String name;

    private String sex;

    private String grade;
}
@Controller
@RequestMapping("/jsonView")
public class JsonViewTest {

    @JsonView(User.class)
    @RequestMapping("")
    @ResponseBody
    public  User test1(){
        User user = new User ();
        user.setGrade ("89");
        user.setSex ("male");
        user.setName ("jone");
        return user;
    }

    @RequestMapping("test2")
    @ResponseBody
    public User test2(@RequestBody @JsonView(User.class) User user){
        return user;
    }
}
test1的结果
{
    "sex": "male",
    "grade": "89"
}

test2的结果
{
    "name": null,
    "sex": "male",
    "grade": "89"
}

知其然,知其所以然,学而不倦。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值