关于注解的使用,用过应该java
的应该都不陌生,最著名的莫过于@Override
,所以这篇文章主要是讲讲注解背后的那些事情,至于普通的使用等可以参考下面的相关文章。
一、注解的基本内容
做什么事之前我们首先要搞清为什么?那么注解的出现是为什么?如果你受够了Spring和Hibernate的配置文件中繁琐的配置,因为写错一个字母整个程序都运行不起来,那么你确实有理由去了解注解,它可以:
- 使代码更加干净简洁,有时候一个注解表达的意思超过整个配置文件;
- 编译器可以在编译期间对类型进行检查,想想一个字母出错的情况吧!
- 可以在运行期间方便的进行读取,多亏了java的反射机制;
由于注解的这些特点,一些流行的框架都既支持注解配置方式又支持文件配置方式,比如Spring
、Hibernate
等,
Mybatis是个坑,两种支持的方式并不相同,有些事情只能XML配置文件来做,而注解是做不来的,比如复杂的ResultMap、批量插入和更新等;
1. 注解的本来面目
沿袭关于Java 中Enum 的全方位剖析这篇文章的做法,我们探究一下究竟注解是什么?我们先自定义一个注解,关于如果自定义注解的详细内容下面将会说,这里只是简单的作为一个例子!如下Path.java
:
@Target({ ElementType.FIELD })
@Retention(RUNTIME)
public @interface Path {
String a = "abc";
String value();
}
我们通过javac
编译这个注解之后也会生成对应的字节码文件,然后通过javap
反编译,结果如下:
发现了一个事实,其实所谓的注解其实在底层是一个继承自java.lang.annotation.Annotation
接口的接口,那么结果就明晰了,接口有的特性注解应该都会有:
- 可以声明方法,但是不能有实现,而且所有的方法都默认是
public abstract
; - 可以声明字段,但是所有字段必须进行初始化,因为所有字段都默认是
public static final
;
但是对于在Java8
中新出现的接口中的默认方法是不可以在注解中使用的。除了带有接口的特性,还可以直接使用java.lang.annotation.Annotation
中的方法。
2. 自定义注解
在分析注解的实际内容之后,我们有必要去了解怎么使用注解?或者说怎么写我们自己的注解?从上面可以看出注解的定义@interface
和接口很相似(实际上就是接口),编译之后也会生成一个class
文件。
2.1 元注解
所谓元注解就是用来描述其他注解类型的,跟所谓的元数据是相同的意思。Java默认提供的元注解有四种:
@Target
:表明该注解可以用在类的哪些元素上,可用的值包含在java.lang.annotation.ElementType枚举类下,一般常用的包括用在类上、字段上、方法上等;@Retention
:表明该注解存在的阶段,有三个值:
RetentionPolicy.SOURCE
:该注解只存在于源代码中,编译之后则会消失;RetentionPolicy.CLASS
:该注解在编译之后仍然存在,但是在运行时不需要虚拟机来保存,当注解没有使用@Retention
,该值也是默认值;RetentionPolicy.RUNTIME
:该注解在编译之后仍然存在,在运行时需要虚拟机来保存,因为要通过反射获取,这也是我们常用的;
@Inherited
:表明被该元注解标识的注解可以被继承,例如如果上面的@Path
注解被@Inherited
标识,当它被用在父类上时,当在子类上通过反射获取注解时也会获取到;@Documented
:表明在生成Javadoc文档时也会包括该注解;
注意:在使用注解时,可以通过静态导入
import static
的方式只导入我们需要的依赖,而不是全部,例如:
import static java.lang.annotation.RetentionPolicy.RUNTIME;
2.2 注解的使用
在一个注解中也可以包含方法和字段,就像我们在上面说到的一样,但是也可以不包含任何元素,这样的注解称为标记注解,例如@FunctionalInterface
。常见的注解使用一般都是通过键值对,即一个名称对应一个属性。但是在这其中还是有一些细节值得注意:
value()
方法:默认情况下,如果注解中包含且只包含value()方法时,使用注解时可以不加value这个属性名;默认值
default
:注解中的方法与接口中的方法不同的是,虽然也没有实现,但是可以设置默认值,当使用注解的时候没有给出该方法的数据时,则注解处理器使用该默认值,另外:- 默认值必须是常量,不能是不确定的值,包括不能是null;
- 如果想要通过默认值指定热定的状态,则可以通过约定-1或者”“等值;
例子如下:
@Target({ ElementType.FIELD }) @Retention(RUNTIME) public @interface Path { String value() default ""; }
注解中的元素类型是有限定的,只能是如下几种:
- 所有的基本类型:
- String:
- 枚举类型:典型的就是几个元注解;
- 注解类型:没错,注解中也能使用注解类型的元素,例如
JPA
中的@AttributeOverrides
中包含@AttributeOverride
数组; - Class类:
- 上述元素组成的数组:
如果使用其他的类型在编译阶段就会报错,另外基本类型的包装类型也不可以在注解中使用。
在类中一个元素中可以使用多个注解,但是在Java 8 之前同一个注解是不能够重复使用在同一个目标上的;
注解类型之间是没有继承关系的,所以
extends
关键字在注解中是不允许出现的;
3. 注解的处理和获取
注解的获取和获取也可以分为两类:
- 一类是静态处理:这种方式直接操作的是源文件,而不是编译之后的字节码文件,可以使用apt工作完成,但是我从来没用过;
- 一类是动态处理:这就是我们常用的,通过反射的方式读取注解的信息,注意此时注解的
@Retention
必须是RetentionPolicy.RUNTIME
;
如果使用注解时没有对应的处理器,则该注解也没有神马用!所有要明确的是,注解的目的是提供一种标记的手段,具体这种标记的提取还是要靠处理器完成。
提取注解的时候,首先要明确注解应用在什么地方,我们非常熟悉的是Class
类、Field
类和Method
类,通过
其中的getAnnotation(...)
获取对应的注解,但是其实Java
给我们提供了一个公共的接口用来描述这种元素,为java.lang.reflect.AnnotatedElement
,其中的API可以分为几类:
isAnnotationPresent()
:判断是否被相应的注解标识;获取不包括父类的注解:
getAnnotation(Class<T> annotationClass)
:获取指定类型的注解;getAnnotations()
:获取所有的注解;getAnnotationsByType(Class<T> annotationClass)
:Java 8新提出的API
;
- 获取包括父类的注解,与上边的功能是一一对应的:
getDeclaredAnnotation(Class<T> annotationClass)
:getDeclaredAnnotations()
:getDeclaredAnnotationsByType(Class<T> annotationClass)
上面获取注解的API中getAnnotationsByType(Class<T> annotationClass)
和getDeclaredAnnotationsByType(Class<T> annotationClass)
都是Java 8 新提出的,为的是支持新特性在同一个元素上重复的使用相同的注解,这种使用方式在Java 8之前是不允许的,如下例:
public class Test{
@Path("a")
@Path("b")
private String name;
}
此时如果想要获取name字段上的注解只能使用getAnnotationsByType(Class<T> annotationClass)
和getDeclaredAnnotationsByType(Class<T> annotationClass)
这两个API
。
除了在java.lang.reflect.AnnotatedElement
中定义的API外,其实现类中也有很多的其他工具,比如Class类中的isAnnotation()
等。
相关文章: