前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringFramework之RequestBodyAdvice的使用

SpringFramework之RequestBodyAdvice的使用

作者头像
克虏伯
发布2020-07-01 15:20:22
2.5K0
发布2020-07-01 15:20:22
举报

Spring版本5.1.4.release.

前一篇讲了RequestBodyAdvice的实现

    有人用RequestBodyAdvice来做参数的解密(前端传过来的是加密的),或者使用RequestBodyAdvice进行全局统一返回,但是我的需求是只对Java对象的特定属性进行解密,下面来看怎么实现。        

List-1

代码语言:javascript
复制
@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

代码语言:javascript
复制
@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

代码语言:javascript
复制
@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

代码语言:javascript
复制
@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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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方法

代码语言:javascript
复制
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接口。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档