注解的产生背景
以前,xml以低耦合的方式得到了广大开发者的青睐,xml在当时基本上能完成框架中的所有配置。但是随着项目越来越庞大,xml的配置也越来越复杂,维护性也随之降低,维护成本增高。于是就产生了一种标记式的高耦合的配置方式——注解。
注解的本质
注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,由java编译器来解析,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。本质就是一个继承了 Annotation 接口的接口。
注解的解析方式
有两种方式:
- 编译期直接扫描:编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理,只适用于那些编译器已经熟知的注解类。
- 运行时反射
典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。
元注解
- @Target:注解的作用目标
- @Retention:注解的生命周期
- @Documented:注解是否应当被包含在 JavaDoc 文档中
- @Inherited:是否允许子类继承该注解
@Target:
用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的、修饰类的、还是用来修饰字段属性的?
@Target 的定义如下:
我们可以通过以下的方式来为这个 value 传值:
@Target(value = {ElementType.FIELD})
被 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:
- ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
- ElementType.FIELD:允许作用在属性字段上
- ElementType.METHOD:允许作用在方法上
- ElementType.PARAMETER:允许作用在方法参数上
- ElementType.CONSTRUCTOR:允许作用在构造器上
- ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
- ElementType.ANNOTATION_TYPE:允许作用在注解上
- ElementType.PACKAGE:允许作用在包上
@Retention:
用于指明当前注解的生命周期。
它的基本定义如下:
同样的,它也有一个 value 属性:
@Retention(value = RetentionPolicy.RUNTIME
这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:
- RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
- RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
- RetentionPolicy.RUNTIME:永久保存,可以反射获取
@Retention注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。
@Documented:
当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。
@Inherited:
具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
Java内置三大注解:
- @Deprecated 意思是“废弃的,过时的”
- @Override 意思是“重写、覆盖”
- @SuppressWarnings 意思是“压缩警告”
@Override
它的定义如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。
所以它就是一种典型的标记式注解,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
@Deprecated
它的基本定义如下:
依然是一种标记式注解,永久存在,可以修饰所有的类型,作用是标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。
但是,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。
@SuppressWarnings
主要用来压制 java 的警告,它的基本定义如下:
它有一个 value 属性需要你主动的传值,这个 value 代表的就是需要被压制的警告类型。例如:
public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}
这么一段代码,程序启动时编译器会报一个警告。
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。
@SuppressWarning(value = "deprecated") public static void main(String[] args) { Date date = new Date(2018, 7, 11); }
这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。
自定义注解
package com.test.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) public @interface MyAnnotation { }
package com.test.Annotation; @MyAnnotation public class AnnotationTest { public static void main(String[] args) { if (AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = (MyAnnotation) AnnotationTest.class.getAnnotation(MyAnnotation.class); System.out.println(annotation); //结果:@com.test.Annotation.MyAnnotation() } } }
为注解添加属性
1.添加属性
语法:类型 属性名();
package com.test.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) public @interface MyAnnotation { String str(); }
package com.test.Annotation; @MyAnnotation(str="abc") public class AnnotationTest { public static void main(String[] args) { if (AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = (MyAnnotation) AnnotationTest.class.getAnnotation(MyAnnotation.class); System.out.println(annotation.str()); //结果:abc } } }
2.为属性指定缺省值(默认值)
语法:类型 属性名() default 默认值;
package com.test.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) public @interface MyAnnotation { String str() default "aaa"; }
package com.test.Annotation; @MyAnnotation public class AnnotationTest { public static void main(String[] args) { if (AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = (MyAnnotation) AnnotationTest.class.getAnnotation(MyAnnotation.class); System.out.println(annotation.str());//结果:aaa } } }
3.value属性
如果一个注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略掉“value=”部分。
例如:@SuppressWarnings("deprecation")
package com.test.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) public @interface MyAnnotation { String str() default "aaa"; String value(); }
package com.test.Annotation; @MyAnnotation("bbb") public class AnnotationTest { public static void main(String[] args) { if (AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = (MyAnnotation) AnnotationTest.class.getAnnotation(MyAnnotation.class); System.out.println(annotation.str() + " " + annotation.value()); } //结果:aaa bbb } }
4.添加高级属性
(1)数组类型的属性
- 增加数组类型的属性:int[] array() default {1,2,4};
- 应用数组类型的属性:@MyAnnotation(array={2,4,5})
- 如果数组属性只有一个值,这时候属性值部分可以省略大括号,如:@MyAnnotation(array=2),这就表示数组属性只有一个值,值为2
(2)枚举类型的属性
- 增加枚举类型的属性:EumTrafficLamp lamp() default EumTrafficLamp.Red;
- 应用枚举类型的属性:@MyAnnotation(lamp=EumTrafficLamp.Green)
(3)注解类型的属性
- 为注解添加一个注解类型的属性,并指定注解属性的缺省值:MetaAnnotation meta() default @MetaAnnotation("uvw");
注解综合示例
EumTrafficLamp:
package com.test.Annotation; public enum EumTrafficLamp { Red, Yellow, Black, White, }
MetaAnnotation:
package com.test.Annotation; public @interface MetaAnnotation { String value(); }
MyAnnotation1:
package com.test.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) public @interface MyAnnotation1 { String color() default "Green"; String value(); int[] array() default { 1, 2, 3, 4 }; EumTrafficLamp lamp() default EumTrafficLamp.Black; MetaAnnotation meta() default @MetaAnnotation("uvw"); }
MyAnnotationTest:
package com.test.Annotation; @MyAnnotation1(color = "Red", value = "abc", array = { 5, 6, 7,8 }, lamp = EumTrafficLamp.White, meta = @MetaAnnotation("xyz") ) public class MyAnnotationTest { public static void main(String[] args) { if (MyAnnotationTest.class.isAnnotationPresent(MyAnnotation1.class)) { MyAnnotation1 annotation = MyAnnotationTest.class.getAnnotation(MyAnnotation1.class); System.out.println(annotation.color()); System.out.println(annotation.value()); for (int num : annotation.array()) { System.out.print(num + " "); } System.out.println(); System.out.println(annotation.lamp()); System.out.println(annotation.meta().value()); MetaAnnotation meta = annotation.meta(); System.out.println(meta); } } } //输出结果: //Red //abc //5 6 7 8 //White //xyz //@com.test.Annotation.MetaAnnotation(value=xyz)