spring自定义注解(普通和属性校验)手动校验,校验并打印日志

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("绑定成功");
    }

猜你喜欢

转载自blog.csdn.net/Fire_Sky_Ho/article/details/120693085