1 理解注解

Java注解是JDK5时引入的特性,目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率。注解不支持继承,因此不能用关键字extends继承某个@interface,但是在编译之后,编译器会自动继承java.lang.annotation.Annotation接口。
实际上Java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,下面的例子是常见的注解:

  1. public class AnnotationDemo {
  2. //@Test注解修饰方法A,在运行该方法时,测试框架会自动识别该方法并单独调用,@Test实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。
  3. @Test
  4. public static void A(){
  5. System.out.println("Test.....");
  6. }
  7. //一个方法上可以拥有多个不同的注解
  8. //对于@Deprecated和@SuppressWarnings(“uncheck”),则是Java本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有@Deprecated注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如@SuppressWarnings(“uncheck”),这就是注解的最简单
  9. @Deprecated
  10. @SuppressWarnings("uncheck")
  11. public static void B(){
  12. }
  13. }

2 基本语法

2.1 声明注解

  1. //声明Test注解
  2. @Target(ElementType.METHOD)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface Test {
  5. }
  6. 1、使用了@interface声明了Test注解
  7. 2、使用@Target注解传入ElementType.METHOD参数来标明@Test只能用于方法上
  8. 3@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时
  9. 4、从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.class文件
  10. 5、对于@Target@Retention是由Java提供的元注解,所谓元注解就是标记其他注解的注解。

2.2 注解分类

2.2.1 JDK基本注解

(1)@Override
重写
(2)@Deprecated
已过时
(3)@SuppressWarning
压制编辑器警告
(4)@SafeVarargs
用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全的问题。

2.2.2 JDK元注解

作用:元注解用于修饰其他的注解
(1)@Retention
定义注解的保留策略。

  1. @Retention(RetentionPolicy.SOURCE)//注解仅存在于源码中,在class字节码文件中不包含
  2. @Retention(RetentionPolicy.CLASS)//默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
  3. @Retention(RetentionPolicy.RUNTIME)//注解会在class字节码文件中存在,在运行时可以通过反射获取到

(2)@Target
指定被修饰的Annotation可以放置的位置(被修饰的目标)。

  1. @Target(ElementType.TYPE) //接口、类
  2. @Target(ElementType.FIELD) //属性
  3. @Target(ElementType.METHOD) //方法
  4. @Target(ElementType.PARAMETER) //方法参数
  5. @Target(ElementType.CONSTRUCTOR) //构造函数
  6. @Target(ElementType.LOCAL_VARIABLE) //局部变量
  7. @Target(ElementType.ANNOTATION_TYPE) //注解
  8. @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属性,赋值时可以省略属性名。

样例:获取类和方法上的注解值

  1. package com.ssm.yuan.p1;
  2. public enum TranscationModel {
  3. Read, Write, ReadWrite
  4. }
  1. package com.ssm.yuan.p1;
  2. import java.lang.annotation.*;
  3. /**
  4. *
  5. * MyAnnotation3注解可以用在方法上
  6. * 注解运行期也保留
  7. * 可继承
  8. */
  9. @Target(ElementType.METHOD)
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Inherited
  12. @Documented
  13. public @interface MyAnnotation3 {
  14. TranscationModel[] models() default TranscationModel.ReadWrite;
  15. }
  1. package com.ssm.yuan.p1;
  2. import java.lang.annotation.*;
  3. /**
  4. *
  5. * MyAnnotation2注解可以用在方法上
  6. * 注解运行期也保留
  7. * 不可继承
  8. */
  9. @Target(ElementType.METHOD)
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. public @interface MyAnnotation2 {
  13. TranscationModel model() default TranscationModel.ReadWrite;
  14. }
  1. package com.ssm.yuan.p1;
  2. import java.lang.annotation.*;
  3. /**
  4. *
  5. * MyAnnotation1注解可以用在类、接口、属性、方法上
  6. * 注解运行期也保留
  7. * 不可继承
  8. */
  9. @Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. public @interface MyAnnotation1 {
  13. String name();
  14. }
  1. package com.ssm.yuan.p1;
  2. import org.junit.Test;
  3. /**
  4. * 获取类和方法上的注解名
  5. */
  6. public class Demo1Test {
  7. @Test
  8. public void list() throws Exception {
  9. // 获取类上的注解
  10. MyAnnotation1 annotation1 = Demo1.class.getAnnotation(MyAnnotation1.class);
  11. System.out.println(annotation1.name());//abc
  12. // 获取方法上的注解
  13. MyAnnotation2 myAnnotation2 = Demo1.class.getMethod("list").getAnnotation(MyAnnotation2.class);
  14. System.out.println(myAnnotation2.model());//Read
  15. }
  16. @Test
  17. public void edit() throws Exception {
  18. MyAnnotation3 myAnnotation3 = Demo1.class.getMethod("edit").getAnnotation(MyAnnotation3.class);
  19. for (TranscationModel model : myAnnotation3.models()) {
  20. System.out.println(model);//Read,Write
  21. }
  22. }
  23. }
  1. package com.ssm.yuan.p1;
  2. /**
  3. *
  4. * 获取类与方法上的注解值
  5. */
  6. @MyAnnotation1(name = "abc")
  7. public class Demo1 {
  8. @MyAnnotation1(name = "xyz")
  9. private Integer age;
  10. @MyAnnotation2(model = TranscationModel.Read)
  11. public void list() {
  12. System.out.println("list");
  13. }
  14. @MyAnnotation3(models = {TranscationModel.Read, TranscationModel.Write})
  15. public void edit() {
  16. System.out.println("edit");
  17. }
  18. }

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的数组
  1. import java.lang.annotation.Annotation;
  2. import java.util.Arrays;
  3. @DocumentA
  4. class A{ }
  5. //继承了A类
  6. @DocumentB
  7. public class DocumentDemo extends A{
  8. public static void main(String... args){
  9. Class<?> clazz = DocumentDemo.class;
  10. //根据指定注解类型获取该注解
  11. DocumentA documentA=clazz.getAnnotation(DocumentA.class);
  12. System.out.println("A:"+documentA);
  13. //获取该元素上的所有注解,包含从父类继承
  14. Annotation[] an= clazz.getAnnotations();
  15. System.out.println("an:"+ Arrays.toString(an));
  16. //获取该元素上的所有注解,但不包含继承!
  17. Annotation[] an2=clazz.getDeclaredAnnotations();
  18. System.out.println("an2:"+ Arrays.toString(an2));
  19. //判断注解DocumentA是否在该元素上
  20. boolean b=clazz.isAnnotationPresent(DocumentA.class);
  21. System.out.println("b:"+b);
  22. /**
  23. * 执行结果:
  24. A:@com.zejian.annotationdemo.DocumentA()
  25. an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()]
  26. an2:@com.zejian.annotationdemo.DocumentB()
  27. b:true
  28. */
  29. }
  30. }

