编辑
2020-04-13
编程语言
00
请注意,本文编写于 1259 天前,最后修改于 105 天前,其中某些信息可能已经过时。

目录

注解的基本原理
注解的使用示例
@Length注释的实现
step1.定义注解 @Length
step2.获取注解并对其验证
step3.使用自定义注解

注解这个东西自从SpringBoot以来一直是Java开发者们必备的生存技巧呀,我们平时几乎大部分时间都是面向注解编程,通过注解我们可以节约大量的时间。用过了这么多的注解,那么我们否有关注过注解的实现原理呢?所以本篇文章主要是讲述注解的有关操作,自己实现一个注解来体会注解的实现原理,注解也不是特别高深的东西,掌握了自然就明白了。

注解的基本原理

注解本来的意思就是用来做标注用:可以在类、字段变量、方法、接口等位置进行一个特殊的标记,为后续做一些诸如: 代码生成、数据校验、资源整合等工作做铺垫。所以注解就是做标记用的,注解一旦对代码标注完成,后续就可以结合Java强大的反射机制,在运行时动态地获取到注解的标注信息,从而可以执行很多其他逻辑,完成我们想要的自动化工作。所以,反射机制很重要。

注解的使用示例

假设我们现在有个Person类,这个Person类要当做参数传入,我们要对参数进行校验:

java
public class Person { private Integer id; private String name; private Integer age; //Getter and Setter }

如果没有注解,那么我们就需要写这样一长串的if else校验:

java
public String addPerson(Person person){ if(person == null){ return "参数为空"; } if(person.getId() == null || "".equals(person.getId())){ return "Person's id is null"; } if(person.getName() == null || "".equals(person.getName())){ return "Person's name is null."; } if(person.getName().length() < 3){ return "Person's name length must lager 3."; } if(person.getAge() == 0){ return "Person's age is null."; } if(person.getAge() <= 0 || person.getAge() >= 150){ return "Person's age error."; } }

所以,可以参考一下如何使用注解来校验这些参数:

java
public class Person { @NotNull(message = "传入的Id为空值") @NotEmpty(message = "传入的Id为空字符串") private String id; @NotNull(message = "传入的Name为空值") @NotEmpty(message = "传入的Name为空字符串") @Length(min = 3, max = 30, message = "姓名长度必须3-30之间") private String name; @NotNull(message = "传入的Age为空值") @Min(value = 0, message = "年龄应该在0-150之间") @Max(value = 150, message = "年龄应该在0-150之间") private Integer age; //Getter and Setter. }

@Length注释的实现

本篇文章中,我们就来实现一下@Length这个注解,这个注解学会了,其他注解也都是一样的:

step1.定义注解 @Length

java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Length{ // 允许的字符串长度最小值 int min(); // 允许的字符串长度最大值 int max(); // 自定义错误提示 String errorMsg(); }

1、注解的定义有点像定义接口interface,但唯一不同的是前面需要加一个@符号

2、注解的成员变量只能使用基本类型、String或者enum枚举,比如int可以,但Integer这种包装类型就不行

3、像上面@Target、@Retention这种加在注解定义上面的注解,我们称为“元注解”, 元注解就是专门用于给注解添加注解的注解,简单理解就是:元注解就是天生就有的注解,可直接用于注解的定义上

4、@Target(xxx)用来说明该自定义注解可以用在什么位置,比如:

  • ElementType. FIELD:说明自定义的注解可以用于类的变量
  • ElementType. METHOD:说明自定义的注解可以于类的方法
  • ElementType. TYPE:说明自定义的注解可以用于类本身、接口或enum类型
  • 其实还有很多,如果记不住的话还是建议现用现查

5、@Retention (xxx)用说明你自定义注解的生命周期,比如:

  • @Retention (RetentionPolicy.RUNTIME):表示注解可以一直保留到运行时,因此可以通过反射获取注解信息
  • @Retention (RetentionPolicy.CLASS):表示注解被编译器编译进class文件,但运行时会忽略
  • @Retention (RetentionPolicy.SOURCE):表示注解仅在源文件中有效, 编译时就会被忽略

所以声明周期从长到短分别为:RUNTIME > CLASS > SOURCE,一般来说,如果需要在运行时去动态获取注解的信息,还是得用RUNTIME,就像本文所用。

step2.获取注解并对其验证

在运行时想获取注解所代包含的信息,该怎么办?我们得用Java的反射相关的知识!下面写了一个验证函数validate(),代码中会逐行用注释去解释想要达到的目的,认真看一下每一行的注释:

java
public class LengthValidator { public static String validateField(Object object) throws IllegalAccessException { // 获取字段值 // 对本文来说就是Person的id、name、age三个字段 Field[] fields = object.getClass().getDeclaredFields(); // 逐个字段校验,看看哪个字段标了注解 for (Field field: fields){ // if判断:检查字段上面有没有标注@Length注解 if(field.isAnnotationPresent(Length.class)){ // 通过反射获取到该字段上标注的@Length的注解的详细信息 Length length = field.getAnnotation(Length.class); // 让我们在反射时看到私有变量 field.setAccessible(true); // 获取实际字段的值 int value = ((String)field.get(object)).length(); // 将实际字段的值和注解的标记值进行对比 if(value < length.min() || value > length.max()){ return length.errorMsg(); } } } return null; } }

step3.使用自定义注解

此时,Person类只需要加上此注解

java
public class Person { private String id; @Length(min = 3, max = 30, errorMsg = "姓名长度必须3-30之间") private String name; private Integer age; //Getter and Setter }

然后使用即可:

java
public class AnnotationTest { public static void main(String[] args) throws IllegalAccessException { Person person = new Person(); person.setName("13"); person.setAge(10); person.setId("001"); String validateField = LengthValidator.validateField(person); if(validateField == null) System.out.println(person); else System.out.println(validateField); } }

mark

本文作者:Tim

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!