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