`
holdbelief
  • 浏览: 699417 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

第十三课时:Annotation 注释

阅读更多

 

从 JDK 5.0 开始,Java 增加了对元数据 (MetaData)的支持,也就是 Annotation (注释)。用 Annotation 程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

Annotation 提供了一条为程序元素设置元数据的方法,从某些方面来看,Annotation 就像修饰符一样被使用,可以用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在 Annotation 的 “name=value” 对中。

Annotation 是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来取得注释里的元数据。

 

一、基本的 Annotation

Java 提供的三个基本 Annotation 的用法:使用 Annotation 时要在其前面增加 @ 符号,并把该 Annotation 当成一个修饰符使用,用于修饰它支持的的程序元素:
  • @Override
  • @Deprecated
  • @SuppressWarnings
Java 提供的三个基本Annotation 都定义在 java.lang 包下。

1、限定重写父类方法:@Override
@Override 就是用来指定方法覆载的,它可以强制一个子类必须要覆盖父类的方法
示例:
public class Fruit
{
public void info()
System.out.println("水果的info方法。。。"); 
}

class Apple extends Fruit
{
//使用@Override指定下面方法必须重写父类方法
@Override 
// public void info()
public void inf0()
System.out.println("苹果重写水果的info方法。。。"); 
}
}

2、标示已过时:@Deprecated
示例:
class Apple
{
//定义info方法已过时
@Deprecated
public void info()
{
System.out.println("Apple的info方法");
}
}
public class DeprecatedTest
{
public static void main(String[] args) 
{
//下面使用info方法时将会被编译器警告
new Apple().info();
}
}

3、抑制编译器警告
示例:取消没有泛型限制的集合引发的编译器警告
@SuppressWarnings(value="unchecked")
public class SuppressWarningsTest
{
public static void main(String[] args) 
{
List<String> myList = new ArrayList();
}
}

二、自定义 Annotation
1、定义 Annotation
定义新的 Annotation 类型使用 @interface 关键字,例如:
// 定义一个简单的 Annotation 类型
public @interface Test
{}
使用 Test 注释:
// 用 @Test 修饰类定义
@Test
public class MyClass
{
……
}

默认情况下,Annotation 可用于修饰任何程序元素,包括类、接口、方法等。
public class MyClass
{
// 使用 @Test Annotation 修饰方法
@Test
public void info()
{
……
}
……
}

定义带成员变量的 Annotation,Annotation 的成员变量在 Annotation 定义中以无参数方法的形式声明,其方法名和返回值定义了该成员变量的名字和类型。示例:
public @interface MyTag
{
// 定义两个成员变量的 Annotation
// Annotation 中的成员变量以方法的形式定义
String name();
int age();
}
使用带成员变量的 Annotation:
public class Test
{
// 使用带成员变量的 Annotation 时,需要为成员变量赋值
@MyTag(name="xx", age=6)
public void info()
{
……
}
……
}

还可以在定义 Annotation 的成员变量时为其指定初始值,使用 default 关键字,示例:
public @interface MyTag
{
String name() default "liuchuanfeng";
int age() default 16;
}
如果为 Annotation 的成员变量指定了默认值,使用该 Annotation 则可以不为这些成员变量指定值,而是直接使用默认值。

2、提取 Annotation 的信息
Java 使用 Annotation 接口来代表程序元素前面的注释,该接口是所有 Annotation 类型的父接口,除此之外,Java 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注释的程序元素,该接口主要有以下几个实现类:
  • Class:类定义
  • Constructor:构造器定义
  • Field:类成员变量定义
  • Mehtod:类的方法定义
  • Package:类的包定义
AnnotatedElement 接口是所有程序元素(如:Class、Mehtod、Constructor)的父接口,所以程序通过反射获得了某个类的 AnnotatedElement 对象(如:Class、Mehtod、Constructor)之后,程序就可以调用对象的如下三个方法来访问 Annotation 的信息:
  • getAnnotation(Class<T> annotationClass):返回该程序元素上存在的、指定的类型的注释,如果该类型的注释不存在,则返回 null
  • Annotation[] getAnnotations():返回程序元素上存在的所有注释
  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注释,存在则返回 true,否则返回 false
示例:下面程序片段用于获取 Test 类 info 方法里的所有注释:
Annotation[] aArray = Class.forName("Test").getMethod("info").getAnnotations();
for (Annotation an : aArray)
{
System.out.println(an);
}
如果需要获取某个注释里的元数据,则可以将注释强制类型转换成所需要的注释类型,然后通过注释对象的抽象方法来访问这些数据,如:
Annotation[] annotation = tt.getClass().getMethod("info").getAnnotations();
// 遍历每个注释对象
for (Annotation tag : annotation)
{
// 如果 tag 注释是 MyTag1 类型
if(tag instanceof MyTag1)
{
System.out.println("Tag is:" + tag);
System.out.println("tag.name(): " + ((MyTag1) tag).method1());
System.out.println("tag.age(): " + ((MyTag1) tag).method2());
}
// 如果 tag 注释是 MyTag2 类型
if(tag instanceof MyTag2)
{
System.out.println("Tag is:" + tag);
System.out.println("tag.name(): " + ((MyTag2) tag).method1());
System.out.println("tag.age(): " + ((MyTag2) tag).method2());
}
}

3、使用 Annotation 的例子
Annotation Testable 没有成员变量,仅仅是一个标记 Annotation,它的作用是标记哪些方法是可测试的。

Testable.java:
import java.lang.annotation.*; 
 
@Retention(RetentionPolicy.RUNTIME)    
@Target(ElementType.METHOD)
//定义一个标记注释,不包含任何成员变量,即不可传入元数据
public @interface Testable 
{
}

MyTest 测试用例里定义了 8 个方法,这 8 个方法没有太大的区别,其中四个方法是用 @Testable 注释来标记这些方法是可测试的。
MyTest
MyTest.java:
public class MyTest
{
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m1() 
{
}
public static void m2() 
{
}   
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m3() 
{        
throw new RuntimeException("Boom");  
}
public static void m4()
{
}       
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m5()
{
}  
    public static void m6()
{
}
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m7()
{            
throw new RuntimeException("Crash");   
}        
public static void m8()
{
}
}
仅仅使用注释来标识程序元素对程序是不会有任何影响的,这也是 Java 注释的一条重要原则,为了让程序中这些注释起作用,我们必须为这些注释提供一个注释处理工具。
下面的注释处理工具会分析目标类,如果目标类中方法使用了@Testable 注释修饰,则通过反射运行该测试方法。
public class TestProcessor
{
public static void process(String clazz) throws ClassNotFoundException
{
int passed = 0;
int failed = 0;

// 遍历 obj 对象的所有方法
for (Method m : Class.forName(clazz).getMethods())
{
// 如果包含 @Testable 标记注释
if(m.isAnnotationPresent(Testable.class))
{
try
{
// 调用 m 方法
m.invoke(null);
// passed 加 1
passed++;
}
catch(Exception ex)
{
System.out.printf("方法" + m + "运行失败,异常:" + ex.getCause() + "\n");
failed++;
}
}
}

// 统计测试结果
System.out.printf("共运行了:" + (passed + failed)+ "个方法,其中:\n" + 
"失败了:" + failed + "个,\n" +  
"成功了:" + passed + "个!\n"); 
}
}

主函数类:RunTests.java
public class RunTests
{
public static void main(String[] args) throws Exception
{
// 处理 MyTest 类
TestProcessor.process("MyTest");
}
}

带成员变量的 Annotation 示例:
ActionListenerFor.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor
{
// 定义一个成员变量,用于设置元数据
// 该 listener 成员变量用于保存监听器实例
String listener();
}

public class AnnotationTest
{
private JFrame mainWin = new JFrame("使用注释绑定事件监听器");
// 使用注释为 ok 按钮绑定事件监听器
@ActionListenerFor(listener="OkListener")
private JButton ok = new JButton("确定");
// 使用注释为 cancel 按钮绑定事件监听器
@ActionListenerFor(listener = "CancelListener")
private JButton cancel = new JButton("取消");

public void init()
{
// 初始化主界面
JPanel jp = new JPanel();
jp.add(ok);
jp.add(cancel);
mainWin.add(jp);
ActionListenerInstaller.processAnnotations(this); // ①
mainWin.setDefaultCloseOption(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(ture);
}

public static void main(String[] args)
{
new AnnotationTest.init();
}
}

// 定义 ok 按钮的事件监听器实现类
class OkListener implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
JOptionPane.show(null, "点击了确认按钮");
}
}

// 定义 cancel 按钮的事件监听器实现类
class CanelListener implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
JOptionPane.show(null, "点击了取消按钮");
}
}

正如前面提到,如果仅仅在程序中使用注释是不会有任何作用的,必须使用注释处理工具来处理程序中的注释。程序中 ① 处代码使用了 ActionListenerInstaller 来处理本程序中的注释,该处理器分析目标对象中所有的 Field,如果该 Field 前使用了 ActionListenerFro Annotation,则取出该 Annotation 中的 listener 元数据,并根据该数据来绑定事件监听器。
ActionListenerInstaller.java
public class ActionListenerInstaller
{
try
{
// 获取 obj 对象的类
Class c1 = obj.getClass();
// 获取指定的 obj 对象中所有的 Field,并遍历每个 Field
for (Field f : c1.getDeclaredFields())
{
// 将指定 Field 设置成可自由访问的,避免 private 的 Field 不可访问
f.setAccessible(true);
// 获取指定 Field 的 ActionListenerFor 类型的注释
ActionListenerFor a = f.getAnnotation(ActionLinstenerFor.class);
if (a != null && a instanceof AbstractButton) 
{
// 获取 a 注释里的元数据 listener (它是一个监听器类)
Class listenerClazz = Class.forName(a.listener());
// 使用反射来创建 listener 类的对象
ActionListener al = listenerClazz.newInstance();
// 获取 f Field 实际对应的对象
AbstractButton b = (AbstractButton) f.get(obj);
//为ab对象添加事件监听器 ab.addActionListener(al);
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}

三、JDK 的元 Annotation
JDK 除了在 java.lang 下提供了 3 个基本 Annotation 之外,还在 java.lang.annotation 包下提供了四个 Meta Annotation (元 Annotation),这四个 Annotation 都是用来修饰其他的 Annotation 定义。
1、使用 @Retention
@Retention 只能用于修饰一个 Annotation 定义,用于指定该 Annotation 可以保留多长时间,@Retention 包含一个 RetentionPolicy 类型的 value 成员变量,
value 成员变量的值只能有如下三个:
  • RetentionPolicy.CLASS:编译器将吧主是记录在 class 文件中。当运行 Java 程序时,JVM 不再保留注释。这是默认值
  • RetentionPolicy.CLASS:编译器将吧主是记录在 class 文件中。当运行 Java 程序时,JVM 也会保留注释,程序可以通过反射获取该注释
  • RetentionPolicy.CLASS:编译器直接丢弃这种策略的注释
2、使用 @Target
@Target 用于指定被修饰的 Annotation 能用于修饰哪些程序元素,它也包含一个名为 value 的成员变量,该成员变量的值只能是如下几个:
  • ElementType.ANNOTATION_TYPE
  • ElementType.CONSTRUCTOR
  • ElementType.FIELD
  • ElementType.LOCAL_VARIABLE
  • ElementType.METHOD
  • ElementType.PACKAGE
  • ElementType.PARAMETER
  • ElementType.TYPE
3、使用 @Documented
@Documented 用于指定被该元 Annotaton 修饰的 Annotation 类将被 javadoc 工具提取成文档,如果定义 Annotation 类时使用了 @Documented 修饰,则所有使用该 Annotation 修饰的程序元素的 API 文档中将会包含该 Annotation 说明
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics