一、环境
<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信息,存在的问题:
- 该信息机构可以随意更改,需要单独校验
- 机构业务上传的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
,在设置参数 缺点:
- 全局拦截
- 需要来回转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);
}
解决方法:
- 不进行注入参数的校验,通过其他组件进行校验,比如网关鉴权插件
- 进行校验,则可以通过自定义
ValidateUtils
工具类,进行参数后置校验 - 继承
RequestResponseBodyMethodProcessor
重写resolveArgument
方法
参考
[记一次springboot项目自定义HandlerMethodArgumentResolver不生效原因与解法]