注解,这个经常在开发中使用到的东西,它的使用语法是怎么样的?如何去自定义一个注解呢?
什么是注解
我们在日常开发中,比如 java 中的@Override
,在 springboot 中用到的@SpringBootApplication
等一系列标注在类或者方法上的注解。我们添加上注解后会有对应的事件处理,比如我们的@Override
注解标明这个方法是重写了父类或者接口的方法,当参数不一致、返回类型不一致等不符合重写的要求时,编译器会报错。类似的@SpringBootApplication
也是标明这个项目的一个 springboot 项目,默认会启动一个 tomcat 容器等。
注解是从 jdk5 开始引入的新特性。
注解的语法
1
| public @interface FirstAnnotation {}
|
通过@interface
即可声明一个注解。
内置的注解
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中
作用在代码的注解是
@Override
- 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated
- 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings
- 指示编译器去忽略注解中声明的警告。
元注解
我们通过上面的语法定义了一个注解,但还需要其他的注解一同作用。在 jdk1.5中定义了 4 个标准注解,它们用来提供对其他 annotation 类型做说明。分别是:
@Retention
- 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented
- 标记这些注解是否包含在用户文档中。
@Target
- 标记这个注解应该是哪种 Java 成员。
@Inherited
- 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
从 Java 7 开始,额外添加了 3 个注解:
@SafeVarargs
- Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
@FunctionalInterface
- Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable
- Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
@Retention
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
public enum RetentionPolicy { SOURCE, CLASS, RUNTIME }
|
这个注解只有一个变量,类型是一个枚举。可以通过这个变量来表明这个注解是保存在源码中(source),还是编入 class 文件中(class),还是在运行时通过反射访问(runtime)。
@Target
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE }
|
@Documented
1 2 3 4 5
| @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
|
它没有任何变量,添加这个注解可以让我们执行 JavaDoc 文档打包时注解会被保存进 doc 文档,反之将在打包时丢弃。
@Inherited
1 2 3 4 5
| @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
|
这个注解是什么意思呢?我们通过一个例子来说明一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Inherited @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DocumentFirst { }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DocumentSecond { }
@DocumentFirst class A{ }
class B extends A{ }
@DocumentSecond class C{ }
class D extends C{ }
public class DocumentDemo {
public static void main(String[] args){ A instanceA=new B(); System.out.println("实例 B 拥有的注解:"+Arrays.toString(instanceA.getClass().getAnnotations()));
C instanceC = new D();
System.out.println("实例 D 拥有的注解:"+Arrays.toString(instanceC.getClass().getAnnotations())); }
}
|
可以看出来,添加了@Inherited
元注解的注解,这个类被继承后的子类也可以拥有这个注解。也就是说这个注解的作用范围是父类和子类。而没有添加这个元注解的注解,作用范围只有被添加的类。
@Repeatable
添加这个注解后,注解可以在同一个声明上使用多次,来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public @interface DocumentA { String[] names; }
@Repeatable public @interface DocumentB { String[] names; }
@DocumentA(names="AAA")
class A { }
@DocumentB(names="AAA") @DocumentB(names="BBB") class B { }
|
@FunctionalInterface
在 jdk1.8中添加了 lambda 表达式,也就是函数式编程。虽然说添加了lambda,但也并不是所有的东西都可以无条件的去使用。需要在接口上添加@FunctionalInterface
注解,并且这个接口里面只能有一个抽象方法。
通过 aop 来进行拦截
通过上面我们可以进行一个自定义的注解,但是我们即便把他声明在方法上,它其实还是没有作用的,如果需要这个注解起到我们想要的作用还需要进行自定义的处理。比如我们使用 aop 来进行这个处理
首先定义一个注解
1 2 3 4 5
| @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface HelloAnnotation { }
|
然后在方法上添加这个注解
1 2 3 4
| @HelloAnnotation public void sayHello(){ System.out.println("i am working"); }
|
最后定义拦截器
1 2 3 4 5 6 7 8 9
| @Around("@annotation(HelloAnnotation)") public Object around(ProceedingJoinPoint proceedingJoinPoint, HelloAnnotation helloAnnotation) { System.out.println("start"); proceedingJoinPoint.proceed(); System.out.println("finish"); }
|
这样我们就可以在调用sayHello()
这个方法后,执行这个拦截器逻辑。
参考