Spring版本5.1.4.release.
前一篇讲了RequestBodyAdvice的实现
有人用RequestBodyAdvice来做参数的解密(前端传过来的是加密的),或者使用RequestBodyAdvice进行全局统一返回,但是我的需求是只对Java对象的特定属性进行解密,下面来看怎么实现。
List-1
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD ,ElementType.METHOD })
public @interface Decrypt {
    /**
     * 解密方式
     *
     * @return
     */
    String value() default "AES";
}
List-1中定义了一个注解。
如下的List-2中,实现了RequestBodyAdvice,类上加了@ControllerAdvice注解,这俩个缺一不可,后面我会说原因。
- supports方法里,判断方法上是否有Decrypt注解,有注解则执行afterBodyRead中的逻辑
 - afterBodyRead中,反射获取对象的属性,如果对象的属性是String类型,且有Decrypt注解,则对其它进行解密后更新值
 
List-2
@ControllerAdvice
public class ArgumentResolverAdvice implements RequestBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.getMethodAnnotation(Decrypt.class) != null;
    }
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        for (Field field : body.getClass().getDeclaredFields()) {
            Decrypt decrypt = field.getAnnotation(Decrypt.class);
            if (decrypt != null) {
                field.setAccessible(true);
                try {
                    Object value = field.get(body);
                    if (value instanceof String) {
                        value = EncryptUtil.decrypt((String) value);
                        field.set(body, value);
                    }else {
                        throw new XXException("目前只支持String类型的解密");
                    }
                } catch (IllegalAccessException e) {
                    Log.error("反射获取值失败", e);
                } catch (XXException e) {
                    Log.error("解密失败", e);
                }
            }
        }
        return body;
    }
    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}
如下List-3,用@RequestBody注解获取request内容,方法上加上@Decrypt注解,这俩个注解缺一不可,如果没有@RequestBody则我们自定义的RequestBodyAdvice不会生效,原因我在前一篇中已分析,如果没有@Decrypt那么就不会执行解密逻辑。
List-3
@RequestMapping("xx")
@Decrypt
@ResponseBody
public XXResponse xx(@RequestBody User user, HttpServletRequest request, HttpServletResponse response) throws XXException {
    return XXService.xx(user, request, response);
}
现在来分析ArgumentResolverAdvice为什么需要实现RequestBodyAdvice接口的同时要加上ControllerAdvice注解:
RequestResponseBodyAdviceChain的afterBodyRead中,调用getMatchingAdvice方法,获取RequestBodyAdvice类型的advice,如下List-5所示
List-4
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
        Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
        if (advice.supports(parameter, targetType, converterType)) {
            body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
        }
    }
    return body;
}
List-5
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
    //1
    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;
            if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
                continue;
            }
       //3   
            advice = adviceBean.resolveBean();
        }
        //4
        if (adviceType.isAssignableFrom(advice.getClass())) {
            result.add((A) advice);
        }
    }
    return result;
}
private List<Object> getAdvice(Class<?> adviceType) {
    //2
    if (RequestBodyAdvice.class == adviceType) {
        return this.requestBodyAdvice;
    }
    else if (ResponseBodyAdvice.class == adviceType) {
        return this.responseBodyAdvice;
    }
    else {
        throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);
    }
}
List-5中,获取requestBodyAdvice集合后,遍历元素,判断是否符合条件,符合条件的才返回:
- 3处resolveBean返回的正是我们在List-2中定义的ArgumentResolverAdvice
 - 4处判断这个类是否是RequestBodyAdvice类型,如果不是则不会加到结果集,所以就是我们要实现RequestBodyAdvice的原因
 
List-5的3处我们再来看下,如下List-6是ControllerAdviceBean的resolveBean方法,其实这里的this.bean是string类型的,从beanFactory中再拿到对应的bean对象。
List-6
public Object resolveBean() {
    return this.bean instanceof String ? this.obtainBeanFactory().getBean((String)this.bean) : this.bean;
}
ArgumentResolverAdvice为什么要加上@ControllerAdviceBean,而不是@Component ?
这要回到RequestResponseBodyMethodProcessor的属性RequestResponseBodyAdviceChain是怎么得到上来,RequestMappingHandlerAdapter.getDefaultReturnValueHandlers()中初始化了RequestResponseBodyMethodProcessor,如下List-7,由此可知requestResponseBodyAdvice是来自RequestMappingHandlerAdapter的,再来看RequestMappingHandlerAdapter中是怎么得到requestResponseBodyAdvice集合的
List-7
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
    ...
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));
    ...
    return handlers;
}
如下List-8中,RequestMappingHandlerAdapter初始化RequestResponseBodyAdvice是从ControllerAdviceBean.findAnnotatedBeans(getApplicationContext())中获得所有的ControllerAdvice类,之后封装为ControllerAdviceBean,从List-9中可以看到ControllerAdviceBean中的bean是String类型的
List-8
private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);
    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
        if (!attrMethods.isEmpty()) {
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
        }
        Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
        if (!binderMethods.isEmpty()) {
            this.initBinderAdviceCache.put(adviceBean, binderMethods);
        }
        if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
        if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
    }
    if (!requestResponseBodyAdviceBeans.isEmpty()) {
        this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }
}
List-9 ControllerAdviceBean的findAnnotatedBeans方法
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
    return (List)Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)).filter((name) -> {
        return context.findAnnotationOnBean(name, ControllerAdvice.class) != null;
    }).map((name) -> {
        return new ControllerAdviceBean(name, context);
    }).collect(Collectors.toList());
}
从List-8和List-9中,可以看出,会从applicationContext中获取有ControllerAdvice注解的bean,且只有这个bean是实现了RequestBodyAdvice接口或者ResponseBodyAdvice接口的才会加入到ReqeustResponseAdvice结果集合中,所以这就是开头为什么说要加上@ControllerAdvice注解,且实现RequestBodyAdvice接口。