文章目录
一、什么是注解?
Annotation表示注解。是JDK1.5的新特性。
注解的主要作用:对我们的程序进行标注。通过注解可以给类增加额外的信息。
注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
二、注解
1. 自定义注解的定义
public @interface 注解名称 {
public 属性类型 属性名();
}
注解中的属性可以有默认值,格式为:
数据类型 属性名() default 默认值;
属性类型可以是基本数据类型,String,Class,注解,枚举,以及上述类型的数组。
public @interface MyAnno {
/**
* 空注解
*/
}
public @interface MyAnno2 {
//public 属性类型 属性名() default 默认值;
public String name();
/**
* 注解中的属性可以有默认值,格式为:
*
* 数据类型 属性名() default 默认值;
* @return
*/
public int age() default 0;
public String gender() default "男";
}
public @interface MyAnno3 {
String value();
}
2. 自定义注解的使用
使用自定义注解格式:
@注解名(属性名1=值1, 属性名2=值2)
注解中,有默认值的属性可以不赋值。
没有默认值的属性一定要赋值。
/**
* 使用自定义注解格式
* @注解名(属性名1=值1, 属性名2=值2)
* 注解中,有默认值的属性可以不赋值。
* 没有默认值的属性一定要赋值。
*/
@MyAnno2(name="测试")
public class Test {
@MyAnno3("单个注解且属性为value可以省略value=")
private int a;
@MyAnno
public static void test(){
}
}
注意:
1.注解可以加在类定义上,变量定义上,方法定义上,不能单独存在。
2.一个注解不要在同一个地方加多次。
3.当注解中只有”一个属性”,并且属性名是”value”,使用注解时,可以省略value属性名
3. 常用元注解
元注解是指:修饰注解的注解
注解名 | 说明 |
---|---|
@Target | 指定注解能在哪里使用 |
@Retention | 注解的生命周期,可以理解为保留时间 |
@Documented | 表示注解在生成帮助文档时会保留下来 |
Inherited | 表示该注解会被子类继承 |
元注解总共有4个,常用的就是前两个:@Target和@Retention。
3.1 @Target
作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
可使用的值定义在 ElementType枚举类 中,常用值如下:
TYPE,类,接口
FIELD, 成员变量
METHOD, 成员方法
PARAMETER, 方法参数
CONSTRUCTOR, 构造方法
LOCAL_VARIABLE, 局部变量
/**
* 常用的四个注解:
* 分别是:
* 可以用在类或者接口上
* 可以用在变量上
* 可以用在方法上
* 可以用在构造方法上
*/
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface MyAnno4 {
/**
* @Target(ElementType枚举类) 用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置
* TYPE,类,接口
* FIELD, 成员变量
* METHOD, 成员方法
* PARAMETER, 方法参数
* CONSTRUCTOR, 构造方法
* LOCAL_VARIABLE, 局部变量
*/
}
@MyAnno4
public class Test02 {
@MyAnno4
private int a;
@MyAnno4
public Test02(){
}
@MyAnno4
public static void test(){
}
}
3.2 @Retention
作用:用来标识注解的生命周期。(即该注解会保留到哪个阶段)
可使用的值定义在 RetentionPolicy枚举类 中,常用值如下:
SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在。
CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在。(默认值)
RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段。
3.2.1 RetentionPolicy.SOURCE
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE) //指定生命周期的在源码阶段,编译之后就消失了
public @interface Hello {
}
@Hello
public class test03 {
public static void main(String[] args) {
}
}
这里看到反编译之后在字节码文件中注解已经消失了
3.2.2 RetentionPolicy.CLASS(默认值)
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.CLASS) //不指定就是默认值,编译的时候存在,运行的时候就消失
public @interface Hello {
}
这里看到反编译之后在字节码文件中注解还存在,但是在运行过程中就已经不存在。
例如:
一个Message注解生命周期为默认
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public @interface Message {
public String name();
public int age();
}
@Message(name = "张三",age=18)
public class People {
}
/**
* 解析注解
*/
public class Test {
public static void main(String[] args) {
//注解在谁头上就反射谁
Class<People> clazz = People.class;
//获取注解 getAnnotation(注解名)
Message annotation = clazz.getAnnotation(Message.class);
//通过注解名调用属性
System.out.println(annotation.name());
System.out.println(annotation.age());
}
}
通过反射去获取注解值,这个时候程序已经在运行阶段,如果生命周期默认为CLASS那么,运行阶段注解已经消失了,值为null,所以会报空指针异常。
3.2.1 RetentionPolicy.RUNTIME
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Message {
public String name();
public int age();
}
@Message(name = "张三",age=18)
public class People {
}
/**
* 解析注解
*/
public class Test {
public static void main(String[] args) {
//注解在谁头上就反射谁
Class<People> clazz = People.class;
//获取注解 getAnnotation(注解名)
Message annotation = clazz.getAnnotation(Message.class);
//通过注解名调用属性
System.out.println(annotation.name());
System.out.println(annotation.age());
}
}
这里通过反射解析注解的时候通过注解去获取属性值,这个时候就已经是运行阶段了,注解@Message就需要指定生命周期为RUNTIME,运行阶段就可以通过反射获取到注解值。
三、注解解析
什么是注解解析?
使用Java反射技术,获得注解的属性数据的过程,称为注解解析
方法名 | 说明 |
---|---|
T getAnnotation(Class annotationClass) | 根据注解类型获得对应注解对象 |
Annotation[] getAnnotations() | 获得当前结构上使用的所有注解 |
boolean isAnnotationPresent(Class annotationClass) | 判断当前结构上是否使用了指定的注解,如果使用了则返回true,否则false |
Field,Method,Constructor,Class 等反射相关的类可以调用以上方法
通过反射来解析注解,注解在谁头上就用谁来解析
注解在类上,使用Class来获取。
注解在构造方法上,使用Constructor来获取。
注解在成员方法上,使用Method来获取。
注解在成员变量上,使用Field来获取。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //生命周期作用到运行阶段
public @interface BookMessage {
String name();
double price();
String[] authors();
}
public class Book {
@BookMessage(name="红楼梦",price = 39.9,authors ={"曹雪芹","高鹗"})
public void show(){}
}
import java.lang.reflect.Method;
public class test02 {
public static void main(String[] args) throws Exception {
Class<Book> clazz = Book.class;
Method show = clazz.getDeclaredMethod("show");
BookMessage annotation = show.getAnnotation(BookMessage.class);
System.out.println(annotation.name());
System.out.println(annotation.price());
System.out.println(annotation.authors()[0]);
System.out.println(annotation.authors()[1]);
}
}
四、模拟JUnit自带的@Test注解
1. 需求
模拟JUnit自带的@Test注解, 自动运行带@MyTest注解的方法
2. 分析
1.定义MyTest注解。
2.定义普通类并包含多个方法,在一些方法头上配@MyTest注解。
3.使用反射得到类中的所有Method。
4.遍历得到每个Method,如果Method上有MyTest, 就运行这个方法。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//指定注解只能用在方法上
@Target(ElementType.METHOD)
//指定注解的生命周期RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
public class Student {
@MyTest
public void weakup(){
System.out.println("wake up!");
}
public void eat(){
System.out.println("eat rice!");
}
public void study(){
System.out.println("study!");
}
@MyTest
public void sleep(){
System.out.println("good night!");
}
}
import java.lang.reflect.Method;
/**
* 注解解析
*/
public class Test {
/**
* 使用反射,解析Student类,获取所有加上MyTest的方法,调用并运行
*/
public static void main(String[] args) throws Exception{
//1.获取Class对象
Class<Student> clazz = Student.class;
//2.反射出所有的方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//3.判断方法上是否有注解
if(method.isAnnotationPresent(MyTest.class)){
//4.调用并运行方法(对象)
method.invoke(clazz.getDeclaredConstructor().newInstance());
}
}
}
}