SpringBoot参数⾮空校验的⾮最优实现历程
  SpringBoot参数⾮空校验在⽹上已经有很多资料了,⾃⼰最近要实现这⼀个功能,⼤概看了下觉得没什么难度,不想在过程中还是遇到了⼀些问题,在此记录,希望有
遇到和我⼀样问题的⼈和过路⼤神不吝指教。
  需求是做⼀个全局请求参数⾮空校验和异常拦截,spring提供的@Validated和Hibernate提供的@Valid⽬前不⽀持请求参数为基本类型的⾮空判断,只能是请求参数封装
为对象时,判断对象属性⾮空,所以要⾃⼰实现⼀个对基本类型的⾮空判断。
  ⾸先说下⽹上原创转载最多的⼀个思路:实现⼀个指向⽅法的注解,注解中创建⼀个String[]属性,⽤来存放⽅法中需要⾮空判断的参数的名称 -----> 创建AOP,切点为
注解的⽅法,增强⽅法中拿到注解中的String[],然后遍历判断是否为空,如果为空则抛出⼀个⾃定义异常 ----->  实现⼀个全局异常处理类,捕获抛出的⾃定义异常,进⾏后
续处理。
  ⾸先说下根据这个思路的实现⾮常简单,也很实⽤,只是有两个吹⽑求疵的问题。第⼀,注解需要写成@CheckParam({param1,param2})这样的形式加在⽅法上,还需
要⼿动写param1,param2这样的要进⾏⾮空判断的参数的名称,⽽不是像@RequestParam注解直接加在参数上就OK了。第⼆,@RequestParam注解本⾝会判断⾮空,⼀
起使⽤时,⾃⼰的注解⽆效。
  下⾯先说第⼀个问题,这个问题⾸先想到实现。
代码1:继承HandlerInterceptorAdapter ,实现。代码说明:(代码中的CheckParamNull是⾃定义注解,ResponseBo是⾃定义的json返回类)
1 public class ParameterNotBlankInterceptor extends HandlerInterceptorAdapter {
2    //在请求处理之前进⾏调⽤(Controller⽅法调⽤之前
3    @Override
4    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
5
6        //如果不是映射到⽅法直接通过
7        if (!(o instanceof HandlerMethod)) {
8            return true;
9        }
10        HandlerMethod handlerMethod = (HandlerMethod) o;
11        Parameter[] methodParameters = Method().getParameters();
12        for (int i = 0; i< methodParameters.length; i++){
13            if(methodParameters[i].getAnnotation(ParamNotBlank.class) != null){
14                CheckParamNull  noblank =methodParameters[i].getAnnotation(CheckParamNull.class);
15                Object obj = Parameter(methodParameters[i].getName());
16                httpServletResponse.setCharacterEncoding("UTF-8");
17                if (obj == null){
18                Writer().(ssage())));
19                    return false;
20                }else if(obj instanceof String && StringUtils.isBlank((String)obj)){
21                Writer().(ssage())));
22                    return false;
23                }
24                return true;
25            }
26        }
27        return true;
28    }
29
30    //请求处理之后进⾏调⽤,但是在视图被渲染之前(Controller⽅法调⽤之后)
31    @Override
32    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
33
34    }
35
36    //在整个请求结束之后被调⽤,也就是在DispatcherServlet 渲染了对应的视图之后执⾏(主要是⽤于进⾏资源清理⼯作)
37    @Override
38    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
39
40    }
41 }
代码2:加⼊链
1 @Configuration
2public class InterceptorConfig extends WebMvcConfigurerAdapter {
3public void addInterceptors(InterceptorRegistry registry){
4        registry.addInterceptor(new ParameterNotBlankInterceptor())
5                .addPathPatterns("/**");
6super.addInterceptors(registry);
7    }
8 }
  使⽤这个⽅法的问题来⾃代码1的第15⾏,当我们同时使⽤@RequestParam的时候,如果在@RequestParam(value="重新定义的请求参数名称")的value属性重新定义了请求名称,那么代码1的第15⾏
Object obj = Parameter(methodParameters[i].getName());拿到的就⼀定是null,因为methodParameters[i].getName()拿到的名称是请求⽅法中参数列表中的参数名,这样即便request中有值,但是由于名称不同,也就⽆法取到值。虽然说@RequestParam本⾝就会判断⾮空,没有必要再⽤⾃定义注解,但是保不准别⼈会拿来⼀起⽤,如果能保证使⽤⾃定义注解时@RequestParam不会⼀起出现,那么这个⽅法也是可⾏的。或者还有⼀种⽅式就是在判断⾃定义注代码解这⾥同时判断是否存在@RequestParam注解,如果有就⽤@RequestParam注解中的value值
来request中取值,但是springweb绑定注解那么多,也不能肯定别⼈就会⽤@RequestParam,如果要判断使⽤那得⼀⼤串代码,果断放弃。
  出现这个问题后,就在想有没有在springweb绑定注解之后⼯作的⽅法,当时想到看能不能新加⼊⼀个解析器实现
