java 注解自定义的方法及注意事项

974 阅读6分钟

注解是什么

jdk5 引入的一种机制,可以理解注解就是被当做一个标记

注解可以使用在java 中的类、方法、变量、参数等位置。

几个基本的注解:

  1. @Override(重写)
  2. @Deprecated(废弃)
  3. @SuppressWarnings(忽略警告)
  4. @Retention(保留的意思,标识此注解是只在代码中、编入class 文件中、运行时反射访问)
  5. @Documented(是否在用户文档里)
  6. @Target(作用于哪些成员)
  7. @Inherited(标记继承关系)

其中4 ~ 7 是元注解。

什么是元注解

如上所述,java 中有四个元注解,但是有人可能会比较懵,这个“元注解”的“元”是什么意思,“元注解”又是什么意思。其实元的意思可以理解为“起始”,“解释”的意思。而对于注解来说,元注解就是用来修饰其他注解的注解。所以我们记住并理解这句话就好。

现在分别介绍几个基本元注解各自的作用

  1. @Retention 用于描述一个注解的生命周期,有三个取值。分别为:SOURCE/CLASS/RUNTIME。作用分别为: (1)在源文件中生效,也就是编译器可识别,可用。

(2)在编译好的class 文件中生效,即编译器和虚拟机都可以使用。

(3)在运行时生效,那么编译器、虚拟机、程序运行时都可以使用。

  1. @Document 这个注解修饰的会被javaDoc 提出出来,形成一个文档。所以如果你想让你的自定义的注解可以被识别,那么就要加这个注解。如果不加这个注解,那么javaDoc 就不会识别。

  2. @Target 这个元注解的意思是定义并且指定被修饰的注解类可以运用在哪些地方。比如说类或者接口、字段属性、方法、参数、构造方法、局部变量等等。

  3. @Inherited 单词本身的意思是继承。所以这个注解就和继承有一定的关系。即如果一个父类被@Inherited 注解修饰过,但是如果它的子注解类没有被任何其他注解应用,那么这个子类就可以继承父注解类的注解。所以实现了一个“继承”的关系。

怎么使用元注解

元注解可以在我们自定义注解的时候使用,举例如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
}
  1. 定义 Annotation 时,@interface 是必须使用的。
  2. @Target 的作用是用来指定Annotation 的生效的位置。如接口、字段、类、包等。
  3. @Rentention 的作用是指定注解何时生效。
  4. @Documented 可有可无,决定Annotation 是否出现在javadoc 中
  5. 标注Annotation 类型,被标注的Annotation 具有继承性。即其他注解可以继承这个注解。

注解的介绍

当我们点进去某个注解,查看里面的源码的时候,会发现里面的内容非常少。所以很显然,注解定义的功能并不是在那里实现。我们都知道,注解其实就是个“标记”,本身并没有任何功能。

这个标记可以作用在代码中的很多位置。参考上一篇元注解@Target 的使用。

同时可以在编译、加载、运行时的不同时间段被读取,执行相应的功能。参考元注解@Retention 的使用。这里再介绍一下:

  1. RetentionPolicy = SOURCE 源码阶段保留,编译后就被丢弃
  2. RetentionPolicy = CLASS 编译时保留,编译后的class 文件中存在,在jvm 运行时会被丢弃。(默认)
  3. RetentionPolicy = RUNTIME 运行时保留,在编译后的class 文件中存在,在jvm 运行时也保留,可以被反射调用。

使用注解,可以在不改变原有代码逻辑的情况,进行一些其他的操作,从而进行逻辑补充。

如何自定义注解

对于我们自定义注解时,一般生命周期都是RUNTIME 类型的。对于设置为SOURCE 和CALSS 的注解,分别需要编译器和虚拟机的配合。因为两种生命周期分别在编译和加载期。所以说,对于我们自定义注解时,一般都是设置为RUNTIME 类型的。

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.ANNOTATION_TYPE)
public @interface MyAnnotation {
    /**
     * 这个在注解中不是方法,是参数
     */
    String value() default "默认值";

    public int number();
}

注解里面的内容不是方法,是“注解类型参数”。

注解中参数使用的格式:class(注解名) + 参数名(如上面例子中的value) + 参数。

如果在注解中只有一个参数,那我们不需要加上参数名,注解会进行自动匹配。

定义元素的时候要注意,其访问修饰符必须是public,不写默认是public。

元素类型只能是基本数据类型、String、class、枚举、注解类型。或者是上述各类型的一维数组。

在定义元素类型的时候,后面的括号里面是没有“参数”的,那个只是一个特殊的语法。后面可以接上default 默认值。如果没有设置默认值,那么在后续使用的时候必须给该元素赋值。

注解类型的元素在定义的时候和普通的java 语法相比似乎有一些不一样,因为它们有可以赋值的属性的特征,又有方法的特征(定义的时候需要在末尾加上一对括号)。在定义好之后,使用的时候,操作元素类型,就像是在操作属性;解析的时候,操作元素类型,就像在操作方法

注解参数的使用方法

我们都知道,自定义注解中参数的定义有两个方面需要注意的。

第一个就是我们在自定义注解的时候,按照java 语言的既有思维看,上文中boolean required() default true; 可能会被当做一个方法,其实这个是“注解类型参数”。我们所说的注解中自定义的参数就是这个required。

第二个就是要确定如何使用这个参数。参数可以有默认值,如果没有默认值,那么在使用这个注解的时候,必须要为此参数赋值。而使用这个参数的方法就是利用java 的反射机制。通过反射机制,我们可以在拦截器或者aop 中使用为此参数设置的值进行相应的逻辑操作。

其他需要注意的

如果一个注解的@Target 的值为Element.PACKAGE,这个注解时不能直接在某个类的package 代码上面配置,而是应该配置在package-info.java中。

自定义注解怎样生效

知道自定义注解的语法之后,我们需要写一部分逻辑来为这个注解添加一些自定义的功能。而通过上文我们知道,注解只是一个标记,没有有任何业务逻辑写在自定义的注解的结构中。

同时通过上文我们知道,我们自定义注解大都是使用@Retention(RetentionPolicy.RUNTIME)修饰,这样在JVM运行时,才能检测到注解,并进行一系列特殊操作。

因此,我们需要明确目标:运行期,探究和使用编译期的内容(编译期配置的注解),要用到Java中的灵魂技术——反射!

于是乎一切都显得比较自然了。通过注解+反射,注解作为一个标记,实现不修改代码增加业务逻辑的功能。

(这篇讲解注解的自定义方法及注意事项,下一篇会举几个例子进行细致的讲解。)