首先,关于这两个类怎么用,我看了一篇博客觉得还不错,Springboot中RequestBody-Advice与ResponseBodyAdvice的正确使用。这里我主要是想探究一下它们是如何工作的。
作用范围
RequestBodyAdvice
当使用HttpEntity作为请求参数或者使用@RequestBody / @RequestPart注解时,它允许你在请求消息体读取并将其转换成对象的前后作出处理。
ResponseBodyAdvice
当使用ResponseEntity作为响应参数或者使用@ResponseBody注解时,它允许你在响应消息体转换写入前作出处理。
简单使用
- 手动:我们可以分别实现这两个接口,通过RequestMappingHandlerAdapter注册相应的类。
- 自动:实现接口的同时,使用@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个。
- RequestResponseBodyMethodProcessor
- HttpEntityMethodProcessor
- 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"
}
知其然,知其所以然,学而不倦。