在上一章,我在成员变量上使用注解,但注解无效,并没有起到约束的作用。为什么呢?那是因为:
- 注解本质只是代表代码之外的额外信息。至于这些信息用来干嘛,与注解本身没有关系。想要利用注解实现一些特殊功能,需要注解处理器来完成
在用代码实现注解处理器之前,我们还需要了解另外一个概念,元注解。所以,处理器的学习,分为两部分
- 元注解
- 处理器的实现
元注解
元注解,是用来声明自定义注解的一些特殊属性。我们常用的两种是 @Target 和 @Retention属性。
@Target
该元注解用来声明和限定注解使用的地方。
- 给整个类添加注解
- 给类中成员变量添加注解
- 给类中构造方法添加注解
- 给类中普通方法添加注解
下面的注解,由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);
}
}
对象创建成功:
到现在为止,今天的注解内容就学习完了。
接下来,我会做一个习题,来巩固今天的注解内容,题目很简单,将在下一章呈现~