Java自定义注解
Contents
注解格式
//元注解
public @interface 注解名称 {
属性列表;
}
注解本质上就是一个接口,该接口默认继承Annotation接口。任何注解默认继承Annotation接口
举例:
public @interface Student {
String name(); // 姓名
int age() default 18; // 年龄
String gender() default "男"; // 性别
}
// 该注解就有了三个属性:name,age,gender
注解属性
- 属性作用:可以让用户在使用注解时传递参数,让注解的功能更加强大。
- 属性格式
- 格式1:数据类型 属性名();
- 格式2:数据类型 属性名() default 默认值;
- 适用数据类型
- 八种基本数据类型(int,float,boolean,byte,double,char,long,short)。
- String类型,Class类型,枚举类型,注解类型。
- 以上所有类型的一维数组。
使用自定义注解
使用格式:
@注解名(属性名=属性值,属性名=属性值,属性名=属性值…)
解析注解的步骤(本质获取注解属性值):
- 获取注解定义的位置的对象 (Class、Method、Field)
- 获取指定的注解
getAnnotation(Class) - 调用注解中的抽象方法获取配置的属性值
定义注解
- 定义一个注解:Book
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 代码实现
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
使用注解
/*
使用注解
注意:如果属性有默认值,使用注解时可以不用赋值。如果没有则一定要赋值
*/
public class DemoUseAnnotation {
@Book(value = "Java编程思想", price = 100, authors = {"Bruce Eckel", "Bruce Eckel2"})
public void use() {
}
}
使用注意事项:如果属性有默认值,使用注解时可以不用赋值。如果没有则一定要赋值
特殊情况:
- 当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。
- 如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value属性名不能省略。
元注解
默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方。如果要限制注解的使用位置怎么办?元注解。
- @Target
- @Retention
@Target作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
- 可选的参数值在枚举类ElemenetType中包括:
- TYPE: 用在类,接口上
- FIELD:用在成员变量上
- METHOD: 用在方法上
- PARAMETER:用在参数上
- CONSTRUCTOR:用在构造方法上
- LOCAL_VARIABLE:用在局部变量上
@Retention作用:定义该注解的生命周期(有效范围)
- 可选的参数值在枚举类型RetentionPolicy中包括:
- SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
- CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
- RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。
注解解析
通过Java技术获取注解数据的过程则称为注解解析。
与注解解析相关的接口
Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个:
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)- 判断当前对象是否有指定的注解,有则返回true,否则返回false。
<T extends Annotation> T getAnnotation(Class<T> annotationClass)- 获得当前对象上指定的注解对象。
<T extends Annotation> T getAnnotation(Class<T> annotationClass)- 获得当前对象及其从父类上继承的所有的注解对象。
Annotation[] getAnnotations()- 获得当前对象上所有的注解对象,不包括父类的。
获取注解数据的原理
注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。
- 如注解作用在方法上,就通过方法(Method)对象得到它的注解。
// 得到方法对象
Method method=clazz.getDeclaredMethod("方法名");
// 根据注解名得到方法上的注解对象
Book book=method.getAnnotation(Book.class);
- 如注解作用在类上,就通过Class对象得到它的注解。
// 获得Class对象
Class c=类名.class;
// 根据注解的Class获得使用在类上的注解对象
Book book=c.getAnnotation(Book.class);
案例:使用反射获取注解的数据
需求说明
- 定义注解Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:RUNTIME
- 定义BookStore类,在类和成员方法上使用Book注解
- 定义DemoResolutionAnn测试类获取Book注解上的数据
代码实现
注解Book
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 作者
String[] authors();
}
BookStore类
@Book(value = "红楼梦", authors = "曹雪芹", price = 998)
public class BookStore {
}
ResolutionAnnotation类
/*
解析注解:取出BookStore类上的注解数据
*/
public class DemoResolutionAnn {
public static void main(String[] args) {
//1.获取类的字节码对象
Class<BookStore> clz = BookStore.class;
//2.判断类是否使用注解
boolean isAnn = clz.isAnnotationPresent(Book.class);
//3.如果使用注解:则解析注解的属性
if (isAnn) {
//获取字节码对象中的注解对象
Book book = clz.getAnnotation(Book.class);
//获取Book注解的属性
String value = book.value();
System.out.println("value = " + value);
double price = book.price();
String[] authors = book.authors();
System.out.println("price = " + price);
System.out.println("authors = " + Arrays.toString(authors));
}
}
}
// 打印
// value = 红楼梦
// price = 998.0
// authors = [曹雪芹]
综合案例:模拟JUnit的@Test注解
案例分析
- 自定义注解@Test:功能类似@Test注解
- 添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 编写调用类MyTestMainClass,使用main方法调用目标类,模拟Junit的运行,只要有@Test注释的方法都会运行。
- 编写测试类使用注解@Test,然后给方法(测试方法)加上 @Test注解
自定义注解@Test
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
//定义注解执行器类
public class MyTestMainClass {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
//获取类的字节码对象
Class<DemoUseCustomTest> clz = DemoUseCustomTest.class;
//获取类的对象
DemoUseCustomTest obj = clz.newInstance();
//获取调用类的方法
Method[] methods = clz.getMethods();
//遍历获取方法的数组,反射执行方法
for (Method m : methods) {
//如果是Test注解,则执行。否则不执行
if (m.isAnnotationPresent(Test.class)) {
m.invoke(obj);
}
}
}
}
// test01 test03 会打印
@Test注解使用
public class DemoUseCustomTest {
@Test
public void test01() {
System.out.println("test01");
}
public void test02() {
System.out.println("test02");
}
@Test
public void test03() {
System.out.println("test03");
}
}