org.hod.support.HandlerMethodArgumentResolver接⼝,以求在springweb绑定注解的解析器⼯作之后执⾏,写完之后发现⽆法执⾏到⾃⼰的解析器。追源码看到如下内容:
51/**
52    * Add the given {@link HandlerMethodArgumentResolver}.
53*/
54public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
55this.argumentResolvers.add(resolver);
56return this;
57    }
58
59/**
60    * Add the given {@link HandlerMethodArgumentResolver}s.
61    * @since 4.3
62*/
63public HandlerMethodArgumentResolverComposite addResolvers(@ resolvers) { 64if (resolvers != null) {
65for (HandlerMethodArgumentResolver resolver : resolvers) {
66this.argumentResolvers.add(resolver);
67            }
68        }
69return this;
70    }
71
72/**
73    * Add the given {@link HandlerMethodArgumentResolver}s.
74*/
75public HandlerMethodArgumentResolverComposite addResolvers(
76            @Nullable List<? extends HandlerMethodArgumentResolver> resolvers) {
77
78if (resolvers != null) {
79for (HandlerMethodArgumentResolver resolver : resolvers) {
80this.argumentResolvers.add(resolver);
81            }
82        }
83return this;
84    }
85
86/**
87    * Return a read-only list with the contained resolvers, or an empty list.
88*/
89public List<HandlerMethodArgumentResolver> getResolvers() {
90return Collections.unmodifiableList(this.argumentResolvers);
91    }
springboot aop92
93/**
94    * Clear the list of configured resolvers.
95    * @since 4.3
96*/
97public void clear() {
98this.argumentResolvers.clear();
99    }
100
101
102/**
103    * Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
104    * {@link HandlerMethodArgumentResolver}.
105*/
106    @Override
107public boolean supportsParameter(MethodParameter parameter) {
108return (getArgumentResolver(parameter) != null);
109    }
110
111/**
112    * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
113    * @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
114*/
115    @Override
116    @Nullable
117public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
118            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
119
120        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
121if (resolver == null) {
122throw new IllegalArgumentException("Unknown parameter type [" + ParameterType().getName() + "]");
122throw new IllegalArgumentException("Unknown parameter type [" + ParameterType().getName() + "]");
123        }
solveArgument(parameter, mavContainer, webRequest, binderFactory);
125    }
126
127/**
128    * Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
129*/
130    @Nullable
131private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
132        HandlerMethodArgumentResolver result = (parameter);
133if (result == null) {
134for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
135if (logger.isTraceEnabled()) {
136                    ace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
137                            GenericParameterType() + "]");
138                }
139if (methodArgumentResolver.supportsParameter(parameter)) {
140                    result = methodArgumentResolver;
141this.argumentResolverCache.put(parameter, result);
142break;
143                }
144            }
145        }
146return result;
147    }
148
149 }
  代码131⾏开始的该类最后⼀⽅法。134⾏遍历所有的Resolvers解析器。139⾏,当拿到第⼀个⽀持解析parameter这个参数的methodArgumentResolver时,就会break 出循环,不会再其他的解析器,⽽@RequestParam的解析器RequestParamMethodArgumentResolver排名第⼀,我写了半天的解析器没啥关系,郁闷···。这样的话只能重写@RequestParam的解析器RequestParamMethodArgumentResolver,⼯作量巨⼤且⿇烦,像我这种菜直接忽略该⽅式···
  来到了最后⼀个招数,既然不能对@RequestParam这种级别的注解做个啥⼯作,反正⼈家也有⾮空判断了,直接⽤就好了,但是不能使⽤它的异常直接返回,因为要统⼀嘛,拦截下异常就OK了,写到这⾥⾃⼰都快崩溃了,前⾯长篇⼤论的半天原来都是废话!【⼿动加个表情吧···】
  拦截异常的代码:
  说明:ServletRequestBindingException是RequestParamMethodArgumentResolver抛出的参数为空异常的⽗类
/**
* GlobalExceptionHandler
*/
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
@ExceptionHandler(value = ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseBo handleValidationException(ValidationException exception) {
(Message(),400);
}
@ExceptionHandler(value = ServletRequestBindingException.class)
public ResponseBo handleServletRequestBindingException(ServletRequestBindingException exception) {
(Message(),400);
}
}
  然后要做的就是没有springweb绑定注解时的⾮空判断,⽽且如果同时使⽤了springweb绑定注解例如@RequestParam和⾃定义注解,还不能影响@RequestParam的使⽤。由于会在请求到达controller之前动作,也就是在@RequestParam的解析器RequestParamMethodArgumentResolver之前执⾏,所以不能⽤,这样绕了⼀⼤圈⼜回到了AOP的怀抱···
  ⾸先注解代码:
1 @Target({ElementType.PARAMETER,ElementType.METHOD})
2 @Retention(RetentionPolicy.RUNTIME)
3public @interface CheckParamNull {
4
5    String message() default "参数为空";
6/**
7    * 是否为null,默认不能为null
8*/
9boolean notNull() default true;
10
11/**
12    * 是否⾮空,默认可以为空
13*/
14boolean notBlank() default false;
15 }
  Aspect代码:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。