注解是啥
注解本身不提供作用,注解只能是被看作元数据,它不包含任何业务逻辑。注解更像是一个标签,一个声明,表面被注释的这个地方,将具有某种特定的逻辑。
注解(Annotation),也叫元数据,是一种代码级别的说明。是Java 的JDK1.5版本开始引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、属性、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
常见注解的种类
常见的注解有三大类:JDK的,自定义的,第三方的(比如框架)
注解三板斧
定义、使用、读取
-
定义:包括名字,能用到哪些地方,有效期,是否可以被继承
-
使用:定义好之后在允许的地方使用标注即可
-
读取:让注解发挥作用,给注解注入灵魂
光有前两步,没什么用,如最熟悉的@Override注解,为什么能验证重写是否有效,怎么不是验证重载?spring的@Autowired为什么是注入作用,而不是输出一句话?显然,他们在程序中做了实现,使得其注解具有各自的作用,也具有了意义,而赋予灵魂的一步就是读取
第一步:定义注解
定义一个注解很简单,和定义一个类很相似。
前置知识
首先是元注解,Java给我们提供了四个元注解,用于我们自定义的注解上:
@Documented| @Retention | @Target | @Inherited
分别解释下
@Documented
代表着此注解会被javadoc工具提取成文档
@Retention:
代表该注解的有效期
SOURCE
表示编译期,如@Override,只做编译时的提示,不会写入字节码中。
CLASS
表示类加载期,会保存在class文件中,但在运行class文件被丢弃,也是默认值
RUNTIME
表示运行期,也是最常用的,可以在代码运行时进行反射执行相关的操作
@Target:
表示这个注解可以放在哪
TYPE
:接口、类、枚举、注解
FIELD
:字段、枚举的常量
METHOD
:方法
PARAMETER
:参数
CONSTRUCTOR
:构造函数
LOCAL_VARIABLE
:局部变量
ANNOTATION_TYPE
:注解
PACKAGE
:包
@Inherited:
表示子类可以继承该类的注解
定义一个注解,分为以下几步:
- 注解的名字(声明注解)
- 注解的修饰目标
- 注解的生命周期
- 注解的属性。
声明注解
注解的声明形式如下:
public @interface 注解名字 {
注解属性
}
例如我们声明注解类型MyAnnotation.java,如下所示:
public @interface MyAnnotation {
}
注解的修饰目标
注解可以用于不同的目标,例如接口、类、构造方法、方法、属性、类型等;
声明注解时,可以使用JDK中已经定义好的元注解@Target
声明注解修饰的目标;
@Target
中使用枚举ElementType表示修饰目标,有如下几种修饰目标:
表示这个注解可以放在哪
TYPE
:接口、类、枚举、注解
FIELD
:字段、枚举的常量
METHOD
:方法
PARAMETER
:参数
CONSTRUCTOR
:构造函数
LOCAL_VARIABLE
:局部变量
ANNOTATION_TYPE
:注解
PACKAGE
:包
来看下@Target
的源码:
package java.lang.annotation;
@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();
}
在自定义注解中使用元注解:@Target
自定义注解@MyAnnotation
使用@Target
,指定修饰目标为TYPE和METHOD,即接口、类、枚举、注解、方法上可以使用注解MyAnnotation
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
}
注解的生命周期
元注解@Retention
,用来定义注解的声明周期;
代表该注解的有效期
SOURCE
表示编译期,如@Override,只做编译时的提示,不会写入字节码中。
CLASS
表示类加载期,会保存在class文件中,但在运行class文件被丢弃,也是默认值
RUNTIME
表示运行期,也是最常用的,可以在代码运行时进行反射执行相关的操作
来看下@Retention
的源码:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
在自定义注解中元注解:@Retention
指注解@MyAnnotation
的生命周期为运行时(我们在一个Test类中使用@MyAnnotation,编译后看Test.class文件,里面没有任何关于注解@MyAnnotation
的影子)
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
注解的属性
注解的属性看起来像个方法,其实是属性,属性类型包括所有基本类型、 String、Class、enum、 Annotation、以上类型的数组形式,注解元素声明形式如下:
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 默认全是public的,所以public可以忽略
String message() default "MyAnnotation";
boolean onload() default false;
}
注解中的属性在使用的时候可以指定值,也可以在声明的时候赋默认值;
第二步:使用注解
使用注解非常简单,不管是JDK中内置的已经定义好的注解还是自定义的注解,只要使用 @注解名称(属性值列表)
的形式,均可以使用;
例如:
public class Test {
@MyAnnotation(message = "测试", onload = true)
public void test1() {
System.out.println("测试注解");
}
}
第三步:读取注解
读取注解一般都是通过反射获取到某个类,或者某个方法上的注解,然后得到注解中属性的值,去做一些判断。
可以看下这个例子:
package com.wlw.annotion;
import org.yaml.snakeyaml.events.Event;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @author glen.wang
* @date 2022/9/5
*/
public class Test {
@MyAnnotation(message = "测试", onload = true)
public void test1() {
System.out.println("测试注解");
}
public static void main(String[] args) {
//获得测试类
Class clazz = Test.class;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
if (myAnnotation != null) {
System.out.println(myAnnotation.message());
System.out.println(myAnnotation.onload());
}
}
}
}
=====
测试
true
但是在实际开发中,我们都是用spring体系开发,所以我们可以写个切面类,切入点就是这个注解,获取到使用 了这个注解的方法或者类,然后根据注解信息做一些处理。【spring 的AOP】