Java中的注解
- 什么是注解Java基础 :反射、注解、代理、线程池、依赖的学习和理解
- 注解的基本使用Java面试---Day5_Liknananana的博客-CSDN博客
- 为什么需要注解,注解的意义何在?Java注解Annotation基础 - Java开发 - 开发语言与工具 - 深度开源
- 反射+注解 依赖注入Java基础 :反射、注解、代理、线程池、依赖的学习和理解
- 什么是依赖注入反射、注解与依赖注入总结
- 依赖注入的使用场景用Dagger2在Android中实现依赖注入
参考资料:关于注解更详细的资料https://zhuanlan.zhihu.com/p/202586806、
这是一篇入门级的文章,通过它你可以学到以下知识:
- 什么是注解
- 注解的本质
- 注解的分类
- 元注解是什么
- 注解的生命周期
- 注解的意义
- 怎么自定义注解
什么是注解
注解和注释很像,注释是给程式员看的,而注解是给计算机看的。 注解是一种对程序的说明,告诉计算机这段程序或者这个变量、方法的作用。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。 被注解标记的这些类,方法,属性等,在虚拟机运行时可以获取到(某些注解只在编译时或者编写代码时起作用,运行起来的时候不会加载到字节码文件中),并做出相应的处理。
注解的本质
注解本质上就是一个接口,该接口默认继承java.lang.annotation.Annotation接口。
注解的分类
按照一般的分类方式,可以分为三类:
- 标准注解
- 元注解
- 自定义的注解
如果按照注解的生命周期分类,同样可以分为三类,主要是通过描述注解的元注解的**@Retention属性的不同取值来分类:**
- SOURCE(源码注解):当前被描述的注解只在源文件中有效,不会保留到class字节码文件中,也不会被JVM读取到。
- CLASS(编译时注解):当前被描述的注解,会保留到class字节码文件中,不会被JVM读取到。
- RUNTIME(运行时注解):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
标准注解
@Override:用于检测被该注解标注的方法是否时继承自父类(接口)的。以下就是源码中Override
注解的定义,可以看到它是一个源码注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Deprecated:用于标注已经过时的方法; @SuppressWarnnings:用于通知Java编译器禁止特定的编译警告;
元注解
元注解就是用于注释注解的注解,用来标识一个注解的作用和使用范围等。 **@Target:**描述注解能够作用的位置,以下是源码中元注解Target的定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
之前我们提到注解可以声明在包、类、字段、方法、局部变量、方法参数等的前面,但并不是每一个注解都能声明在这些地方。可以使用**@Target来限制注解的作用位置** @Target的属性的取值可以是:
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention:描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。 Annotation被保留的时间有长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取。因此有些注解仅在编译阶段生效,在运行阶段失效就是通过这个元注解@Retention来描述的 按照注解的生命周期分类可以分为:源码注解,编译时注解,运行时注解 @Retention属性的取值可以是(RetentionPoicy):
- SOURCE(源码注解):当前被描述的注解只在源文件中有效,不会保留到class字节码文件中,也不会被JVM读取到。
- CLASS(编译时注解):当前被描述的注解,会保留到class字节码文件中,不会被JVM读取到。
- RUNTIME(运行时注解):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
注解的意义
根据注解的生命周期其实可以总结出在不同场景下的意义:
- 为编译器提供辅助信息,以便于检测错误,抑制警告
- 编译源代码时进行额外处理,生成源代码或者xml文件
- 在运行时进行额外的处理
简而言之,注解起到**“描述、配置”**的作用。
怎么定义注解
语法格式
定义注解的语法格式,其实在上面在标准注解Override以及元注解Target处,我们已经见过注解的定义了
public @interface 注解名称{
... 属性列表
}
如何解析注解
注解作为供计算机“阅读”的注释,那么对于运行时注解(关于源码注解和编译时注解暂不做讨论)计算机或者说Java虚拟机是如何从注解中获取到信息的呢? 首先之前有提到注解的本质是接口,它们都直接或者间接的继承自**Annotation**
首先我们要明白通过反射可以动态获取对象信息和调用对象方法。而一个定义为运行时可见的注解,在运行时,也就是当编译的产物class文件,被转载到虚拟机中时,class文件中的Annotation就会被虚拟机读取。解析,运行时注解的原理正是利用了反射机制(解析编译时注解用到的是APT机制,之后再讲)。 在Java的**java.lang.reflect**
包下新增了**AnnotatedElement**
接口,这个接口是所有程序元素(Field、Method、Package、Class和Constructor)的父类。程序通过反射获取了某个类的**AnnotatedElement**
对象之后,程序就可以调用该对象的如下七个方法来访问Annotation信息:
**T getAnnotation(Class annotationClass) **
返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;
**Annotation[] getDeclaredAnnotation(Class)**
返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解;
**Annotation[] getAnnotations()**
返回该程序元素上存在的所有注解;
**Annotation[] getDeclaredAnnotations()**
返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;
**Annotation[] getAnnotationsByType(Class)**
返回直接存在于此元素上指定注解类型的所有注解;
**Annotation[] getDeclaredAnnotationsByType(Class)**
返回直接存在于此元素上指定注解类型的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注解;
**boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)**
判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false;
解析注解代码实现
理论先行,下面我们不妨动手实践,自定义一个注解,并通过反射去解析注解
自定义注解
这里简单的声明一个水果名称的注解,可以看到我使用**@Target**,并指定它的属性是**ElementType.FIELD**
,说明这是一个作用在成员属性上的注解;并且使用了**@Retention**,指定它的属性是**RetentionPolicy.RUNTIME**
,说明这是一个运行时注解
/**
* 水果名称注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitName {
String value() default " ";
}
使用注解
使用注解这没什么好说的,就是在对应的程序元素上声明即可
public class Apple {
@FruitName("Apple")
private String appleName;
public String getAppleName() {
return appleName;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public void displayName(){
System.out.println(getAppleName());
}
}
解析注解
最后实现利用反射来获取到注解的信息,拿到注解的信息,我们就可以将这些信息添加到我们的实例对象中。
public class AnnotationParser {
public static void main(String[] args) {
//获取到声明的属性
Field[] fields = Apple.class.getDeclaredFields();
for (Field field : fields) {
//通过getAnnotation拿到程序元素上指定类型的注解
if (field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = field.getAnnotation(FruitName.class);
System.out.println("水果的名称:" + fruitName.value());
}
}
}
}
不出意外的化输出的结果就是:
水果的名称:Apple
总结:所以注解就是一个接口,它用来说明程序元素的作用,让计算机在运行时根据注解的内容去动态的处理被注解的程序元素,而解析注解的内容是通过Java的**java.lang.reflect**
包下新增了**AnnotatedElement**
接口实现的。
注解的用途
在面试中也遇到过这个问题,一般什么时候我们会去使用注解呢?当时是比较懵的,自己开发中没有去自定义过注解,但是很多框架中都用到了注解,明白为什么这些框架中要去使用它,这个问题就也明晰了。
实现反射动态加载类
在java编程中,我们往往是面向抽象编程,利用依赖倒转原则,通常在参数传递时定义的类型是抽象类型,而具体哪一个子类通过反射动态去加载,这样能降低系统的耦合性。首先定义一个用于声明类名和方法的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
String className();
String methodName();
}
下面就是具体的使用了,我们可以通过反射获取注解上声明的具体类名和方法名,然后再利用反射去找到这个类,并执行其中的方法
@Pro(className = "annomation.Apple",methodName = "displayName")
public class ReflectPro {
public static void main(String[] args) {
//1.1获取该类的字节码文件对象
Class<ReflectPro> reflectProClass= ReflectPro.class;
//1.2获取上边的注解对象
Pro an=reflectProClass.getAnnotation(Pro.class);
//1.3调用注解对象中定义的抽象方法,获取返回值;
String className=an.className();
String methodName=an.methodName();
//验证是否真的获取到了
System.out.println(className);
System.out.println(methodName);
//3.加载该类进入内存
Class cls= null;
try {
cls = Class.forName(className);
//4.创建对象
Object obj=cls.newInstance();
//5.获取方法对象
Method method=cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}