SpringFramework之RequestBodyAdvice的使用

编程

    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接口。

以上是 SpringFramework之RequestBodyAdvice的使用 的全部内容, 来源链接: utcz.com/z/517922.html

回到顶部