注解处理器

在上一章,我在成员变量上使用注解,但注解无效,并没有起到约束的作用。为什么呢?那是因为:

  • 注解本质只是代表代码之外的额外信息。至于这些信息用来干嘛,与注解本身没有关系。想要利用注解实现一些特殊功能,需要注解处理器来完成

在用代码实现注解处理器之前,我们还需要了解另外一个概念,元注解。所以,处理器的学习,分为两部分

  1. 元注解
  2. 处理器的实现

元注解

元注解,是用来声明自定义注解的一些特殊属性。我们常用的两种是 @Target 和 @Retention属性。

@Target

该元注解用来声明和限定注解使用的地方。

  1. 给整个类添加注解
  2. 给类中成员变量添加注解
  3. 给类中构造方法添加注解
  4. 给类中普通方法添加注解

下面的注解,由Target元注解声明,只能用在方法上。如果用在成员变量,或者构造方法中,会报错误。

@Target({ElementType.METHOD})
@interface MyAnnotation {}
class TestTarget {

    @MyAnnotation()
    int i;

    @MyAnnotation()
    public TestTarget(){}

    @MyAnnotation()
    public void method() {}
}

在这里插入图片描述如果想要注解作用在多个地方,用大括号把元注解的属性值括起来,因为Target的value属性在源码中,本身就是数组,所以可以用大括号给数组赋值的方式,给value赋值,其中 Type表示可以作用于整个类或者接口.
此演示,也演示了如何给注解体属性中,数组类型的属性赋值

@Target({ElementType.METHOD,ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.FIELD})
@interface MyAnnotation {}
 @MyAnnotation()
class TestTarget {

    @MyAnnotation()
    int i;

    @MyAnnotation()
    public TestTarget(){}

    @MyAnnotation()
    public void method() {}
}

此时,MyAnnotation 注解用来注解构造方法,和成员变量,或者类,都不会再有问题。

@Retention

我们知道,我们的代码运行过程,首先需要javac编译,生成一个字节码文件,然后是java命令,执行代码。
同理, 注解也有这三种状态,是由保留级别决定的。保留级别,是利用Retention元注解定义。

  • RetentionPolicy.SOURCE 注解将被编译器丢弃(字节码文件中没有)
  • RetentionPolicy.CLASS 注解在字节码文件中可用,但会被JVM丢弃,内存中没有。这是注解的默认保留级别
  • RetentionPolicy.RUNTIME JVM在运行时,注解信息也会被保留

注解处理器

注解处理器本身,没有特殊语法,他的实现,只是通过反射技术,获得所需注解信息,然后根据需求实现特殊功能。
所以,这篇文章,直接根据需求实现处理器,看看其过程是怎样的。

代码需求背景

  • 定义一个Student类,包含name和age两个成员
  • name,即学生名字中包含的字符个数不得超过指定值
  • age,即学生年龄必须在指定范围内
  • name 和 名字都满足条件才能创建学生对象,否则抛出异常

定义注解

此注解包含的信息是,学生年龄的上限和下限

@Retention(RetentionPolicy.RUNTIME)
public @interface AgeConstraint {
    int maxAge() ;
    int minAge() ;
}

此注解包含的信息是,学生名字长度的上限

@Retention(RetentionPolicy.RUNTIME)
public @interface NameLength {
    //表示学生名字的上线
    int lengthLimit();
}

定义Student类

public class Student {

    //合法取值范围[18, 25]
    @AgeConstraint(maxAge = 25, minAge = 18)
    int age;

    //名字长度不超过5个字
    @NameLength(lengthLimit = 5)
    String name;

    private Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

注解处理器

创建一个StudentFactory 类,这个类的角色就是注解处理器。它内部是怎么实现的呢?

package process;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class StudentFactoryV2 {
    //利用反射技术实现处理器的思想是,获取Student目标类的成员变量的注解信息,根据注解信息进行操作。
    //反射技术的起点是Class对象,那我们先创建Student类的Class对象
    Class studentClz;

    public StudentFactoryV2 (){
        studentClz = Student.class;
    }

    //定义一个方法,来获取并判断目标类的成员变量的注解信息.如果想创建的人所传递的参数合法,那就返回Student对象,否则,抛出异常
    public Student newStudent (int age, String name) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1: 获取成员变量
        Field ageField = studentClz.getDeclaredField("age");
        ageField.setAccessible(true);
        //2:判断该成员变量是否有注解
        if(ageField.isAnnotationPresent(AgeConstraint.class)){
            //3:获取该注解,以及注解属性
            AgeConstraint ageAnnotation = ageField.getAnnotation(AgeConstraint.class);
            int maxAge = ageAnnotation.maxAge();
            int minAge = ageAnnotation.minAge();

            //4:判断ageField成员变量是否符合注解约束
            if( age < minAge || age > maxAge){
                throw new IllegalArgumentException ("学生年龄超出范围: age = " + age);
            }
        }

        //同理,获取Student的姓名成员变量,以及该变量的注解,和注解属性。并判断是否满足约束,如果不满足则抛出异常
        Field nameField = studentClz.getDeclaredField("name");
        nameField.setAccessible(true);
        if(nameField.isAnnotationPresent(NameLength.class)){
            NameLength nameLength = nameField.getAnnotation(NameLength.class);
            int lengthLimit =  nameLength.lengthLimit();

            if(name.length() > lengthLimit){
                throw new IllegalArgumentException ("学生姓名太长: name = " + name);
            }
        }

        //到此,还没有抛出异常,说明使用者想要创建的Student对象,其传递的参数满足约束,那么,通过反射技术,为其创建对象并返回。
        Constructor constructor = studentClz.getDeclaredConstructor(int.class, String.class);
        constructor.setAccessible(true);
        Student student = (Student)constructor.newInstance(age, name);

        return student;
    }
}

接下来,我定义一个类,测试我的处理器是否能供成功运行。

public class TestNewStu {
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //利用处理器来帮我们创建对象,所以先创建处理器对象
        StudentFactoryV2 studentFactory = new StudentFactoryV2();
        int age = 35;
        String name = "张三";
        Student student = studentFactory.newStudent(age, name);
        System.out.println(student);
    }
}

通过测试代码,我们发现我传递的年龄参数是35,不符合注解中约束的[18,25],我运行一下。测试是否能够创建对象:
在这里插入图片描述
我改变参数,让年龄,和姓名都满足约束,测试能否创建对象?

public class TestNewStu {
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //利用处理器来帮我们创建对象,所以先创建处理器对象
        StudentFactoryV2 studentFactory = new StudentFactoryV2();
        int age = 20;
        String name = "张三";
        Student student = studentFactory.newStudent(age, name);
        System.out.println(student);
    }
}

对象创建成功:
在这里插入图片描述
到现在为止,今天的注解内容就学习完了。
接下来,我会做一个习题,来巩固今天的注解内容,题目很简单,将在下一章呈现~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值