欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Java 注解】笔记整理

程序员文章站 2024-02-16 09:26:52
...

阅读文章:
1、秒懂,Java 注解 (Annotation)你可以这样学
2、深入浅出Java注解
3、Java 技术之注解 Annotation
4、AbstractProcessor注解处理器


注:下文 1 - 3 主要摘抄自 深入浅出Java注解


1、注解的定义

Annotation(注解)就是Java提供了一种源程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。从Java 1.5开始支持支持。(元数据是描述数据的数据)

Annotation是被动的元数据,永远不会有主动行为。

特别说明:
- 注解仅仅是元数据,和业务逻辑无关,所以当你查看注解类时,发现里面没有任何逻辑处理;
- javadoc中的@author、@version、@param、@return、@deprecated、@hide、@throws、@exception、@see是标记,并不是注解;


2、注解的作用

  • 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息;
    如格式检查:告诉编译器信息,比如被 @Override 标记的方法如果不是父类的某个方法,IDE会报错

  • 编译阶段时的处理: 软件工具可以利用注解信息来生成代码、Html文档或者做其它相应处理;
    如减少配置:运行时动态处理,得到注解信息,实现代替配置文件的功能;

  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取。
    如减少重复工作:比如第三方框架xUtils,通过注解 @ViewInject减少对 findViewById 的调用,类似的还有(JUnit、ActiveAndroid等);


3、自定义注解

特征:

  • 注解类会被 @interface 标记;
  • 注解类的顶部会被 @Documented@Retention@Target@Inherited 这四个注解标记(@Documented、@Inherited 可选,@Retention、@Target 必须要有);
  • 在 Java 1.8 新加了 @Repeatable

(1)@Target:
作用:用于描述注解的使用范围,即被描述的注解可以用在什么地方。

取值:

1CONSTRUCTOR:构造器;
2、FIELD:实例;
3、LOCAL_VARIABLE:局部变量;
4METHOD:方法;
5、PACKAGE:包;
6、PARAMETER:参数;
7TYPE:类、接口(包括注解类型) 或enum声明。

源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

从源码中可以看到,valueElementType 数组类型,而 ElementType 实际上是一个枚举,因此 @Target 的取值可以取单个或者多个,只要是在一个数组内即可。例如:

@Target({ElementType.METHOD,ElementType.FIELD})

另外,如果注解中只有一个元素时,一般默认命名为 value,且在赋值的时候可以省略名字。下面为完整的书写:

@Target(value = {ElementType.METHOD,ElementType.FIELD})

(2)@Retention:
作用:表示需要在什么级别保存该注解信息,用于描述注解的生命周期,即被描述的注解在什么范围内有效;

取值:

1、SOURCE:在源文件中有效,即源文件(.java)保留,在编译器进行编译时它将被丢弃忽视(编译之后生成 .class是不会保留的)
2CLASS:在class文件中有效,注解只被保留到编译进行的时候,即class保留,它并不会被加载到 JVM 中
3、RUNTIME:在运行时有效,即运行时保留,会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

(3)@Documented:
作用:用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc 此类的工具文档化。

取值:它属于标记注解,没有成员

(4)@Inherited:
作用:用于描述某个被标注的类型是可被继承的。如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。

取值:它属于标记注解,没有成员

(5)@Repeatable:
详见:秒懂,Java 注解 (Annotation)你可以这样学

自定义注解格式:

 元注解
  public @interface 注解名{
      定义体;
  }

注解参数可支持的数据类型:

1、所有基本数据类型(int,float,boolean,byte,double,char,long,short);
2、String类型;
3、Class类型;
4enum类型;
5、Annotation类型;
6、以上所有类型的数组。
特别说明:

1、注解类中的方法只能用public或者默认这两个访问权修饰,不写public就是默认

2、如果注解类中只有一个成员,最好把方法名设置为”value”,因为一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

3、注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。如果没有指定默认值,而且在使用该注解的时候也没有指定值,那么编译器会报错,并且不能通过编译。
【Java 注解】笔记整理

@Target(value = {ElementType.METHOD,ElementType.FIELD})
public @interface AnnotationTest {
    String author();
    Priority priority() default Priority.MEDIUM;//枚举类型
    Status status() default Status.NOT_STARTED;//枚举类型
}

enum Priority {LOW, MEDIUM, HIGH}
enum Status {STARTED, NOT_STARTED}

4、注解的解析

在没看那两篇文章的时候,虽然大概知道注解的作用,但是会有一个疑惑,我使用了这个注解,但是它是怎么起作用的呢?明明在定义这个注解的时候我就定义了其中的成员元素,其余的啥都没做了呀!

对于注解的解析,会根据 Retention 的不同而有不同的解析过程。

(1) 对于 @Retention(RetentionPolicy.CLASS)

对于 RetentionPolicy.CLASS 的注解,具体的实现就比较复杂了,除了要实现一个继承自 AbstractProcessor 的自定义 Processor 类,因为在编译的时候会扫描全部继承自 AbstractProcessor 的自定义类,执行里面的 process 方法。

另外,还要额外的设置一些东西,大致如下:

1. 在 main 目录下新建 resources 资源文件夹; 
2. 在 resources文件夹下建立 META-INF/services 目录文件夹; 
3. 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4. 在 javax.annotation.processing.Processor 文件写入注解处理器的全称(即自定义的 Processor 类的全称 ),包括包路径;

当然上面这四步也可以用谷歌的一个库来自动实现,首先需要导入

'com.google.auto.service:auto-service:1.0-rc4'

然后在自定义的 Processor 类前用 @AutoService(Processor.class) 修饰。

