Java-Annotation(注解)
共 23040字,需浏览 47分钟
·
2021-06-12 02:19
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | 随风的海子
来源 | urlify.cn/euq6Br
一、注解的概念和作用
从JDK1.5开始,Java增加了对元数据(MetaData)的支持即Annotation(注解)其实就是代码标记,在不改变原有的代码逻辑情况下通过注解可以让代码在编译期或者运行期添加一下额外的处理,当然这些处理也需要开发者自己来定义。
二、5个基本的注解
下面来看Java提供的5个基本的注解(其中好几个我们都很熟悉了)
1. @Override
@Override 用于限定重写父类的方法,它可以强制子类必须覆盖父类的方法否则会引发编译错误
代码代码示例
public class Fruit {
public void info() {
System.out.println("this is fruit info");
}
}
class Apple extends Fruit {
/**
* 这里虽然不加@Override注解也不会报任何编译错误和运行时错误
* 但是如果info方法目的就是为了重写父类的info方法,这里不加@Override
* 如果info写错了也不会报任何编译异常,一旦写错了程序员也不会发现
*/
@Override
public void info() {
System.out.println("this is apple info");
}
}
2. @Deprecated
用于标识某个程序元素(类,方法)等已经过时了,当其他程序使用已经过时的类和方法时编译器将会给与警告,但是并不影响程序的运行。
3. @SuppressWarnings
表示被该注解修饰的程序元素以及该程序元素中的所有子元素取消显示指定的编译警告
@SuppressWarnings(value = "unchecked")
public class SuppressWarningsTest {
public static void main(String[] args) {
List<String> myList = new ArrayList<>();
}
}
4. @Safevarargs
用于抑制堆污染警告
堆污染:当把一个不带泛型的对象赋给一个带泛型的变量时就会发生堆污染警告
5. @FunctionalInterface
用于修饰函数式接口的,当一个接口只有一个抽象方法时可以给该接口添加注解表示这时一个函数式接口
三、作用于注解的注解
JDK除了在java.lang包下提供了5个基本的注解之外,还在java.lang.annotation包下提供了5个Meta Annotation(元注解),其中4个用于修饰其他的注解,还有一个
@Repeatable
为Java8新增的允许重复注解
1. @Retention
只能用于对其他注解的定义,指定被修饰的注解能够保留多长时间,它包含一个
RetentionPolicy
类型的成员变量,在使用该注解时必须给这个变量赋值,下面是赋值说明
属性值 | 含义描述 |
---|---|
RetentionPolicy.SOURCE | 表示该注解只保留在源码级别,编译时会被编译器直接丢弃 |
RetentionPolicy.CLASS | 表示该注解会被编译到class文件中,但是当Java程序运行时会被丢弃,即JVM读取不到该注解的信息(一般用于在编译器通过改注解来添加额外的文件) |
RetentionPolicy.RUNTIME | 表示该注解会被编译到class文件中,当Java程序运行时JVM可以通过反射读取到该注解的信息 |
2. @target
只能用于对其他注解的定义,指定被修饰的注解能用于哪些程序单元,它包含一个
ElementType[]
成员变量该变量的值如下
属性值 | 含义描述 |
---|---|
ElementType.TYPE | 表示该注解只能修饰类、接口、或者枚举、以及注解类型 |
ElementType.FIELD | 表示该注解只能修饰成员变量 |
ElementType.METHOD | 表示该注解只能修饰方法 |
ElementType.PARAMETER | 表示该注解可以修饰参数 |
ElementType.CONSTRUCTOR | 表示该注解只能修饰构造器 |
ElementType.LOCAL_VARIABLE | 表示该注解只能修饰局部变量 |
ElementType.ANNOTATION_TYPE | 表示该注解只能修饰注解 |
ElementType.TYPE_PARAMETER | 表示该注解可以用于任何声明类型的地方(1.8新增的) |
ElementType.TYPE_USE | 表示该注解可以用于任意使用类型的地方(1.8新增的) |
ElementType.PACKAGE | 表示该注解只能修饰包定义 |
3. @Documented
用于指定改注解修饰的注解类将会被javadoc工具提取成文档
4. @Inherited
指定被它修饰的注解将会有继承性,即如果某个类被@A注解修饰了,同事@A注解被@Inherited注解修饰,则这个类的子类也将默认被@A修饰
5. @Repeatable
java8新增的允重复注解(传统的java语法不允许在同一段代码使用一样的两个注解,如果强行使用只能使用一个注解容器来承载,@Repeatable就是为了解决这个问题的,其实其思想也是容器思想)
四、自定义注解
1. 定义注解
自定义注解的格式
public @interface 注解名{成员变量类型 成员变量名() default 默认值;}
其实和接口的定义非常类似
示例:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface DemoAnnotation {
String name() default "name";
}
2. 注解解析使用
在使用了自定义注解修饰了类、方法、成员变量等之后这些注解不会自己生效,需要程序员自己提供相应的注解处理工具来处理对应的注解信息Java使用了Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口,同时Java1.5在java.lang.reflect包下新增了AnnotatedElement接口用于表示程序中可以接受注解的程序元素,这个接口的主要实现类有如下几个:
实现类名 | 说明 |
---|---|
Class | 类定义 |
Filed | 类的成员变量定义 |
Method | 类的方法定义 |
Package | 类的包定义 |
Constructor | 构造器定义 |
瞅瞅源码:
那么使用方式就很舒服了:我们只需要通过反射获取到对象的Class 便可以通过Class获取对象上的注解信息了,而且通过Class也能获取到Method和Constructor这样也能获取到对应的注解,那关键问题就是AnnotateElement接口到底让子类实现了哪些接口了?
方法名 | 作用说明 |
---|---|
getAnnotation(Class<A>annotationClass) | 获取该程序元素上指定类型的注解,如果不存在返回null |
getDeclaredAnnotation(Class<A>annotationClass) | Java8新增方法,尝试获取直接修饰该程序元素指定类型的注解(不包括继承的),如果不存在返回null |
getAnnotations() | 返回该程序元素上的所有注解 |
getDeclaredAnnotations() | 返回直接修饰该程序元素的所有注解(不包括继承的) |
isAnnotationPresent(Class<?extends Annotation>annotationClass) | 判断该程序元素上是否存在指定类型的注解 |
getAnnotationsByType(Class<A> annotationClass) | 与上面的getAnnotation()方法类似,只是针对java8的重复注解功能,所以需要使用这个方法获取修饰该程序元素指定类型的多个注解 |
getDeclaredAnnotationsByType(Class<A> annotationClass) | 与上面的getDeclaredAnnotation方法类似,只是针对java8的重复注解功能,所以需要使用这个方法获取修饰该程序元素指定类型的多个注解 |
代码示例:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAnnotation {
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotationParent {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DemoAnnotation {
String name() default "name";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodParentAnnotation {
}
@ClassAnnotationParent
public class AnnotationParentTest {
@MethodParentAnnotation
public void infoParent(){
System.out.println("this is annotation parent test info");
}
}
@ClassAnnotation
public class AnnotationTest extends AnnotationParentTest {
@DemoAnnotation
public void info() {
System.out.println("this is Annotation test info ");
}
}
public class HaiziTest {
public static void main(String[] args) throws NoSuchMethodException {
AnnotationTest test = new AnnotationTest();
Class<? extends AnnotationTest> testClass = test.getClass();
//获取类对象上的
Annotation[] annotations = testClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation = " + annotation);
}
Annotation[] declaredAnnotations = testClass.getDeclaredAnnotations();
for (Annotation declaredAnnotation : declaredAnnotations) {
System.out.println("declaredAnnotation = " + declaredAnnotation);
}
ClassAnnotation demoAnnotation = testClass.getAnnotation(ClassAnnotation.class);
if (demoAnnotation!=null){
System.out.println("demoAnnotation = " + demoAnnotation);
}
ClassAnnotation declaredDemoAnnotation = testClass.getDeclaredAnnotation(ClassAnnotation.class);
if (declaredDemoAnnotation!=null){
System.out.println("declaredDemoAnnotation = " + declaredDemoAnnotation);
}
//获取方法上的
System.out.println(" ===========方法上的========== " );
Method info = testClass.getMethod("info");
DemoAnnotation annotation = info.getAnnotation(DemoAnnotation.class);
System.out.println("annotation = " + annotation);
DemoAnnotation declaredAnnotation = info.getDeclaredAnnotation(DemoAnnotation.class);
System.out.println("declaredAnnotation = " + declaredAnnotation);
Annotation[] infoAnnotations = info.getAnnotations();
for (Annotation infoAnnotation : infoAnnotations) {
System.out.println("infoAnnotation = " + infoAnnotation);
}
Annotation[] infoDeclaredAnnotations = info.getDeclaredAnnotations();
for (Annotation infoDeclaredAnnotation : infoDeclaredAnnotations) {
System.out.println("infoDeclaredAnnotation = " + infoDeclaredAnnotation);
}
}
}
五、Java8新增的注解
1. @Repeatable
重复注解:即同一程序元素需要使用多个一样的注解的时候使用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatableTags.class)
public @interface RepeatableTag {
String name() default "haizi";
int age() default 18;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RepeatableTags {
RepeatableTag[] value();
}
//这是传统写法
//@RepeatableTags({
// @RepeatableTag(name = "hazi",age = 18),
// @RepeatableTag(name = "luoyu",age = 17)
//})
//java8提供的写法
@RepeatableTag(name = "haizi",age = 19)
@RepeatableTag(name = "luoyu",age = 18)
public class RepeatableTest {
}
public static void main(String[] args) {
Class<RepeatableTest> repeatableTestClass = RepeatableTest.class;
RepeatableTag[] declaredAnnotationsByType = repeatableTestClass.getDeclaredAnnotationsByType(RepeatableTag.class);
//java8的写法
for (RepeatableTag repeatableTag : declaredAnnotationsByType) {
System.out.println("repeatableTag = " + repeatableTag);
}
//传统写法
RepeatableTags declaredAnnotation = repeatableTestClass.getDeclaredAnnotation(RepeatableTags.class);
for (RepeatableTag repeatableTag : declaredAnnotation.value()) {
System.out.println("repeatableTag = " + repeatableTag);
}
/**
* 这里其实是获取不的 返回null,原因是两个@RepeatableTag注解其实以及被默认组装成
* @RepeatableTags注解,即@RepeatableTag以及被@RepeatableTags代替了
*/
RepeatableTag repeatableTag = repeatableTestClass.getDeclaredAnnotation(RepeatableTag.class);
System.out.println("repeatableTag = " + repeatableTag);
}
2. java8新增的Type Annotation
java8为ElementType枚举增加了TYPE_PARAMETER、和、TYPE_USE两个枚举值
TYPE_USE:被称为类型注解可以让被修饰的注解放在任何用到类型的地方
创建对象(用new 关键字创建时)
类型转换
使用implement实现接口
使用throws声明抛出异常
......
说明:这样做是为了让编译器执行更严格的编译检查,保证代码的健壮性
六、编译时处理Annotation
APT(Annotation Processing Tool)是一种注解处理工具,它对源代码文件进行检测,并找出源文件所含的Annotation信息,然后针对不通的Annotation进行额外的处理
Java提供的javac.exe工具指定有一个-processor
选项,该选项可以指定一个Annotation处理器
Annotation处理器:每个自定义的Annotation处理去都需要实现javax.annotation.processing包下的Processor接口,不过实现该接口就必须实现接口中的所有方法,因此通常会采用继承AbstractProcessor的方式来实现Annotation处理器,一个Annotation处理器可以处理多种Annotation类型。
1. 案例
自定义APT根据源文件中的注解来生成额外的文件,下面将定义3种注解类型分别用于修饰持久化类,标识属性普通成员属性(案例来自《疯狂java讲义第3版》)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Persistent {
String table();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Property {
String column();
String type();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Id {
String column();
String type();
String generator();
}
@Persistent(table = "person")
public class Person {
@Id(column = "person_id", type = "Integer", generator = "identity")
private Integer id;
@Property(column = "person_name", type = "String")
private String name;
@Property(column = "person_age", type = "Integer")
private Integer age;
public Person() {
}
public Person(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
//指定编译最低版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"Persistent", "Id", "Property"})
public class MyProcess extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
PrintStream ps = null;
try {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Persistent.class);
for (Element element : elements) {
//获取正在处理的类名
Name simpleName = element.getSimpleName();
Persistent persistent = element.getAnnotation(Persistent.class);
ps = new PrintStream(new FileOutputStream(simpleName + ".hbm.xml"));
ps.println("<?xml version=\"1.0\"?>");
ps.println(" \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"");
ps.println(" \"http://www.hibernate.org/dtd/ hibernate-mapping-3.0.dtd\">");
ps.println("<hibernate-mapping>");
ps.println(" <class name=\"" + element);
ps.println("\" table=\"" + persistent.table() + "\">");
List<? extends Element> list = element.getEnclosedElements();
for (Element element1 : list) {
if (element1.getKind() == ElementKind.FIELD) {
Id id = element1.getAnnotation(Id.class);
if (id != null) {
ps.println(" <id name=\""
+ element1.getSimpleName()
+ "\" column=\"" + id.column()
+ "\" type=\"" + id.type()
+ "\">");
}
}
Property property = element1.getAnnotation(Property.class);
if (property != null) {
ps.println(" <property name=\""
+ element1.getSimpleName()
+ "\" column=\"" + property.column()
+ "\" type=\"" + property.type()
+ "\">");
}
}
}
ps.println(" </class>");
ps.println("</hibernate-mapping>");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return true ;
}
}