JAVA自定义注解和提取注解信息

本文深入讲解Java注解的定义、使用方法及应用场景,包括如何创建自定义注解、提取注解信息,以及通过示例展示注解在单元测试和事件监听器绑定中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一节:定义注解

      定义新的Annotation类型使用@interface关键字(在原有interface关键字前增加@符号)。定义一个新的Annotation类型与定义一个接口很像,例如:

public @interface Test{
}

定义完该Annotation后,就可以在程序中使用该Annotation。使用Annotation,非常类似于public、final这样的修饰符,通常,会把Annotation另放一行,并且放在所有修饰符之前。例如:

@Test
public class MyClass{
....
}

根据注解是否包含成员变量,可以把注解分为如下两类:

  • 标记注解:没有成员变量的Annotation被称为标记。这种Annotation仅用自身的存在与否来为我们提供信息,例如@override等。

  • 元数据注解:包含成员变量的Annotation。因为它们可以接受更多的元数据,因此被称为元数据Annotation。 成员以无参数的方法的形式被声明,其方法名和返回值定义了该成员变量的名字和类型。

 成员变量

Annotation只有成员变量,没有方法。Annotation的成员变量在Annotation定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。例如:

public @interface MyTag{
    string name();
    int age();
}

示例中定义了2个成员变量,这2个成员变量以方法的形式来定义。

一旦在Annotation里定义了成员变量后,使用该Annotation时就应该为该Annotation的成员变量指定值。例如:

public class Test{
    @MyTag(name="红薯",age=30)
    public void info(){
    ......
    }
}

也可以在定义Annotation的成员变量时,为其指定默认值,指定成员变量默认值使用default关键字。示例:

public @interface MyTag{
    string name() default "我兰";
    int age() default 18;
}

如果Annotation的成员变量已经指定了默认值,使用该Annotation时可以不为这些成员变量指定值,而是直接使用默认值。例如:

public class Test{
    @MyTag
    public void info(){
    ......
    }
}

 

如果注解只有一个成员变量,则建议取名为value,在使用时可用忽略成员名和赋值符=

注解的语法与定义形式

(1)以@interface关键字定义

(2)注解需要标明注解的生命周期,注解的修饰目标等信息,这些信息是通过元注解实现。上面的语法不容易理解,下面通过例子来说明一下,这个例子就是Target注解的源码,

源码分析如下:第一:元注解@Retention,成员value的值为RetentionPolicy.RUNTIME。第二:元注解@Target,成员value是个数组,用{}形式赋值,值为ElementType.ANNOTATION_TYPE第三:成员名称为value,类型为ElementType[]另外,需要注意一下,如果成员名称是value,在赋值过程中可以简写。如果成员类型为数组,但是只赋值一个元素,则也可以简写。如上面的简写形式为:@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)

 

 (3)注解中可以包含枚举

package com.annotation.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitColor {

    enum Color{RED,YELLOW,WHITE}
    
    Color fruitColor() default Color.RED;
    
}
 

第二节 提取Annotation信息

  • 使用注解修饰了类/方法/成员变量等之后,这些注解不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理注解信息(当然,只有当定义注解时使用了@Retention(RetentionPolicy.RUNTIME)修饰,JVM才会在装载class文件时提取保存在class文件中的注解,该注解才会在运行时可见,这样我们才能够解析).
  • Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。
  • java5在java.lang.reflect包下新增了 用AnnotatedElement接口代表程序中可以接受注解的程序元素.
  • AnnotatedElement接口的实现类有:Class(类元素)、Field(类的成员变量元素)、Method(类的方法元素)、Package(包元素),每一个实现类代表了一个可以接受注解的程序元素类型。
  • 这样, 我们只需要获取到Class、 Method、 Filed等这些实现了AnnotatedElement接口的类的实例,通过该实例对象调用该类中的方法(AnnotatedElement接口中抽象方法的重写) 就可以获取到我们想要的注解信息了。

  • 获得Class类的实例有三种方法

  • (1)利用对象调用getClass()方法获得Class实例

  • (2)利用Class类的静态的forName()方法,使用类名获得Class实例

  • (3)运用.class的方式获得Class实例,如:类名.class

AnnotatedElement接口提供的抽象方法(在该接口的实现类中重写了这些方法):

1.  <T extends Annotation> T getAnnotation(Class<T> annotationClass)
<T extends Annotation>为泛型参数声明,表明A的类型只能是Annotation类型或者是Annotation的子类。