下面,我还是来说一下在 Android Studio 里面实现的步骤。

假设我要定义一个注解 @AnnotationTest,具体实现如下:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface AnnotationTest {
}

本身没有啥意义,就是用来演示用的。
然后需要定义对应的注解处理器 AnnotationTestProcessor ,源码在下面贴出来,先看下去。

一般来说,注解处理器只是在编译的时候用到,正式的时候并不需要打包到 apk 中,所以会分别建两个 Java Library,名字自拟,一个放自定义的注解,另一个放注解处理器。
注意:在 AS 中新建 Java Library 的步骤就是 "File -> New -> New Module...",然后在弹出的面板里面选择 Java Library 就可以了。

这里 processor-lib 放自定义的注解:
【Java 注解】笔记整理

然后 processor 放对应的注解处理器:
【Java 注解】笔记整理
且在其 build.gradle 文件里面有如下依赖:

//前面说过的第三方库
implementation 'com.google.auto.service:auto-service:1.0-rc4'
//依赖含有 AnnotationTest 注解的 library
implementation project(':processor-lib')
//一个第三方库,用于生成 Java 文件的
implementation 'com.squareup:javapoet:1.10.0'

这里自定义的注解处理器,为了演示,在其 process 方法里面实现生成一个 HelloWorld 的 java 文件,所以有在上面依赖 javapoet。具体代码如下:

//使用 auto-service
@AutoService(Processor.class)
public class AnnotationTestProcessor extends AbstractProcessor {

    private Filer filer;


    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new LinkedHashSet<>();
        set.add(AnnotationTest.class.getCanonicalName());
        return set;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            // 创建test方法
            MethodSpec test = MethodSpec.methodBuilder("test")//
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//
                    .returns(void.class)//
                    .addStatement("$T.out.println($S)", System.class, "自动创建的")//
                    .build();

            // 创建HelloWorld类
            TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//
                    .addModifiers(Modifier.PUBLIC)//
                    .addMethod(test)//
                    .build();

            String packageName = processingEnv.getElementUtils().getPackageOf(annotatedElement).getQualifiedName().toString();
            try {
                JavaFile javaFile = JavaFile.builder(packageName, helloWorld)//
                        .addFileComment(" This codes are generated automatically. Do not modify!")//
                        .build();
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return false;
    }
}

对于实现的各个方法,不讲其具体含义,谷歌一大把。

最后就是使用了,如果是在一个 module 里面使用,则需要添加如下依赖:

implementation project(':processor-lib')
annotationProcessor project(':processor')

其中,因为 processor library 只需要在编译的时候用到,所以需要使用到 annotationProcessor(用来替代 android-apt 的,具体看这里)。
然后就是具体的使用了,假设我在 MainActivity 方法里面定义一个方法,并使用 AnnotationTest 注解修饰,如下:

@AnnotationTest
public void test() {
    Log.d("TAG", "This is a test");
}

此时,build 一下该 module,就可以看到有生成在自定义注解处理器中定义的 HelloWorld.java
【Java 注解】笔记整理

【Java 注解】笔记整理

注意,如果在 module 里面没有使用过 AnnotationTest 注解(而 test() 方法不一定需要被调用),是不会生成 HelloWorld.java的。在生成了 HelloWorld.java之后,就可以直接使用了。如下:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test();
    }
    @AnnotationTest
    public void test() {
        Log.d("TAG", "This is a test");
        //直接使用
        HelloWorld.test();
    }
}

(2) 对于 @Retention(RetentionPolicy.SOURCE)

虽然看似在使用 RetentionPolicy.SOURCE 类型注解的时候,是 Java集成开发环境(IDE)工具软件在进行提示(本人用的是 IntelliJ IDEA ),如:
【Java 注解】笔记整理
而如果直接使非 IDE,如文本编辑器等,在敲上述代码的时候,是不会想上面那样报错的,但是当你使用命令行去编译 .java 文件的时候,也会报错(同时是上面那段代码):
【Java 注解】笔记整理

这就表明,当注解类型为 RetentionPolicy.SOURCE 时,就像前面说的,本质上是作用于 .java 文件编译成 .class 文件阶段的( javac 是收录于 JDK 中的 Java 语言编译器),IDE 能够在你敲代码的时候就进行提示,只不过是通过技术手段(不深究)实现了提前提示,而不用等到去编译 .java 文件的时候再提示,因为 IDE 在编译 .java 的时候本质上也是借助 javac

但是在编译成 .class 文件后,@Override 就会被抛弃,而不会保留到 .class 文件中。但是在编译的时候,也能像 RetentionPolicy.CLASS 那样被自定义注解处理器扫描到,并作出相应的逻辑。
这里一定要注意,只是注解不会被保留到 .class 的文件中,但是注解起到的作用,还是可以注解自定义注解处理器实现的。
因为我把 (1) 中的 RetentionPolicy.CLASS 改成的 RetentionPolicy.SOURCE 也同样能生成 HelloWorld.java

(3) 对于 @Retention(RetentionPolicy.RUNTIME)

对于 RetentionPolicy.RUNTIME 的注解,会在运行时保留,被加载进入到 JVM 中,因此在程序运行时可以获取到,主要是利用 反射机制 在代码运行过程中动态地执行一些操作,具体的示例可以参考 Java 技术之注解 Annotation

当然也可以像 (2) 一样使用注解处理器

但是就性能上而言,反射比注解处理器差点,因为注解处理器是在编译的时候就处理好了,但是使用反射会方便一点,不需要定义额外的注解处理器。

相关标签: Java 注解