1.准备环境
1.1 引入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 https://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.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yhy</groupId>
<artifactId>custom-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>custom-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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.13</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.利用aop自定义注解
annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnalysisActuator {
String note() default "";
}
aop
通知需要用到自定义注解类,方便拿到注解信息,就写在serviceStatistics方法参数里,spring自动注入
@Aspect
@Component
public class AnalysisActuatorAspect {
final static Logger log = LoggerFactory.getLogger(AnalysisActuatorAspect.class);
ThreadLocal<Long> beginTime = new ThreadLocal<>();
@Pointcut("@annotation(analysisActuator)")
public void serviceStatistics(AnalysisActuator analysisActuator) {
}
@Before("serviceStatistics(analysisActuator)")
public void doBefore(JoinPoint joinPoint, AnalysisActuator analysisActuator) {
// 记录请求到达时间
beginTime.set(System.currentTimeMillis());
log.info("note:{}", analysisActuator.note());
}
@After("serviceStatistics(analysisActuator)")
public void doAfter(AnalysisActuator analysisActuator) {
log.info("statistic time:{}, note:{}", System.currentTimeMillis() - beginTime.get(), analysisActuator.note());
}
}
service
@Service
public class HelloWorldService {
@AnalysisActuator(note = "获取聊天信息方法")
public String getHelloMessage(String name) {
return "Hello " + Optional.ofNullable(name).orElse("World!");
}
}
controller
@RestController
@RestController
public class HelloWorldController {
@Autowired
private HelloWorldService helloWorldService;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(String name) {
return helloWorldService.getHelloMessage(name);
}
}
访问 http://127.0.0.1:8081/hello
结果
note:获取聊天信息方法
statistic time:0, note:获取聊天信息方法
3. 自定义校验注解
需求:导入表格每行数据封装到类里,然后要校验数据合法性,如果数据有错误,则返回错误信息
问题:如果属性很多,一个一个if判断,那就得写很多if,不优雅
解决:通过实现ConstraintValidator完成自定义校验注解,填写错误信息,然后手动校验,错误则获取错误信息
@Constraint 指定校验实现类,两个属性groups和payload是必须添加的(实现ConstraintValidator需要)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ExcelDataValidator.class})
public @interface ExcelDataVaild {
int min() default 2;
int max() default 255;
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
校验类需要实现ConstraintValidator接口。
接口使用了泛型,需要指定两个参数,第一个自定义注解类,第二个为需要校验的数据类型。
实现接口后要override两个方法,分别为initialize方法和isValid方法。其中initialize为初始化方法,可以在里面做一些初始化操作。isValid方法就是我们最终需要的校验方法了,可以在该方法中实现具体的校验步骤。
public class ExcelDataValidator implements ConstraintValidator<ExcelDataVaild, String> {
private int min;
private int max;
@Override
public void initialize(ExcelDataVaild excelDataVaild) {
min = excelDataVaild.min();
max = excelDataVaild.max();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StrUtil.isBlank(value) || value.length() < min || value.length() > max) {
return false;
}
return true;
}
}
需要校验的类
@Data
public class User {
@ExcelDataVaild(min = 2, max = 6,message = "名字错误")
private String name;
@ExcelDataVaild(min = 11, max = 11,message = "手机号错误")
private String phone;
@ExcelDataVaild(min = 5, max = 32,message = "地址错误")
private String address;
}
手动校验工具
@Slf4j
public class ValidatorUtils {
private static Validator validatorFast = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
private static Validator validatorAll = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
/**
* 校验遇到第一个不合法的字段直接返回不合法字段,后续字段不再校验
*
* @param <T>
* @param domain
* @return
* @throws Exception
* @Time 2020年6月22日 上午11:36:13
*/
public static <T> Set<ConstraintViolation<T>> validateFast(T domain) {
Set<ConstraintViolation<T>> validateResult = validatorFast.validate(domain);
if (validateResult.size() > 0) {
log.info(validateResult.iterator().next().getPropertyPath() + ":" + validateResult.iterator().next().getMessage());
}
return validateResult;
}
/**
* 校验所有字段并返回不合法字段
*
* @param <T>
* @param domain
* @return
* @throws Exception
* @Time 2020年6月22日 上午11:36:55
*/
public static <T> Set<ConstraintViolation<T>> validateAll(T domain) {
Set<ConstraintViolation<T>> validateResult = validatorAll.validate(domain);
if (validateResult.size() > 0) {
Iterator<ConstraintViolation<T>> it = validateResult.iterator();
while (it.hasNext()) {
ConstraintViolation<T> cv = it.next();
log.info(cv.getPropertyPath() + ":" + cv.getMessage());
}
}
return validateResult;
}
}
业务层
@Service
public class UserService {
public void vaildUser() {
//假装读取excel表格数据并封装到user
User user = new User();
user.setName("1");
user.setPhone("1231233213");
user.setAddress("eeddd");
Set<ConstraintViolation<User>> validateResult = null;
try {
validateResult = ValidatorUtils.validateAll(user);
if (validateResult.size() > 0) {
//获取检验信息,进行业务处理
System.out.println(validateResult.iterator().next().getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
控制层
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/user", method = RequestMethod.GET)
public void user() {
userService.vaildUser();
}
}
访问 http://127.0.0.1:8081/user
结果
phone:手机号错误
name:名字错误
对带有注解 javax.validation.Valid 的请求参数对象 进行 请求参数检验和输出
自定义一个注解
/**
* 对带有注解 javax.validation.Valid 的请求参数对象 进行 请求参数检验和输出
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReqParamValidAndLog {
}
切面
先输出请求参数,在校验,不通过就把检验信息返回
@Aspect
@Component
@Slf4j
public class ReqParamValidAndLogAspect {
@Around("@annotation(com.aspect.ReqParamValidAndLog)")
public Object validateParameters(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String[] parameterNames = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
Annotation[] parameterAnnotations = method.getParameterAnnotations()[i];
for (Annotation annotation : parameterAnnotations) {
if (annotation.annotationType().equals(Valid.class)) {
Object req = args[i];
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
String fullMethodName = className + "." + methodName;
String reqParamName = parameterNames[0];
log.info("请求参数打印,方法 {},请求参数对象名{}:{}", fullMethodName, reqParamName, JSON.toJSONString(req));
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
Validator validator = factory.getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(req);
//返回检验不通过信息
if (CollUtil.isNotEmpty(violations)) {
ConstraintViolation<Object> violation = violations.iterator().next();
return Result.ofFailMsg(violation.getPropertyPath() + " " + violation.getMessage());
}
}
}
}
}
return joinPoint.proceed(args);
}
}
请求类
@Data
public class TraceLabelParentChildBindOfBatchReq {
@NotEmpty
private String code;
@NotNull
private List<String> childCodeList;
}
控制层
注解写在方法上,会去打印带有@Valid注解的参数,并进行校验,错误就会返回错误信息
@PostMapping("parentChildBindOfBatch")
@ReqParamValidAndLog
public Result<String> parentChildBindOfBatch(@Valid @RequestBody TraceLabelParentChildBindOfBatchReq req) {
return Result.ofSuccessMsg("绑定成功");
}
@PostMapping("parentChildBindOfCsvFile")
@ReqParamValidAndLog
public Result<String> parentChildBindOfCsvFile(@RequestPart("csvFile") MultipartFile csvFile, @Valid TraceLabelParentChildBindOfCsvFileReq req) {
return Result.ofSuccessMsg("绑定成功");
}