注解的原理与实现
注解这个东西自从SpringBoot以来一直是Java开发者们必备的生存技巧呀,我们平时几乎大部分时间都是面向注解编程,通过注解我们可以节约大量的时间。用过了这么多的注解,那么我们否有关注过注解的实现原理呢?所以本篇文章主要是讲述注解的有关操作,自己实现一个注解来体会注解的实现原理,注解也不是特别高深的东西,掌握了自然就明白了。
注解的基本原理
注解本来的意思就是用来做标注用:可以在类、字段变量、方法、接口等位置进行一个特殊的标记,为后续做一些诸如: 代码生成、数据校验、资源整合等工作做铺垫。所以注解就是做标记用的,注解一旦对代码标注完成,后续就可以结合Java强大的反射机制,在运行时动态地获取到注解的标注信息,从而可以执行很多其他逻辑,完成我们想要的自动化工作。所以,反射机制很重要。
注解的使用示例
假设我们现在有个Person类,这个Person类要当做参数传入,我们要对参数进行校验:
1public class Person {
2 private Integer id;
3 private String name;
4 private Integer age;
5
6 //Getter and Setter
7}
如果没有注解,那么我们就需要写这样一长串的if else校验:
1public String addPerson(Person person){
2 if(person == null){
3 return "参数为空";
4 }
5
6 if(person.getId() == null || "".equals(person.getId())){
7 return "Person's id is null";
8 }
9
10 if(person.getName() == null || "".equals(person.getName())){
11 return "Person's name is null.";
12 }
13
14 if(person.getName().length() < 3){
15 return "Person's name length must lager 3.";
16 }
17
18 if(person.getAge() == 0){
19 return "Person's age is null.";
20 }
21
22 if(person.getAge() <= 0 || person.getAge() >= 150){
23 return "Person's age error.";
24 }
25}
所以,可以参考一下如何使用注解来校验这些参数:
1public class Person {
2 @NotNull(message = "传入的Id为空值")
3 @NotEmpty(message = "传入的Id为空字符串")
4 private String id;
5
6 @NotNull(message = "传入的Name为空值")
7 @NotEmpty(message = "传入的Name为空字符串")
8 @Length(min = 3, max = 30, message = "姓名长度必须3-30之间")
9 private String name;
10
11 @NotNull(message = "传入的Age为空值")
12 @Min(value = 0, message = "年龄应该在0-150之间")
13 @Max(value = 150, message = "年龄应该在0-150之间")
14 private Integer age;
15
16 //Getter and Setter.
17}
@Length注释的实现
本篇文章中,我们就来实现一下@Length这个注解,这个注解学会了,其他注解也都是一样的:
step1.定义注解 @Length
1import java.lang.annotation.ElementType;
2import java.lang.annotation.Retention;
3import java.lang.annotation.RetentionPolicy;
4import java.lang.annotation.Target;
5
6@Target({ElementType.FIELD})
7@Retention(RetentionPolicy.RUNTIME)
8public @interface Length{
9 // 允许的字符串长度最小值
10 int min();
11
12 // 允许的字符串长度最大值
13 int max();
14
15 // 自定义错误提示
16 String errorMsg();
17}
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(),代码中会逐行用注释去解释想要达到的目的,认真看一下每一行的注释:
1public class LengthValidator {
2 public static String validateField(Object object) throws IllegalAccessException {
3 // 获取字段值
4 // 对本文来说就是Person的id、name、age三个字段
5 Field[] fields = object.getClass().getDeclaredFields();
6
7 // 逐个字段校验,看看哪个字段标了注解
8 for (Field field: fields){
9 // if判断:检查字段上面有没有标注@Length注解
10 if(field.isAnnotationPresent(Length.class)){
11 // 通过反射获取到该字段上标注的@Length的注解的详细信息
12 Length length = field.getAnnotation(Length.class);
13 // 让我们在反射时看到私有变量
14 field.setAccessible(true);
15 // 获取实际字段的值
16 int value = ((String)field.get(object)).length();
17 // 将实际字段的值和注解的标记值进行对比
18 if(value < length.min() || value > length.max()){
19 return length.errorMsg();
20 }
21 }
22 }
23
24 return null;
25 }
26}
step3.使用自定义注解
此时,Person类只需要加上此注解
1public class Person {
2 private String id;
3
4 @Length(min = 3, max = 30, errorMsg = "姓名长度必须3-30之间")
5 private String name;
6
7 private Integer age;
8
9 //Getter and Setter
10}
然后使用即可:
1public class AnnotationTest {
2 public static void main(String[] args) throws IllegalAccessException {
3 Person person = new Person();
4 person.setName("13");
5 person.setAge(10);
6 person.setId("001");
7
8 String validateField = LengthValidator.validateField(person);
9 if(validateField == null)
10 System.out.println(person);
11 else
12 System.out.println(validateField);
13 }
14}