我们在日常开发中,编写 HTTP 接口时需要对接口接受的参数进行有效性、非空验证。通常做法是通过大量的 if 语句进行判断,但是这样做太过麻烦。我们是不是可以通过类似 @RequestParam 注解的方式对每个参数进行有效性、非空验证呢?这样我们就可以避免编写大量的 if 语句。
在 Spring 框架中,提供了 WebMvcConfigurer 接口。该接口其实是 Spring 内部的一种配置方式,采用 JavaBean 的形式来代替传统的 xml 配置文件形式进行针对框架个性化定制,可以自定义一些 Handler,Interceptor,ViewResolver,MessageConverter。基于 java-based 方式的 Spring MVC 配置,需要创建一个配置类并实现 WebMvcConfigurer 接口。
在 Spring Boot 1.5 版本中,你可以重写 WebMvcConfigurerAdapter 的方法来添加自定义拦截器、消息转换器、参数解析器等。Spring Boot 2.0 后,该类被标记为 @Deprecated(被弃用)。官方推荐直接实现 WebMvcConfigurer 接口(因为 Java8 支持接口提供默认方法,这也就替代了 WebMvcConfigurerAdapter 迭代器的作用)或者直接继承 WebMvcConfigurationSupport 类。
笔者推荐去实现 WebMvcConfigurer 接口。
自定义一个注解,使用自定义的注解修饰接口的某个参数,然后自定义参数解析器。验证是否触发我们的解析器,后续在进一步深入。代码如下:
(1)自定义注解 Demo1Annotation,允许该注解用来修饰方法参数。如下:
import java.lang.annotation.*; // 允许将注解包含在 JavaDoc 文档中 @Documented // 指定注解使用范围为参数 @Target(ElementType.PARAMETER) // 指定注解会在 class 字节码中存在,在运行时可以通过反射获取 @Retention(RetentionPolicy.RUNTIME) public @interface Demo1Annotation { }
(2)创建自定义的 @Configuration 类,继承 WebMvcConfigurerAdapter 适配器。重写 addArgumentResolvers() 方法,在该方法中创建一个匿名 HandlerMethodArgumentResolver 类,实现该类的 supportsParameter() 和 resolveArgument() 方法。如下:
import org.springframework.context.annotation.Configuration; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; /** * 配置参数解析处理器 * @author Administrator 2021/4/16 12:40 * @version 1.0 */ @Configuration public class Demo1Handel extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new HandlerMethodArgumentResolver(){ @Override public boolean supportsParameter(MethodParameter methodParameter) { if(methodParameter.hasParameterAnnotation(Demo1Annotation.class)) { return true; } return false; } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { if(methodParameter.hasParameterAnnotation(Demo1Annotation.class)) { System.out.println("Demo1Annotation"); } // 获取修饰的参数名称 String paramName = methodParameter.getParameterName(); // 获取被修饰参数接收的值 String paramValue = nativeWebRequest.getParameter(paramName); System.out.println(paramName + " ==> " + paramValue); return paramValue; } }); } }
其中:
supportsParameter():方法用于判断给定的参数是否是我们想要的参数。可以通过 MethodParameter 的 hasParameterAnnotation() 方法判断给定的参数是否被自定义的注解 Demo1Annotation 修饰。如果被 Demo1Annotation 修饰,则返回 true;否则,返回 false;
resolveArgument():方法中依然可以使用 hasParameterAnnotation() 方法判断参数是否被自定义的注解 Demo1Annotation 修饰。如果是,我们就可以做一些业务处理(上面方法仅仅执行 System.out.println("Demo1Annotation") 语句)。
(3)定义一个控制器,在控制器的 demo() 方法中使用 @Demo1Annotation 注解修饰 msg 参数。如下:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 使用自定义的注解修饰接口参数 * @author Administrator 2021/4/16 12:47 * @version 1.0 */ @RestController @RequestMapping("/demo1") public class Demo1Controller { @GetMapping("/demo") public String demo(@Demo1Annotation String msg, String version){ return "From Server ==> msg=" + msg + ", version=" + version; } }
自定义一个注解,注解提供 notNull(判空,如果为true,表示参数不能为空;如果为false,则表示允许参数为空)和 msg(当参数没有通过验证是抛出的错误信息)成员,然后使用该注解去验证接口参数是否为空。如下:
(1)自定义 Demo2Annotation 注解,如下:
import java.lang.annotation.*; /** * 自定义注解,验证接口参数 * @author Administrator 2021/4/16 12:37 * @version 1.0 */ // 允许将注解包含在 JavaDoc 文档中 @Documented // 指定注解使用范围为参数 @Target(ElementType.PARAMETER) // 指定注解会在 class 字节码中存在,在运行时可以通过反射获取 @Retention(RetentionPolicy.RUNTIME) public @interface Demo2Annotation { /** * 验证参数不为空,默认为false * @return */ boolean notNull() default false; /** * 指定错误提示信息 * @return */ String msg() default ""; }
(2)创建自定义的 @Configuration 类,继承 WebMvcConfigurerAdapter 适配器。重写 addArgumentResolvers() 方法,在该方法中创建一个匿名 HandlerMethodArgumentResolver 类,实现该类的 supportsParameter() 和 resolveArgument() 方法。如下:
import org.springframework.context.annotation.Configuration; import org.springframework.core.MethodParameter; import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.lang.reflect.AnnotatedElement; import java.util.List; /** * 配置参数解析处理器 * @author Administrator 2021/4/16 12:40 * @version 1.0 */ @Configuration public class Demo2Handel extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new HandlerMethodArgumentResolver(){ @Override public boolean supportsParameter(MethodParameter methodParameter) { if(methodParameter.hasParameterAnnotation(Demo2Annotation.class)) { return true; } return false; } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { // 获取修饰的参数名称 String paramName = methodParameter.getParameterName(); // 获取被修饰参数接收的值 String paramValue = nativeWebRequest.getParameter(paramName); System.out.println(paramName + " ==> " + paramValue); if(methodParameter.hasParameterAnnotation(Demo2Annotation.class)) { System.out.println("Demo2Annotation"); System.out.println("开始校验参数:" + paramName); Demo2Annotation annotation = methodParameter.getParameterAnnotation(Demo2Annotation.class); System.out.println("annotation=" + annotation); boolean notNull = annotation.notNull(); if(notNull && StringUtils.isEmpty(paramValue)) { String errorMsg = annotation.msg(); throw new IllegalArgumentException(errorMsg); } } return paramValue; } }); } }
(3)定义控制器 Demo2Controller,在 demo() 方法中使用 @Demo2Annotation 注解来验证 msg 参数。如下:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 使用自定义的注解验证接口参数 * @author Administrator 2021/4/16 12:47 * @version 1.0 */ @RestController @RequestMapping("/demo2") public class Demo2Controller { @GetMapping("/demo") public String demo(@Demo2Annotation(notNull = true, msg = "msg 不能为空") String msg, String version){ return "From Server ==> msg=" + msg + ", version=" + version; } }
注意:我们可以通过 NativeWebRequest 类的 getHeader() 方法获取 HTTP 请求头中的字段,例如:
// 权限校验 String token = nativeWebRequest.getHeader("token"); System.out.println("token=" + token);
上面代码获取 HTTP 头中的 token 字段,可以通过该种方式实现权限验证。