功能:返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null

2. Annotation[] getAnnotations()

功能:返回此元素上存在的所有注解,包括没有显示定义在该元素上的注解(继承得到的)。(如果此元素没有注释,则返回长度为零的数组。)

3. <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)

功能:这是Java8新增的方法,该方法返回直接修饰该程序元素、指定类型的注解(忽略继承的注解)。如果该类型的注解不存在,返回null.

4. Annotation[] getDeclaredAnnotations()

功能:返回直接存在于此元素上的所有注解,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)

5. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

功能:判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。

6. <T extends Annotation> T[] getAnnotationsByTpye(Class<T> annotationClass)

功能: 因为java8增加了重复注解功能,因此需要使用该方法获得修饰该程序元素、指定类型的多个注解。

7. <T extends Annotation> T[] getDeclaredAnnotationsByTpye(Class<T> annotationClass)

功能: 因为java8增加了重复注解功能,因此需要使用该方法获得直接修饰该程序元素、指定类型的多个注解。

 

Class提供了getMethod()、getField()以及getConstructor()方法(还有其他方法),这些方法分别获取与方法、域变量以及构造函数相关的信息,这些方法返回Method、Field 以及Constructor类型的对象。

 

@Target(ElementType.Method)
@Retention(RetentionPopicy.RUNTIME)
public @interface MyTag
{
     string name() default "yeeku";
     int age() default 32;
}

public class Test
{
    @MyTag
    public void info()
    {

    }
}


获取Test类的info方法里的所有注解,并打印这些注解
Annotation [] aArray=Class.forName("Test").getMethod("info").getAnnotations(); //获取Class实例的方法1
for(Annotation an : aArray)
{
     system.out.println(an);
}

如果需要获取某个注解里的元数据则可以将注解强制类型转换,转换成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据
获取tt对象的info方法所包含的所有注解
Annotation [] annotation=tt.getClass().getMethod("info").getAnnotations(); //获取Class实例的方法2
for(Annotation tag : annotation)
{
     //如果tag注解是MyTag类型
     system.out.println("tag.name(): "+((MyTag)tag).name());
     system.out.println("tag.age(): "+((MyTag)tag).age());
}

 

模拟Junit框架

我们用@Testable标记哪些方法是可测试的, 只有被@Testable修饰的方法才可以被执行.
1
2
3
4
5
6
7
8
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable {
}
如下定义TestCase测试用例定义了6个方法, 其中有4个被@Testable修饰了:
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
public class TestCase {
   
    @Testable
    public void test1() {
        System.out.println("test1");
    }
   
    public void test2() throws IOException {
        System.out.println("test2");
        throw new IOException("我test2出错啦...");
    }
   
    @Testable
    public void test3() {
        System.out.println("test3");
        throw new RuntimeException("我test3出错啦...");
    }
   
    public void test4() {
        System.out.println("test4");
    }
   
    @Testable
    public void test5() {
        System.out.println("test5");
    }
   
    @Testable
    public void test6() {
        System.out.println("test6");
    }
}
为了让程序中的这些注解起作用, 必须为这些注解提供一个注解处理工具.
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
public class TestableProcessor {
   
    public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        int passed = 0;
        int failed = 0;
        for (Method method : Class.forName(clazz).getMethods()) {
            if (method.isAnnotationPresent(Testable.class)) {           //获取Class实例的方法3
                try {
                    method.invoke(null);
                    ++passed;
                }
                catch (IllegalAccessException | InvocationTargetException e) {
                    System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >");
                    e.printStackTrace(System.out);
                    ++failed;
                }
            }
        }
   
        System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
    }
   
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        TestableProcessor.process("com.feiqing.annotation.TestCase");
    }
}

抛出特定异常

前面介绍的只是一个标记Annotation,程序通过判断Annotation是否存在来决定是否运行指定方法,现在我们要针对只在抛出特殊异常时才成功添加支持,这样就用到了具有成员变量的注解:
1
2
3
4
5
6
7
8
9
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestableException {
    Class<? extends Throwable>[] value();
}
  • TestCase
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
public class TestCase {
   
    public void test1() {
        System.out.println("test1");
    }
   
    @TestableException(ArithmeticException.class)
    public void test2() throws IOException {
        int i = 1 / 0;
        System.out.println(i);
    }
   
