0
点赞
收藏
分享

微信扫一扫

@RequestBody默认值注入踩坑及解决


一、环境

<properties>
<spring.version>5.3.22</spring.version>
<spring-boot.version>2.7.3</spring-boot.version>
<spring-cloud.version>3.1.3</spring-cloud.version>
<spring-cloud-dependencies.version>2021.0.3</spring-cloud-dependencies.version>
<spring-cloud-starter-alibaba.version>2021.0.1.0</spring-cloud-starter-alibaba.version>
</properties>

二、场景描述

接手老系统的对接API重构,目前项目是重构出来核心业务服务、开放平台、应用中心、网关 以前老系统都是客户端通过请求传入orgCode、orgName信息,存在的问题:

  1. 该信息机构可以随意更改,需要单独校验
  2. 机构业务上传的SDK的侵入,这个信息可以通过token获取应用信息获得

所以目前是希望,基础鉴权模块提供自动注入功能,不侵入业务执行

三、思路

3.1 基于JsonDeserializer

使用自定义注解@OrgCodeAutowired

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonDeserialize(using = AppOrgCodeDeserializer.class)
public @interface OrgCodeAutowired {
}

public class OrgCodeDeserializer extends JsonDeserializer<String> implements ContextualDeserializer {

@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
return getOrgCode();
}

private String getOrgCode() {
AppDetail appDetail = (AppDetail) AuthContext.get();
if (Objects.isNull(appDetail)) {
throw new BizException(ErrorCode.APP_DETAIL_IS_NULL);
}
return appDetail.getAppKey();
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext prov, BeanProperty property) throws JsonMappingException {
OrgCodeAutowired annotation = property.getAnnotation(OrgCodeAutowired.class);
if (Objects.nonNull(annotation)) {
return this;
}
return prov.findContextualValueDeserializer(property.getType(), property);
}
}

断点发现,​​createContextual​​​方法可以进入,但是​​deserialize​​​方法无法进入 查询stackoverflow得知需要重写​​getNullValue​​方法

​​stackoverflow​​

补上

public class OrgCodeDeserializer extends JsonDeserializer<String> implements ContextualDeserializer {

@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
return getOrgCode();
}
@Override
public String getNullValue(DeserializationContext ctxt) {
return getOrgCode();
}

private String getOrgCode() {
AppDetail appDetail = (AppDetail) AuthContext.geAuthenticationt();
if (Objects.isNull(appDetail)) {
throw new BizException(ErrorCode.APP_DETAIL_IS_NULL);
}
return appDetail.geOrgCode();
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext prov, BeanProperty property) throws JsonMappingException {
OrgCodeAutowired annotation = property.getAnnotation(OrgCodeAutowired.class);
if (Objects.nonNull(annotation)) {
return this;
}
return prov.findContextualValueDeserializer(property.getType(), property);
}
}

断点发现还是无法进入​​deserialize​​,尝试在请求参数中加入

{
"orgCode": null
}

发现可以进入了,没有该​​attribute​​​则不会触发​​deserialize​​​,如果想实现估计得看下​​Jackson​​​的源码修改​​objectMapper​

3.2 尝试Filter

通过实现​​HttpServletRequestWrapper​​​,然后解析​​json​​,在设置参数 缺点:

  1. 全局拦截
  2. 需要来回转json,性能不太好

不满足需求,放弃该方案

3.3 基于HandlerMethodArgumentResolver

首先定义orgCode超类

public interface OrgCode {

void setOrgCode(String orgCode);

}

请求参数继承该超类

@Data
@ApiModel
public class DemoReq implements Serializable, OrgCode {

private static final long serialVersionUID = 1L;

@NotBlank
private String orgCode;
}

实现​​HandlerMethodArgumentResolver​

public class OrgCodeResolver implements HandlerMethodArgumentResolver {

private RequestResponseBodyMethodProcessor handlerMethodArgumentResolver;

public OrgInfoResolver(RequestResponseBodyMethodProcessor handlerMethodArgumentResolver) {
this.handlerMethodArgumentResolver = handlerMethodArgumentResolver;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class) && OrgCode.class.isAssignableFrom(parameter.getParameterType());
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
OrgCode object = (OrgCode) handlerMethodArgumentResolver.resolveArgument(parameter,mavContainer,webRequest,binderFactory);

AppDetail appDetail = (AppDetail) AuthContext.geAuthenticationt();
if (Objects.isNull(appDetail)) {
throw new BizException(ErrorCode.APP_DETAIL_IS_NULL);
}

object.setOrgCode(appDetail.geOrgCode());
return object;
}
}

配置自动装配类

public class HandlerMethodArgumentResolverAutoConfiguration implements InitializingBean {

@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

@Override
public void afterPropertiesSet() throws Exception {
List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>();

for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {
if(argumentResolver instanceof RequestResponseBodyMethodProcessor){
customArgumentResolvers.add(new OrgCodeResolver((RequestResponseBodyMethodProcessor) argumentResolver));
}
customArgumentResolvers.add(argumentResolver);
}

requestMappingHandlerAdapter.setArgumentResolvers(customArgumentResolvers);

}
}

至此解决,踩了不少坑,效率有点低

坑:因为使用了​​RequestResponseBodyMethodProcessor​​​进行​​json​​​解析,而​​RequestResponseBodyMethodProcessor​​​在其代码中内置了​​@Validate​​​参数校验模块,故如果使用​​@Validate​​校验,则无法对注入的参数进行校验。

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

//判断入参是不是Optional类型,是则返回嵌套类型
parameter = parameter.nestedIfOptional();
//使用消息转换器转换参数
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());

//获取参数类型的短名称
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
//获取web数据绑定器
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null){
//判断参是是否使用了@Validated注解或者使用了@Valid开头的注解,则使用Validator接口实现类校验数据(如果适用)
validateIfApplicable(binder, parameter);
//判断校验结果是否有错误,然后判断当前参数后挨着的是不是BindingResult对象
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}

解决方法:

  1. 不进行注入参数的校验,通过其他组件进行校验,比如网关鉴权插件
  2. 进行校验,则可以通过自定义​​ValidateUtils​​工具类,进行参数后置校验
  3. 继承​​RequestResponseBodyMethodProcessor​​​重写​​resolveArgument​​方法

参考

​​[记一次springboot项目自定义HandlerMethodArgumentResolver不生效原因与解法]​​

举报

相关推荐

0 条评论