Contents

Java自定义注解

注解格式

//元注解
public @interface 注解名称 {
    属性列表;
}

注解本质上就是一个接口,该接口默认继承Annotation接口。任何注解默认继承Annotation接口

举例:

public @interface Student {
    String name(); // 姓名

    int age() default 18; // 年龄

    String gender() default "男"; // 性别
}
// 该注解就有了三个属性:name,age,gender

注解属性

  1. 属性作用:可以让用户在使用注解时传递参数,让注解的功能更加强大。
  2. 属性格式
    • 格式1:数据类型 属性名();
    • 格式2:数据类型 属性名() default 默认值;
  3. 适用数据类型
    • 八种基本数据类型(int,float,boolean,byte,double,char,long,short)。
    • String类型,Class类型,枚举类型,注解类型。
    • 以上所有类型的一维数组。

使用自定义注解

使用格式:

@注解名(属性名=属性值,属性名=属性值,属性名=属性值…)

解析注解的步骤(本质获取注解属性值):

  1. 获取注解定义的位置的对象 (Class、Method、Field)
  2. 获取指定的注解 getAnnotation(Class)
  3. 调用注解中的抽象方法获取配置的属性值

定义注解

  1. 定义一个注解:Book
    • 包含属性:String value() 书名
    • 包含属性:double price() 价格,默认值为 100
    • 包含属性:String[] authors() 多位作者
  2. 代码实现
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() {
    }
}

使用注意事项:如果属性有默认值,使用注解时可以不用赋值。如果没有则一定要赋值

特殊情况:

  1. 当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。
  2. 如果注解中除了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);

案例:使用反射获取注解的数据

需求说明

  1. 定义注解Book,要求如下:
    • 包含属性:String value() 书名
    • 包含属性:double price() 价格,默认值为 100
    • 包含属性:String[] authors() 多位作者
    • 限制注解使用的位置:类和成员方法上
    • 指定注解的有效范围:RUNTIME
  2. 定义BookStore类,在类和成员方法上使用Book注解
  3. 定义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注解

案例分析

  1. 自定义注解@Test:功能类似@Test注解
    • 添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
    • 编写调用类MyTestMainClass,使用main方法调用目标类,模拟Junit的运行,只要有@Test注释的方法都会运行。
  2. 编写测试类使用注解@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");
    }
}