spring 自定义注解
翻看公司代码,看到了自定义的注解,查了查,再次记录一下,还是太菜
下面是我的实现
1. 自定义注解
package com.test;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
/**
* Documented 注解表明这个注解应该被 javadoc工具记录
*/
@Documented
/**
*注解会被保留到那个阶段 有三个取值
* SOURCE 只在源代码级别保留,编译时就会被忽略
* CLASS 编译时被保留,在class文件中存在,但JVM将会忽略
* RUNTIME 运行时被JVM或其他使用反射机制的代码所读取和使用
*/
@Retention(RetentionPolicy.RUNTIME)
/**
* 说明了Annotation所修饰的对象范围
*/
@Target({FIELD, ANNOTATION_TYPE, PARAMETER})
/**
* 表示处理的这个注解的类是哪一个
* 我这里的是 myValidateINtercepter处理
*/
@Constraint(validatedBy = myValidateINtercepter.class)
@interface Enums{
String enumList() default ""; //1
String message() default "值不在字典内"; //2
String field() default ""; //3
/**
* 下面的这俩是必须的。我第一次没加 payload ,就报错了 ,报错为 Enums contains Constraint annotation, but does not contain a payload parameter.
*/
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
- 补充说明
- groups,payload 是必须添加的,要不然就会报错,我也不知为啥就报错。反正必须要添加,下面贴出我的报错信息
上面代码里面,注解里面的 1,2,3 字段是根据自己的业务来的 望知晓
2. 自定义校验类
实现ConstraintValidator接口,一个参数为标注的枚举类型,第二个参数为需要校验的类型
package com.test;
import com.test.Enums;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
/**
* @description:
* @author: liuchen
* @date: 2020/7/29
**/
public class myValidateINtercepter implements ConstraintValidator<Enums,String> {
private String enumList;
private String field;
private Enums constraintAnnotation;
/**
* 做初始化工作
* @param constraintAnnotation 自己定义的注解
*/
@Override
public void initialize(Enums constraintAnnotation) {
this.enumList = constraintAnnotation.enumList();
this.field = constraintAnnotation.field();
this.constraintAnnotation = constraintAnnotation;
}
/**
* 做校验
* @param s 待校验的值
* @param constraintValidatorContext 上下文
* @return
*/
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
//检查 代校验的值合法否?
if (Arrays.asList(enumList.split(",")).contains(s)) {
//合法,通过
return true;
}else{
/**
* 禁用默认的消息模板
*/
constraintValidatorContext.disableDefaultConstraintViolation();
/**
* 设置自己的消息模板
*/
constraintValidatorContext.buildConstraintViolationWithTemplate(String.format("%s 当前值不在字段范围内,字典范围为[%s]",s,enumList))
.addConstraintViolation();
//不合法,不通过
return false;
}
}
}
关于constraintValidatorContext 的解释
- 这种东西debug一下,看的清清楚楚。下面贴出我的debug的结果
3. 使用示例
3.1 实体类
/**
* 实体类
*/
@Data
class Student{
//原生注解
@Min(value = 0,message = "id不能小于0")
private Integer id;
@NotBlank(message = "不能为空字符串")
private String name;
private Double age;
/*1*/@Valid
private List<Address> list;
@Valid
private Address address;
/*3*/
public interface AGroup{
}
public interface BGroup{
}
}
@Data
class Address{
@NotBlank()
/*2*/@Enums(enumList = "1,2",groups = Student.AGroup.class)
private String province;
@Enums(enumList = "1,2,3",groups = Student.BGroup.class)
private String city;
}
关于Student的解释说明
- 注释1 @Valid:表示被标注的属性也是需要校验的。
- 说一下 @Valid和@Validated的区别
- @Validated不能作用于字段,这个点开源码看@Target就可以看出来.
- @Validated里面支持分组校验。@Valid不支持
- 关于分组校验,请看
3.2 controller 注释1
和 这个例子中 注释2 中的groups 属性,在controller里面指定分组,校验规则就会按照对应的组来,如果不指定,就会校验那些没有指明组的属性
- 注释3 表示不同的组,这其实就是一个接口,里面啥都不用写。
3.2 controller
package com.test;
import com.alibaba.fastjson.JSONObject;
import com.common.model.ApiResult;
import com.common.model.ResultCode;
import lombok.Data;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Constraint;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import static java.lang.annotation.ElementType.*;
/**
* @description:
* @author: liuchen
* @date: 2020/7/29
**/
@RestController
@RequestMapping("myTest")
public class MyTestcontroller {
@PostMapping("/test")
public ApiResult test(@RequestBody /*1*/@Validated(value = Student.BGroup.class) Student student ,/*2*/BindingResult result){
/*3*/ //方式一
if(result.hasErrors()){
//在绑定参数的时候出错啦
StringBuffer returnResult = new StringBuffer();
/*5*/List<ObjectError> allErrors = result.getAllErrors();
allErrors.stream().forEach(error->returnResult.append(error.getDefaultMessage()));
return new ApiResult(200,returnResult.toString());
}
System.out.println(JSONObject.toJSONString(student));
return ApiResult.success();
}
/*4*/
//方式二 定义一个统一的异常处理, 可以采用 @ControllerAdvice 的那种全局统一处理
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ApiResult handleException(MethodArgumentNotValidException bindResult){
StringBuffer stringBuffer = new StringBuffer();
List<ObjectError> allErrors = bindResult.getBindingResult().getAllErrors();
allErrors.stream().forEach(error -> {System.out.println(String.format("code:%s,msg:%s",error.getCode(),error.getDefaultMessage()));stringBuffer.append(error.getDefaultMessage());});
return new ApiResult(200,stringBuffer.toString());
}
}
关于controller的解释说明
- 注释1 指定了需要校验的组是 BGroup。属于AGroup的就不会被校验
- 如果校验不通过,springmvc就会抛出异常MethodArgumentNotValidException,此时就有两种选择
- 注释4 统一的异常处理,
- 注释2 将BindingResult 入参,在业务方法里面处理。
- 注释2 关于BindingResult。至于这个对象是怎么来的,就要你熟悉springmvc 的流程了。 在绑定参数和校验的时候就会有这个对象
- 注释5 通过bindResult就可以得到所有的错误,还记得在自定义校验的时候设置的模板了吗
2. 自定义校验类
,从这里就能得到自定义的错误消息
4. 请求示例
{
"id": 1,
"name": "fdfdsfdsf",
"age": 0.0,
"list": [
{
"province": "1434343",
"city": "1"
}
],
"address": {
"province": "2",
"city": "1"
}
}
5. Validation.buildDefaultValidatorFactory()
这个东西就是 自己校验,springmvc帮我们封装了校验,这个方法就是 原生的校验
- 校验代码
B name = new B(-1, "", 54545454.0);
Set<ConstraintViolation<Object>> validateSet = Validation.buildDefaultValidatorFactory() //定义验证工厂
.getValidator() //得到验证器
.validate(name,a1.class); //验证 (对象,组)//这个组就是上面列子中的组
String messages = validateSet.stream()
.map(ConstraintViolation::getMessage)
.reduce((m1, m2) -> m1 + "|| " + m2)
.orElse("参数输入有误!");
throw new IllegalArgumentException(messages);
}
- 自己的实体
@Data
class B{
@Min(value = 0,groups = a1.class,message = "不能小于0")
private Integer id;
@NotBlank(message = "不能为空",groups =b1.class )
private String name;
@Max(value = 100,message = "不能大于100")
private Double age;
public B( Integer id, String name, Double age) {
this.id = id;
this.name = name;
this.age = age;
}
}
interface a1{
}
interface b1{
}
这就是 springmvc帮我们封装好的部分,原生的实现就那样
大体就是这样,以备之后使用