Java注解的简单实现

289 阅读2分钟

@Retention

@Retention注解用来表示注解保留的阶段(源码、字节码、运行时)

@Retention(RetentionPolicy.SOURCE) 注解仅存在于源码中,在class字节码文件中不包含

@Retention(RetentionPolicy.CLASS),默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得

@Retention(RetentionPolicy.RUNTIME),注解会在class字节码文件中存在,在运行时可以通过反射获取到

自定义注解只能使用RetentionPolicy.RUNTIME

@Retention(RetentionPolicy.RUNTIME)
public @interface FirstAnnotation {
    
}

@Target

@Target用来限定注解可以使用的范围,可以是类、方法、参数等

@Target(ElementType.TYPE) 作用接口、类、枚举、注解

@Target(ElementType.FIELD) 作用属性字段、枚举的常量

@Target(ElementType.METHOD) 作用方法

@Target(ElementType.PARAMETER) 作用方法参数

@Target(ElementType.CONSTRUCTOR) 作用构造函数

@Target(ElementType.LOCAL_VARIABLE)作用局部变量

@Target(ElementType.ANNOTATION_TYPE)作用于注解

@Target(ElementType.PACKAGE) 作用于包

@Target(ElementType.TYPE_PARAMETER) 作用于类型泛型

@Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class

一般比较常用的是ElementType.TYPE

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FirstAnnotation {
    
}

@Documented

@Documented的作用是能够将注解中的元素包含到 Javadoc 中去

@Inherited

当一个类被@Inherited标记时,如果子类没有被其他注解标记,那么子类会继承父类的注解

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FirstAnnotation {
    
}

@Repeatable

@Repeatable标记的注解表示可以同时标记一个对象多次

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FirstAnnotation.class)
public @interface FirstAnnotation {
    RepeatAnnotionTest[] value();
}
@RepeatAnnotionTest(value = "Java")
@RepeatAnnotionTest(value = "C++")
@RepeatAnnotionTest(value = "Python")
public class TestRepeat {

}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FirstAnnotation.class)
public @interface RepeatAnnotionTest {
    String value() default "";
}

实例

假如我们现在要写一个校验的注解,用来校验年龄不能低于多少岁

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ValidateAge {
    int minAge() default 1;
}
public class TestValidateAge {
    public static void main(String[] args){
        TestValidateAge testValidateAge = new TestValidateAge();
        testValidateAge.validateAge(15);
    }

    @ValidateAge(minAge = 20)
    public void validateAge(int age){
        System.out.println(processAnnotion(age));
    }

    //解析注解
    private String processAnnotion(int age){
        try {
           //通过反射拿到对应的方法
           Method validateAge = TestValidateAge.class.getDeclaredMethod("validateAge",int.class);
           //判断方法上是否存在指定的注解
           boolean isPresent = validateAge.isAnnotationPresent(ValidateAge.class);
           if(isPresent){
               //获取方法上的注解
               ValidateAge annotation = validateAge.getAnnotation(ValidateAge.class);
               //获取注解中属性的值
               int minAge = annotation.minAge();
               if(age < minAge ){
                   return "年龄不能低于" + minAge + "岁,校验失败";
               } else {
                   return "你的年龄是" + age + "岁,校验通过";
               }
           }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return "校验失败";
    }
}
@RestController
@RequestMapping("/user")
@Validated   //controller开启校验
public class TestController {

    @PostMapping("/test/{id}")
    public String test(
            @PathVariable @Max(value = 10,message = "不可以超过10") Integer id, //@Max hibernate自带的校验 高版本的springboot好像没有 要自行导包 
            @RequestBody @Valid TestDTO testDTO) {  //表示要校验对象类型的 和@Validated一样的效果 
        System.out.println(testDTO.getName());
        TestDTO testDTO1 = TestDTO.builder().name("李四").age(19).build(); //开启了loombook的build模式 这样设置值比较简单 但是好像有坑 比如不能像平时那样new对象了 

        return "hello world";
    }
}
@ExceptionHandler(MethodArgumentNotValidException.class) //参数校验不通过的异常
@ResponseBody   
@ResponseStatus(code = HttpStatus.BAD_REQUEST) // 状态码400
public UnifyResponse handleBeanValidation(HttpServletRequest req, MethodArgumentNotValidException e) {
    String requestUrl = req.getRequestURI(); //请求地址
    String method = req.getMethod();  //请求方法

    List<ObjectError> errors = e.getBindingResult().getAllErrors(); //获取拿到所有报错信息
    String message = this.formatAllErrorMessages(errors);  //拼起来 一起返回

    return new UnifyResponse(10001, message,method + " " + requestUrl);
}
private String formatAllErrorMessages(List<ObjectError> errors) {
    StringBuffer errorMsg = new StringBuffer();
    errors.forEach(error ->
            errorMsg.append(error.getDefaultMessage()).append(';')
    );
    return errorMsg.toString();
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordEqual {
    int min() default 4;  //定义变量
    int max() default 8;  //定义变量
    String message() default "passwords are not equal"; //定义变量
    Class<?>[] groups() default {};  //好像是固定的 暂时不知道怎么用
    Class<? extends Payload>[] payload() default {}; //暂时不知道哪来干嘛
}

@Target 元注解 定义了注解要用在什么上面 @Retention 元注解 定义了注解的保留阶段 @Constraint(validatedBy = PasswordValidator.class) 指向处理逻辑的类

public class PasswordValidator implements ConstraintValidator<PasswordEqual, TestDTO> {

    private int min;
    private int max;
    @Override
    public void initialize(PasswordEqual constraintAnnotation) { //初始化参数
       this.min = constraintAnnotation.min();
       this.max = constraintAnnotation.max();
    }

    @Override
    public boolean isValid(TestDTO testDTO, ConstraintValidatorContext constraintValidatorContext) {
        String password1 = testDTO.getPassword1();
        String password2 = testDTO.getPassword2();
        boolean match = password1.equals(password2); //处理逻辑 当两个新旧密码不一样的时候 返回false 表示校验不通过 
        return match;
    }
}
@Data
@Builder
@PasswordEqual(message = "两次密码不相同")  //传参 
public class TestDTO {
    private String name;
    private Integer age;
    private String password1;
    private String password2;
}