SpringBoot中⾃定义注解实现参数⾮空校验的⽰例
前⾔
由于刚写项⽬不久,在写 web 后台接⼝时,经常会对前端传⼊的参数进⾏⼀些规则校验,如果⼊参较少还好,⼀旦需要校验的参数⽐较多,那么使⽤ if 校验会带来⼤量的重复性⼯作,并且代码看起来会⾮常冗余,所以我⾸先想到能否通过⼀些⼿段改进这点,让 Controller 层减少参数校验的冗余代码,提升代码的可阅读性。
经过阅读他⼈的代码,发现使⽤ annotation 注解是⼀个⽐较⽅便的⼿段,SpringBoot ⾃带的 @RequestParam 注解只会校验请求中该参数是否存在,但是该参数是否符合⼀些规格⽐如不为 null 且不为空就⽆法进⾏判断的,所以我们可以尝试⼀下增强请求参数中的注解。
准备⼯作
有了前⾯的思路,我们先搭⼀个架⼦出来。
SpringBoot 2.3.5.REALEASE
JDK 1.8
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"
xsi:schemaLocation="/POM/4.0.0 /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.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.bestzuo</groupId>
<artifactId>springboot-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-annotation</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-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引⼊AOP相应的注解-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其中 aspectjweaver ⽤于引⼊ AOP 的相关的注解,如 @Aspect、@Pointcut 等.
使⽤⾃定义注解实现统⼀⾮空校验
总体思路:⾃定义⼀个注解,对必填的参数加上该注解,然后定义⼀个切⾯,校验该参数是否为空,如果为空则抛出⾃定义的异常,该异常被⾃定义的异常处理器捕获,然后返回相应的错误信息。
1.⾃定义注解
创建⼀个名为 ParamCheck 的注解,代码如下:
package cn.bestzuo.springbootannotation.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 参数不能为空注解,作⽤于⽅法参数上
*
* @author zuoxiang
* @since 2020-11-11
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamCheck {
/**
* 是否⾮空,默认不能为空
*/
boolean notNull() default true;
}
其中@Target注解中的ElementType.PARAMETER表⽰该注解的作⽤范围,我们查看源码可以看到,注解的作⽤范围定义⽐较⼴泛,可以作⽤于⽅法、参数、构造⽅法、本地变量、枚举等等。
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/
** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
当然,我们定义的注解可以扩展,不仅仅去校验参数是否为空,⽐如我们可以增加字符串长度的校验。
2.⾃定义异常类
我们在这⾥⾃定义异常的原因,是为了配合⾃定义注解使⽤,⼀旦校验出不符合我们⾃定义注解规格的参数,可以直接抛出⾃定义异常返回。代码如下:
package cn.ption;
public class ParamIsNullException extends RuntimeException {
private final String parameterName;
private final String parameterType;
public ParamIsNullException(String parameterName, String parameterType) {
super("");
this.parameterName = parameterName;
this.parameterType = parameterType;
}
/**
* 重写了该⽅法
*
* @return 异常消息通知
*/
@Override
public String getMessage() {
return "Required " + this.parameterType + " parameter \'" + this.parameterName + "\' must be not null !";
}
public final String getParameterName() {
return this.parameterName;
}
public final String getParameterType() {
return this.parameterType;
}
}
该异常继承RuntimeException,并定义了两个成员属性、重写了getMessage() ⽅法
之所以⾃定义该异常,⽽不⽤现有的org.springframework.web.bind.MissingServletRequestParameterException 类,是因为MissingServletRequestParameterException为Checked 异常,在动态代理过程中,很容易引发flect.UndeclaredThrowableException
异常。
3.⾃定义 AOP
代码如下:
package cn.bestzuo.springbootannotation.aop;
import cn.bestzuo.springbootannotation.annotation.ParamCheck;
import cn.ption.ParamIsNullException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.flect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import flect.Method;
@Component
@Aspect
springboot aoppublic class ParamCheckAop {
private static final Logger LOGGER = Logger(ParamCheckAop.class);
/**
* 定义有⼀个切⼊点,范围为 controller 包下的类
*/
@Pointcut("execution(public * ller..*.*(..))")
public void checkParam() {
}
@Before("checkParam()")
public void doBefore(JoinPoint joinPoint) {
}
/**
* 检查参数是否为空
*
* @param pjp 连接点
* @return 对象
* @throws Throwable 异常
*/
@Around("checkParam()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = ((MethodSignature) Signature());
/
/得到拦截的⽅法
Method method = Method();
//获取⽅法参数注解,返回⼆维数组是因为某些参数可能存在多个注解
Annotation[][] parameterAnnotations = ParameterAnnotations();
if (parameterAnnotations.length == 0) {
return pjp.proceed();
}
//获取⽅法参数名
String[] paramNames = ParameterNames();
//获取参数值
Object[] paramValues = Args();
/
/获取⽅法参数类型
Class<?>[] parameterTypes = ParameterTypes();
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], paramValues[i], parameterTypes[i] == null ? null : parameterTypes[i].getName());
break;
}
}
}
return pjp.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 || "".String().trim())) {
throw new ParamIsNullException(paramName, parameterType);
}
}
}
4.全局异常处理器
该异常处理器捕获在 ParamCheckAop 类中抛出的 ParamIsNullException 异常,并进⾏处理,代码如下: import cn.bestzuo.springbootannotationmon.Result;
import cn.ums.EnumResultCode;
import cn.bestzuo.springbootannotation.utils.ResponseMsgUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
public class GlobalExceptionHandler {
private static final Logger LOGGER = Logger(GlobalExceptionHandler.class);
/**
* 参数为空异常处理
*
* @param ex 异常
* @return 返回的异常
*/
@ExceptionHandler({MissingServletRequestParameterException.class, ParamIsNullException.class})
public Result<String> requestMissingServletRequest(Exception ex) {
<("request Exception:", ex);
return ResponseMsgUtil.builderResponse(Code(), ex.getMessage(), null);
}
/**
* 特别说明:可以配置指定的异常处理,这⾥处理所有
*
* @param request 请求
* @param e 异常体
* @return 返回的异常
*/
@ExceptionHandler(value = Exception.class)
public Result<String> errorHandler(HttpServletRequest request, Exception e) {
<("request Exception:", e);
ption();
}
}
5.测试
⾸先定义⼀个 Controller 进⾏测试:
@RestController
public class HelloController {
/**
* 测试@RequestParam注解
*
* @param name 测试参数
* @return 包装结果
*/
@GetMapping("/hello1")
public Result<String> hello1(@RequestParam String name) {
return ResponseMsgUtil.builderResponse(Code(), "请求成功", "Hello," + name);
}
/**
* 测试@ParamCheck注解
*
* @param name 测试参数
* @return 包装结果
*/
@GetMapping("/hello2")
public Result<String> hello2(@ParamCheck String name) {
return ResponseMsgUtil.builderResponse(Code(), "请求成功", "Hello," + name);
}
/**
* 测试@ParamCheck与@RequestParam⼀起时
*
* @param name 测试参数
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论