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

第一节:定义注解

      定义新的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 ;
     }
   
     //下同
}

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值