    @TestableException(ArithmeticException.class)
    public void test3() {
        System.out.println("test3");
        throw new RuntimeException("我test3出错啦...");
    }
   
    public void test4() {
        System.out.println("test4");
    }
   
    @TestableException({ArithmeticException.class, IOException.class})
    public void test5() throws FileNotFoundException {
        FileInputStream stream = new FileInputStream("xxxx");
    }
   
    @Testable
    public void test6() {
        System.out.println("test6");
    }
}
  • 注解处理器
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
public class TestableExceptionProcessor {
   
    public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        int passed = 0;
        int failed = 0;
        Object obj = Class.forName(clazz).newInstance();
        for (Method method : Class.forName(clazz).getMethods()) {
            if (method.isAnnotationPresent(TestableException.class)) {
                try {
                    method.invoke(obj, null);
                    // 没有抛出异常(失败)
                    ++failed;
                } catch (InvocationTargetException e) {
                    // 获取异常的引发原因
                    Throwable cause = e.getCause();
   
                    int oldPassed = passed;
                    for (Class excType : method.getAnnotation(TestableException.class).value()) {
                        // 是我们期望的异常类型之一(成功)
                        if (excType.isInstance(cause)) {
                            ++passed;
                            break;
                        }
                    }
                    // 并不是我们期望的异常类型(失败)
                    if (oldPassed == passed) {
                        ++failed;
                        System.out.printf("Test <%s> failed <%s> %n", method, e);
                    }
                }
            }
        }
        System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
    }
   
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        process("com.feiqing.annotation.TestCase");
    }
}

注解添加监听器

下面通过使用Annotation简化事件编程, 在传统的代码中总是需要通过addActionListener方法来为事件源绑定事件监听器:
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
41
42
43
44
/**
 * Created by jifang on 15/12/27.
 */
public class SwingPro {
    private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
   
    private JButton ok = new JButton("确定");
    private JButton cancel = new JButton("取消");
   
    public void init() {
        JPanel jp = new JPanel();
   
        // 为两个按钮设置监听事件
        ok.addActionListener(new OkListener());
        cancel.addActionListener(new CancelListener());
   
        jp.add(ok);
        jp.add(cancel);
        mainWin.add(jp);
        mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainWin.pack();
        mainWin.setVisible(true);
    }
   
    public static void main(String[] args) {
        new SwingPro().init();
    }
}
   
class OkListener implements ActionListener {
   
    @Override
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "你点击了确认按钮!");
    }
}
   
class CancelListener implements ActionListener {
   
    @Override
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "你点击了取消按钮!");
    }
}
下面我们该用注解绑定监听器:
  • 首先, 我们需要自定义一个注解
1
2
3
4
5
6
7
8
9
/**
 * Created by jifang on 15/12/27.
 */
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
    Class<? extends ActionListener> listener();
}
  • 然后还要一个注解处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * Created by jifang on 15/12/27.
 */
public class ActionListenerInstaller {
   
    public static void install(Object targetObject) throws IllegalAccessException, InstantiationException {
        for (Field field : targetObject.getClass().getDeclaredFields()) {
            // 如果该成员变量被ActionListenerFor标记了
            if (field.isAnnotationPresent(ActionListenerFor.class)) {
                // 设置访问权限
                field.setAccessible(true);
   
                // 获取到成员变量的值
                AbstractButton targetButton = (AbstractButton) field.get(targetObject);
   
                // 获取到注解中的Listener
                Class<? extends ActionListener> listener = field.getAnnotation(ActionListenerFor.class).listener();
   
                // 添加到成员变量中
                targetButton.addActionListener(listener.newInstance());
            }
        }
    }
}
  • 主程序(注意注释处)
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
public class SwingPro {
   
    private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
   
    /**
     * 使用注解设置Listener
     */
    @ActionListenerFor(listener = OkListener.class)
    private JButton ok = new JButton("确定");
   
    @ActionListenerFor(listener = CancelListener.class)
    private JButton cancel = new JButton("取消");
   
    public SwingPro init() {
        JPanel jp = new JPanel();
   
        // 使得注解生效
        try {
            ActionListenerInstaller.install(this);
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace(System.out);
        }
   
        jp.add(ok);
        jp.add(cancel);
        mainWin.add(jp);
        mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainWin.pack();
        mainWin.setVisible(true);
   
        return this;
    }
   
    //下同
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值