SpringFramework之RequestBodyAdvice的使用

Easter79
• 阅读 787

    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注解,这俩个缺一不可,后面我会说原因。

  1. supports方法里,判断方法上是否有Decrypt注解,有注解则执行afterBodyRead中的逻辑
  2. 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接口。

点赞
收藏
评论区
推荐文章
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Souleigh ✨ Souleigh ✨
3年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Stella981 Stella981
3年前
RabbitMQ学习:RabbitMQ的六种工作模式之简单和工作模式(三)
上一篇:RabbitMQ学习:RabbitMQ的基本概念及RabbitMQ使用场景(二)https://my.oschina.net/u/4115134/blog/3223371(https://my.oschina.net/u/4115134/blog/3223371)RabbitMQ的六种工作模式
Stella981 Stella981
3年前
Spring Boot(三):Thymeleaf 使用详解
在上篇文章SpringBoot(二):web应用开发,实现CRUD(https://my.oschina.net/u/4006148/blog/3163419)中简单介绍了一下Thymeleaf,这篇文章将更加全面详细的介绍Thymeleaf的使用。Thymeleaf是新一代的模板引擎,在Spring4.0中推荐使用Thymeleaf来
Stella981 Stella981
3年前
Mybatis通过Interceptor来简单实现影子表进行动态sql读取和写入 续
    继上一篇Mybatis通过Interceptor来简单实现影子表进行动态sql读取和写入地址:https://my.oschina.net/u/3266761/blog/3014017(https://my.oschina.net/u/3266761/blog/3014017)    之后留了一个小坑,那就是希望能够根据控制层传输过
Stella981 Stella981
3年前
Angular相关内容学习总结
一、angular项目搭建1、前端框架Yeoman(http://my.oschina.net/u/1416844/blog/196199) http://my.oschina.net/u/1416844/blog/196199二、angular结构划分angular经典的架构和目
Stella981 Stella981
3年前
Kqueue 实现非阻塞 Socket 通信
如果有误,请大神指出啊!\之前留下的坑之前写过一篇kqueue实现文件操作监控(http://my.oschina.net/shinedev/blog/501795),讲了Kqueue在文件监控的应用,文章给出的例子只对于一个test文件进行监控。Kqueue或者Epoll更多的是被使用在Socket
Easter79 Easter79
3年前
Springboot框架,实现请求数据解密,响应数据加密的功能。
一、简要说明:  在做这个功能的时候,参考了很多文章,也试了用过滤器解决。但总体来说还是很麻烦,所以换了另一种解决方案。直接实现RequestBodyAdvice和ResponseBodyAdvice两个接口,进行加密解密处理。  关于RequestBodyAdvice和ResponseBodyAdvice接口的作用,可参考该文档: (1)h
Wesley13 Wesley13
3年前
GOJS使用
GOJS使用前端拓扑图1.基础版:引入go.js<scriptsrc"https://my.oschina.net//u/4402671/blog/3234986/js/go.js"type"text/javascript"</script
Stella981 Stella981
3年前
Spring MVC的文件下载
这篇文章介绍的方法在某些时候会有一些问题,小瑕疵,请大家看更好的方法(https://my.oschina.net/songxinqiang/blog/898901)读取文件要下载文件,首先是将文件内容读取进来,使用字节数组存储起来,这里使用spring里面的工具类实现importorg.springframework.u
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k