为了避免对大量参数进行过多的非空校验,我们可以自定义一个非空验证的注解,因为spring自带的@RequestParam并不能对参数进行非空
首先需要创建一个spring boot项目,并引入相关maven依赖,pom文件如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ooliuyue</groupId> <artifactId>aoptest</artifactId> <version>0.0.1-SNAPSHOT</version> <name>aoptest</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 复制代码
总体思路:自定义一个注解,对必填的参数加上该注解,然后定义一个切面,校验该参数是否为空,如果为空则抛出自定义的异常,该异常被自定义的异常处理器捕获,然后返回相应的错误信息。 创建一个名为'ParamCheck'的注解,代码如下:
package com.ooliuyue.springboot_aoptest.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Auther: ly * @Date: 2018/12/27 11:43 */ /** * “参数不为空”注解,作用于方法上 */ @Target(ElementType.PARAMETER) //表示该注解作用于方法参数上 /** * @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃; * 而另一些却被编译在class文件中; * 编译在class文件中的Annotation可能会被虚拟机忽略, * 而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。 * 使用这个meta-Annotation可以对 Annotation的“生命周期”限制。 * 作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效) * 取值(RetentionPoicy)有: * 1.SOURCE:在源文件中有效(即源文件保留) * 2.CLASS:在class文件中有效(即class保留) * 3.RUNTIME:在运行时有效(即运行时保留) */ @Retention(RetentionPolicy.RUNTIME) public @interface ParamCheck { /** * 是否非空,默认不能为空 * @return */ boolean notNull() default true; } 复制代码
这个异常类与自定义注解配合一起使用,当加上'@ParamCheck'的参数为空时,抛出该异常,代码如下:
package com.ooliuyue.springboot_aoptest.exception; /** * 自定义异常类 * @Auther: ly * @Date: 2018/12/27 09:55 */ public class ParamIsNullException extends RuntimeException { private final String parameterName; private final String parameterType; public String getParameterName() { return parameterName; } public String getParameterType() { return parameterType; } public ParamIsNullException(String parameterName, String parameterType) { super(""); this.parameterName = parameterName; this.parameterType = parameterType; } public String getMessage(){ return "请求参数 " + this.parameterName + " 不能为空 !"; } } 复制代码
代码如下:
package com.ooliuyue.springboot_aoptest.aop; import com.ooliuyue.springboot_aoptest.annotation.ParamCheck; import com.ooliuyue.springboot_aoptest.exception.ParamIsNullException; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** * @Auther: ly * @Date: 2018/12/27 10:10 */ @Component //把切面类加入到IOC容器中 @Aspect public class ParamCheckAop { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 定义一个切入点,范围为controller包下的类 */ @Pointcut("execution(public * com.ooliuyue.springboot_aoptest.controller..*.*(..))") public void checkParam(){ } /** * 前置通知,在连接点之前执行的通知 * @param joinPoint */ @Before("checkParam()") public void doBefore(JoinPoint joinPoint){ logger.info("进行非空验证"); } @Around("checkParam()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); //得到拦截的方法 Method method = signature.getMethod(); //获取方法参数注解,返回二维数组是因为某些参数可能存在多个注解 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); if (parameterAnnotations == null || parameterAnnotations.length == 0) { return proceedingJoinPoint.proceed(); } //获取方法参数名 String[] paramNames = signature.getParameterNames(); //获取参数值 Object[] paranValues = proceedingJoinPoint.getArgs(); //获取方法参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterAnnotations.length; i++ ) { for (int j = 0; j < parameterAnnotations[i].length; j++) { //如果该参数前面的注解是ParamCheck的实例,并且notNull()=true,则进行非空校验 if (parameterAnnotations[i][j] != null && parameterAnnotations[i][j] instanceof ParamCheck && ((ParamCheck) parameterAnnotations[i][j]).notNull()) { paramIsNull(paramNames[i], paranValues[i], parameterTypes[i] == null ? null : parameterTypes[i].getName()); break; } } } return proceedingJoinPoint.proceed(); } /** * 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理) * * @param joinPoint */ @AfterReturning("checkParam()") public void doAfterReturning(JoinPoint joinPoint) { } /** * 参数非空校验,如果参数为空,则抛出ParamIsNullException异常 * @param paramName * @param value * @param parameterType */ private void paramIsNull(String paramName, Object value, String parameterType) { if (value == null || "".equals(value.toString().trim())) { throw new ParamIsNullException(paramName, parameterType); } } } 复制代码
该异常处理器捕获在ParamCheckAop类中抛出的ParamIsNullException异常,并进行处理,代码如下:
package com.ooliuyue.springboot_aoptest.exception; import com.ooliuyue.springboot_aoptest.common.Result; import com.ooliuyue.springboot_aoptest.enums.EnumResultCode; import com.ooliuyue.springboot_aoptest.utils.ResponseMsgUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @Auther: ly * @Date: 2018/12/27 13:50 */ @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler({MissingServletRequestParameterException.class, ParamIsNullException.class}) public Result<String> getException(Exception ex){ LOGGER.error("request Exception:", ex); return ResponseMsgUtil.builderResponse(EnumResultCode.FAIL.getCode(),ex.getMessage(),null); } } 复制代码
新建一个名为HelloController的类,用于测试,代码如下:
package com.ooliuyue.springboot_aoptest.controller; import com.ooliuyue.springboot_aoptest.annotation.ParamCheck; import com.ooliuyue.springboot_aoptest.common.Result; import com.ooliuyue.springboot_aoptest.enums.EnumResultCode; import com.ooliuyue.springboot_aoptest.utils.ResponseMsgUtil; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Auther: ly * @Date: 2018/12/27 11:43 */ @RestController public class HelloController { /** * 测试@RequestParam注解 * @param name * @return */ @GetMapping("/hello1") public Result<String> hello1(@RequestParam String name){ return ResponseMsgUtil.builderResponse(EnumResultCode.SUCCESS.getCode(),"请求成功","Hello," + name); } /** * 测试@ParamCheck注解 * @param name * @return */ @GetMapping("/hello2") public Result<String> hello2(@ParamCheck String name){ return ResponseMsgUtil.builderResponse(EnumResultCode.SUCCESS.getCode(),"请求成功","Hello," + name); } /** * 测试@ParamCheck 和 @RequestParam * * @param name * @return */ @GetMapping("/hello3") public Result<String> hello3(@ParamCheck @RequestParam String name){ return ResponseMsgUtil.builderResponse(EnumResultCode.SUCCESS.getCode(),"请求成功","Hello," + name); } } 复制代码
参数值为空在浏览器输入http://localhost:8080/hello1,结果如下:
控制台输出错误信息参数值不为空在浏览器输入http://localhost:8080/hello1?name=张三,结果如下:
参数值为空在浏览器输入http://localhost:8080/hello2?name=,结果如下:
控制台输出错误信息参数值不为空在浏览器输入http://localhost:8080/hello2?name=李四,结果如下:
和测试@ParamCheck 的结果一样