1 理解注解
Java注解是JDK5时引入的特性,目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率。注解不支持继承,因此不能用关键字extends继承某个@interface,但是在编译之后,编译器会自动继承java.lang.annotation.Annotation接口。
实际上Java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {//@Test注解修饰方法A,在运行该方法时,测试框架会自动识别该方法并单独调用,@Test实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。@Testpublic static void A(){System.out.println("Test.....");}//一个方法上可以拥有多个不同的注解//对于@Deprecated和@SuppressWarnings(“uncheck”),则是Java本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有@Deprecated注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如@SuppressWarnings(“uncheck”),这就是注解的最简单@Deprecated@SuppressWarnings("uncheck")public static void B(){}}
2 基本语法
2.1 声明注解
//声明Test注解@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Test {}1、使用了@interface声明了Test注解2、使用@Target注解传入ElementType.METHOD参数来标明@Test只能用于方法上3、@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时4、从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.class文件5、对于@Target和@Retention是由Java提供的元注解,所谓元注解就是标记其他注解的注解。
2.2 注解分类
2.2.1 JDK基本注解
(1)@Override
重写
(2)@Deprecated
已过时
(3)@SuppressWarning
压制编辑器警告
(4)@SafeVarargs
用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全的问题。
2.2.2 JDK元注解
作用:元注解用于修饰其他的注解
(1)@Retention
定义注解的保留策略。
@Retention(RetentionPolicy.SOURCE)//注解仅存在于源码中,在class字节码文件中不包含@Retention(RetentionPolicy.CLASS)//默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,@Retention(RetentionPolicy.RUNTIME)//注解会在class字节码文件中存在,在运行时可以通过反射获取到
(2)@Target
指定被修饰的Annotation可以放置的位置(被修饰的目标)。
@Target(ElementType.TYPE) //接口、类@Target(ElementType.FIELD) //属性@Target(ElementType.METHOD) //方法@Target(ElementType.PARAMETER) //方法参数@Target(ElementType.CONSTRUCTOR) //构造函数@Target(ElementType.LOCAL_VARIABLE) //局部变量@Target(ElementType.ANNOTATION_TYPE) //注解@Target(ElementType.PACKAGE) //包
注:可以指定多个位置。
例如:@Target({ElementType.METHOD, ElementType.TYPE})
此注解可以在方法和类上面使用。
(3)@Inherited
指定被修饰的Annotation将具有继承性。
(4)@Documented
指定被修饰的该Annotation可以被javadoc工具提取成文档。
2.2.3 自定义注解
注解分类(根据Annotation是否包含成员变量,可以把Annotation分为两类):
(1)标记Annotation
没有成员变量的Annotation。仅利用自身存在与否提供信息。
(2)元数据Annotation
包含成员变量的Annotation。可以接受(提供)更多的元数据。
自定义注解过程:
- 使用@interface关键字, 其定义过程与定义接口非常类似。
- Annotation的成员变量在Annotation定义中是以无参的方法形式声明的,其方法名和返回值类型定义了该成员变量的名称和类型。
- 可以是以default关键位成员变量设置默认值。
- 名字为value属性,赋值时可以省略属性名。
样例:获取类和方法上的注解值
package com.ssm.yuan.p1;public enum TranscationModel {Read, Write, ReadWrite}
package com.ssm.yuan.p1;import java.lang.annotation.*;/**** MyAnnotation3注解可以用在方法上* 注解运行期也保留* 可继承*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface MyAnnotation3 {TranscationModel[] models() default TranscationModel.ReadWrite;}
package com.ssm.yuan.p1;import java.lang.annotation.*;/**** MyAnnotation2注解可以用在方法上* 注解运行期也保留* 不可继承*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyAnnotation2 {TranscationModel model() default TranscationModel.ReadWrite;}
package com.ssm.yuan.p1;import java.lang.annotation.*;/**** MyAnnotation1注解可以用在类、接口、属性、方法上* 注解运行期也保留* 不可继承*/@Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyAnnotation1 {String name();}
package com.ssm.yuan.p1;import org.junit.Test;/*** 获取类和方法上的注解名*/public class Demo1Test {@Testpublic void list() throws Exception {// 获取类上的注解MyAnnotation1 annotation1 = Demo1.class.getAnnotation(MyAnnotation1.class);System.out.println(annotation1.name());//abc// 获取方法上的注解MyAnnotation2 myAnnotation2 = Demo1.class.getMethod("list").getAnnotation(MyAnnotation2.class);System.out.println(myAnnotation2.model());//Read}@Testpublic void edit() throws Exception {MyAnnotation3 myAnnotation3 = Demo1.class.getMethod("edit").getAnnotation(MyAnnotation3.class);for (TranscationModel model : myAnnotation3.models()) {System.out.println(model);//Read,Write}}}
package com.ssm.yuan.p1;/**** 获取类与方法上的注解值*/@MyAnnotation1(name = "abc")public class Demo1 {@MyAnnotation1(name = "xyz")private Integer age;@MyAnnotation2(model = TranscationModel.Read)public void list() {System.out.println("list");}@MyAnnotation3(models = {TranscationModel.Read, TranscationModel.Write})public void edit() {System.out.println("edit");}}
3 注解与反射机制
Java所有注解都继承了Annotation接口,也就是说 Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。
同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
| Class | 类的Class对象定义 |
|---|---|
| Constructor | 代表类的构造器定义 |
| Filed | 代表类的成员变量定义 |
| Method | 代表类的方法定义 |
| Package | 代表类的包定义 |
AnnotatedElement中相关的API方法,以上5个类都实现以下的方法:
| 返回值 | 方法名称 | 说明 |
|---|---|---|
| getAnnotation(ClassannotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 | |
| Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的 |
| boolean | isAnnotationPresent(Class<?extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
| Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组 |
import java.lang.annotation.Annotation;import java.util.Arrays;@DocumentAclass A{ }//继承了A类@DocumentBpublic class DocumentDemo extends A{public static void main(String... args){Class<?> clazz = DocumentDemo.class;//根据指定注解类型获取该注解DocumentA documentA=clazz.getAnnotation(DocumentA.class);System.out.println("A:"+documentA);//获取该元素上的所有注解,包含从父类继承Annotation[] an= clazz.getAnnotations();System.out.println("an:"+ Arrays.toString(an));//获取该元素上的所有注解,但不包含继承!Annotation[] an2=clazz.getDeclaredAnnotations();System.out.println("an2:"+ Arrays.toString(an2));//判断注解DocumentA是否在该元素上boolean b=clazz.isAnnotationPresent(DocumentA.class);System.out.println("b:"+b);/*** 执行结果:A:@com.zejian.annotationdemo.DocumentA()an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()]an2:@com.zejian.annotationdemo.DocumentB()b:true*/}}
4 注解处理器
如果没有处理注解的工具,那么注解也不会有太大的作用。对于不同的注解有不同的注解处理器。虽然注解处理器的编写千变万化,但是也有处理标准。
- 针对运行时注解会采用反射机制处理。
针对编译时注解会采用 AbstractProcessor 来处理。
4.1 运行时注解处理器
定义注解:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Documentedpublic @interface Get {String value() default "";}
使用注解:
public class AnnotationTest {@Get(value = "http://ip.taobao.com/59.108.54.37")public String getIpMsg() {return "";}@Get(value = "http://ip.taobao.com/")public String getIp() {return "";}}
获取注解值:
public class AnnotationProcessor {public static void main(String[] args) {Method[] methods = AnnotationTest.class.getDeclaredMethods();for (Method m : methods) {Get get = m.getAnnotation(Get.class);System.out.println(get.value());}}}
输出:
http://ip.taobao.com/59.108.54.37http://ip.taobao.com/
4.2 编译时注解处理器
定义注解:
@Retention(RetentionPolicy.CLASS)@Target(ElementType.FIELD)public @interface BindView {int value() default 1;}
编写注解处理器: ```java public class ClassProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();for (Element ele : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {if (ele.getKind() == ElementKind.FIELD) {messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + ele.toString());}}return true;
}
@Override public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override public Set
getSupportedAnnotationTypes() { Set<String> annotations = new LinkedHashSet<>();annotations.add(BindView.class.getCanonicalName());return annotations;
}
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
} }
- init 被注解处理工具调用,并输入 processingEnvironment 参数。processingEnvironment 提供了很多工具类,如 Elements、Types、Filer 和 Messenger 等。- process 注解处理的主函数,这里处理扫描、评估和处理注解的代码,以及生产 Java 文件。- getSupportedAnnotationTypes 指明注解处理器是处理哪些注解的。- getSupportedSourceVersion 指明使用的 Java 版本,通常返回 SourceVersion.latestSupported。在Java7以后,也可以用注解的形式代替getSupportedAnnotationTypes和getSupportedSourceVersion。即@SupportedAnnotationTypes和@SupportedSourceVersion注解。```java@SupportedSourceVersion(SourceVersion.RELEASE_8)@SupportedAnnotationTypes("com.caoshen.annotations.BindView")public class ClassProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {...}@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);}}
并在 processor 的 build.gradle 配置依赖:
dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation project(':annotations')}
5 Java8中注解增强
5.1 新增元注解@Repeatable
在同一个位置重复相同的注解。
之前这样表示同一个位置使用相同注解:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface FilterPath {String [] value();}//使用@FilterPath({"/update","/add"})public class A { }
现在可以这样表示:
@FilterPath("/web/update")@FilterPath("/web/add")public class A {}
5.2 新增的两种ElementType
在Java8中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE ,在Java8前注解只能标注在一个声明(如字段、类、方法)上,Java8后,新增的TYPE_PARAMETER可以用于标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)。
