1 注解
注解提供一种为程序元素设置元数据的方法。
元数据是添加到程序元素如方法、字段、类和包上的额外信息
注解是一种分散式的元数据设置方式,XML是集中式的设置方式
注解不会直接干扰程序代码的运行
所有的注解类都是继承自java.lang.annotation.Annotation接口
注解的功能:
1.作为特定的标记,用于告诉编译器一些信息(@Override表示方法重写了父类的方法,编译器会去检查是否父类有对应的方法)
2.编译时动态处理,如动态生成代码(lombook工具中的@Data方法,在编译时动态生成类的set/get等方法)
3.运行时动态处理,作为额外信息的载体,如获取注解信息(一个注解,本质上来说只是一种特殊的注释而已)
注解的分类
1.标准注解:@Override、@Deprecated、@SuppressWarnings
2.元注解:@Retention、@Target、@Inherited、@Documented
3.自定义注解
2 元注解
用来修饰注解的注解,通常用在注解的定义上
@Target:注解的作用目标
@Retention:注解的生命周期
@Documented:注解是否应当被包含在JavaDoc文档中
@Inherited:是否允许子类继承该注解
@Target
描述所修饰的注解的使用范围
查看Target(java.lang.annotation.Target)的源码
注解Target需要传入枚举类型ElementType的数组来表示该注解的使用范围
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType的属性和代表的使用范围
属性 | 注解使用范围 |
TYPE | 允许所修饰的注解作用在类、接口、枚举上 |
FIELD | 允许所修饰的注解作用在类字段上 |
METHOD | 允许所修饰的注解作用在类方法上 |
PARAMETER | 允许所修饰的注解作用在方法参数上 |
CONSTRUCTOR | 允许所修饰的注解作用在类构造函数上 |
LOCAL_VARIABLE | 允许所修饰的注解作用在本地局部变量上 |
ANNOTATION_TYPE | 允许所修饰的注解作用在注解上 |
PACKAGE | 允许所修饰的注解作用在包上 |
TYPE_PARAMETER | 允许所修饰的注解作用在泛型类的参数上 |
TYPE_USE | 允许所修饰的注解作用在上面所提的所有范围中 |
举几个我们熟悉的注解的例子:
Spring框架中的@Controller注解
Spring框架中的@Autowired注解
Spring框架中的@RequestParam注解
@Target注解
@Retention
标注注解被保留时间的长短
用于定义注解的生命周期,即被描述的注解在什么范围内有效
查看Retention(java.lang.annotation.Retention)的源码
注解Retention需要传入枚举类型RetentionPolicy来表示该注解的生命周期
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
RetentionPolicy的属性和代表的生命周期
属性 | 含义 |
Source | 在源文件中保存注解,但是在编译的class文件中就不会存在注解了 |
Class | 注解不仅会出现在源代码中,还会出现在class文件中 |
RUNTIME | 在运行时可以通过反射能够获取该注解的信息 |
3 自定义注解
属性名其实就是注解的参数,返回值的类型就是参数的类型
1.定义注解类
只能作用在类的成员变量上,生命周期是保留到运行时的,可以通过反射获取
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfoAnnotation {
//名字
public String name();
//年龄
public int age() default 22;
//擅长的开发语言
public String[] language();
}
作用在类和方法上,生命周期是保留到运行时的,可以通过反射获取
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseInfoAnnotation {
//课程名
public String courseName();
//课程标签
public String courseTag();
}
2.在类上使用注解
@CourseInfoAnnotation(courseName = "软件工程",courseTag = "1")
public class Course {
@PersonInfoAnnotation(name="陈老师",language = {"JAVA","C++"})
private String teacher;
@CourseInfoAnnotation(courseName = "计算机基础",courseTag = "2")
private void getInfo()
{}
}
在程序里不对注解做处理,注解看起来没什么用,恰恰是这样,所以注解不干扰程序的运行
3.在运行时获取注解信息
获取注解信息依赖于AnnotatedElement接口,而反射中的类Method/Field等都实现了这个类
获取注解信息:
Annotation[] getAnnotations()----获取对象上(类)的所有注解,包括对象继承的注解
getAnnotation(Class<T> annotationClass)---获取对象上的单个指定注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)---判断指定类型的注解是否存在于此元素上
解析类的注解:
public class AnnotationParser {
//解析类注解
static void parserTypeAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("com.base.annotation.Course");
//这里只获取class对象的注解,不会获取类成员的注解
Annotation[] annotations=clazz.getAnnotations();
for(Annotation annotation:annotations){
CourseInfoAnnotation courseInfoAnnotation=(CourseInfoAnnotation) annotation;
System.out.println(courseInfoAnnotation.courseName()+" "+courseInfoAnnotation.courseTag());
}
}
public static void main(String[] args) throws ClassNotFoundException {
AnnotationParser.parserTypeAnnotation();
}
}
解析成员字段的注解:
public class AnnotationParser {
//解析成员字段注解
static void parserFieldAnnotation() throws ClassNotFoundException, NoSuchFieldException {
Class clazz = Class.forName("com.base.annotation.Course");
Field[] fields = clazz.getDeclaredFields();//获取类的每一个成员字段
for(Field field:fields)
{
//判断每个成员字段是否有被PersonInfoAnnotation注解修饰
if(field.isAnnotationPresent(PersonInfoAnnotation.class))
{
PersonInfoAnnotation personInfoAnnotation=field.getAnnotation(PersonInfoAnnotation.class);
System.out.println(personInfoAnnotation.name()+ personInfoAnnotation.age());
for(String l:personInfoAnnotation.language())
{
System.out.println(l);
}
}
}
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
AnnotationParser.parserFieldAnnotation();
}
}
解析成员函数的注解:
public class AnnotationParser {
//解析成员方法注解
static void parserMethodAnnotation() throws ClassNotFoundException{
Class clazz = Class.forName("com.base.annotation.Course");
Method[] methods= clazz.getDeclaredMethods();//获取类的每一个成员方法
for(Method method:methods)
{
//判断每个成员方法是否有被CourseInfoAnnotation注解修饰
if(method.isAnnotationPresent(CourseInfoAnnotation.class))
{
CourseInfoAnnotation courseInfoAnnotation=method.getAnnotation(CourseInfoAnnotation.class);
System.out.println(courseInfoAnnotation.courseName()+" "+courseInfoAnnotation.courseTag());
}
}
}
public static void main(String[] args) throws ClassNotFoundException{
AnnotationParser.parserMethodAnnotation();
}
}
如果修改注解的Retention属性不为RUNTIME,执行后无法解析注解。可以发现一旦修改Retention不为RUNTIME,注解就无法通过反射来获取
4 注解的工作原理
1.通过键值对的形式为注解属性赋值
2.编译器检查注解的使用范围,将注解信息写入元素属性表
3.运行时JVM将RUNTIME的所有注解属性取出并最终存入map中
4.创建AnnotationInvocationHandler实例并传入前面的map
5.JVM使用JDK动态代理为注解生成代理类,并初始化处理器
6.调用invoke方法,通过传入方法名返回注解对应的属性值