【Java 注解】笔记整理

版权声明:转载请标明出处「OneDeveloper」 https://blog.csdn.net/OneDeveloper/article/details/80090165

阅读文章:
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作为默认值是一种常用的做法。如果没有指定默认值,而且在使用该注解的时候也没有指定值,那么编译器会报错,并且不能通过编译。
这里写图片描述

@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 放自定义的注解:
这里写图片描述

然后 processor 放对应的注解处理器:
这里写图片描述
且在其 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
这里写图片描述

这里写图片描述

注意,如果在 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 ),如:
这里写图片描述
而如果直接使非 IDE,如文本编辑器等,在敲上述代码的时候,是不会想上面那样报错的,但是当你使用命令行去编译 .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) 一样使用注解处理器

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

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页