4 注解处理器

如果没有处理注解的工具,那么注解也不会有太大的作用。对于不同的注解有不同的注解处理器。虽然注解处理器的编写千变万化,但是也有处理标准。

  • 针对运行时注解会采用反射机制处理。
  • 针对编译时注解会采用 AbstractProcessor 来处理。

    4.1 运行时注解处理器

    定义注解:

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target(ElementType.METHOD)
    3. @Documented
    4. public @interface Get {
    5. String value() default "";
    6. }

    使用注解:

    1. public class AnnotationTest {
    2. @Get(value = "http://ip.taobao.com/59.108.54.37")
    3. public String getIpMsg() {
    4. return "";
    5. }
    6. @Get(value = "http://ip.taobao.com/")
    7. public String getIp() {
    8. return "";
    9. }
    10. }

    获取注解值:

    1. public class AnnotationProcessor {
    2. public static void main(String[] args) {
    3. Method[] methods = AnnotationTest.class.getDeclaredMethods();
    4. for (Method m : methods) {
    5. Get get = m.getAnnotation(Get.class);
    6. System.out.println(get.value());
    7. }
    8. }
    9. }

    输出:

    1. http://ip.taobao.com/59.108.54.37
    2. http://ip.taobao.com/

    4.2 编译时注解处理器

    定义注解:

    1. @Retention(RetentionPolicy.CLASS)
    2. @Target(ElementType.FIELD)
    3. public @interface BindView {
    4. int value() default 1;
    5. }

    编写注解处理器: ```java public class ClassProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

    1. Messager messager = processingEnv.getMessager();
    2. for (Element ele : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
    3. if (ele.getKind() == ElementKind.FIELD) {
    4. messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + ele.toString());
    5. }
    6. }
    7. return true;

    }

    @Override public synchronized void init(ProcessingEnvironment processingEnvironment) {

    1. super.init(processingEnvironment);

    }

    @Override public Set getSupportedAnnotationTypes() {

    1. Set<String> annotations = new LinkedHashSet<>();
    2. annotations.add(BindView.class.getCanonicalName());
    3. return annotations;

    }

    @Override public SourceVersion getSupportedSourceVersion() {

    1. return SourceVersion.latestSupported();

    } }

  1. - init 被注解处理工具调用,并输入 processingEnvironment 参数。processingEnvironment 提供了很多工具类,如 ElementsTypesFiler Messenger 等。
  2. - process 注解处理的主函数,这里处理扫描、评估和处理注解的代码,以及生产 Java 文件。
  3. - getSupportedAnnotationTypes 指明注解处理器是处理哪些注解的。
  4. - getSupportedSourceVersion 指明使用的 Java 版本,通常返回 SourceVersion.latestSupported
  5. Java7以后,也可以用注解的形式代替getSupportedAnnotationTypesgetSupportedSourceVersion。即@SupportedAnnotationTypes@SupportedSourceVersion注解。
  6. ```java
  7. @SupportedSourceVersion(SourceVersion.RELEASE_8)
  8. @SupportedAnnotationTypes("com.caoshen.annotations.BindView")
  9. public class ClassProcessor extends AbstractProcessor {
  10. @Override
  11. public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
  12. ...
  13. }
  14. @Override
  15. public synchronized void init(ProcessingEnvironment processingEnvironment) {
  16. super.init(processingEnvironment);
  17. }
  18. }

并在 processor 的 build.gradle 配置依赖:

  1. dependencies {
  2. implementation fileTree(dir: 'libs', include: ['*.jar'])
  3. implementation project(':annotations')
  4. }

5 Java8中注解增强

5.1 新增元注解@Repeatable

在同一个位置重复相同的注解。
之前这样表示同一个位置使用相同注解:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface FilterPath {
  4. String [] value();
  5. }
  6. //使用
  7. @FilterPath({"/update","/add"})
  8. public class A { }

现在可以这样表示:

  1. @FilterPath("/web/update")
  2. @FilterPath("/web/add")
  3. public class A {}

5.2 新增的两种ElementType

在Java8中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE ,在Java8前注解只能标注在一个声明(如字段、类、方法)上,Java8后,新增的TYPE_PARAMETER可以用于标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)。