14.3.3 使用注解的示例
下面分别介绍两个使用注解的例子,第一个注解@Testable
没有任何成员变量,仅是一个标记注解,它的作用是标记哪些方法是可测试的。
程序 定义注解
1 2 3 4 5 6 7 8 9
| import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Testable { }
|
上面程序定义了一个@Testable
注解,定义该注解时使用了@Retention
和@Target
两个JDK
的元注解,其中@Retention
注解指定@Testable
注解可以保留到运行时(JVM
可以提取到该注解的信息),而@Target
注解指定@Testable
只能修饰方法。
上面的@Testable
用于标记哪些方法是可测试的,该注解可以作为JUnit
测试框架的补充,在JUnit
框架中它要求测试用例的测试方法必须以test
开头。如果使用@Testable
注解,则可把任何方法标记为可测试的。
如下MyTest
测试用例中定义了8个方法,这8个方法没有太大的区别,其中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 33 34 35
| public class MyTest { @Testable public static void m1() { }
public static void m2() { }
@Testable public static void m3() { throw new IllegalArgumentException("参数出错了!"); }
public static void m4() { }
@Testable public static void m5() { }
public static void m6() { }
@Testable public static void m7() { throw new RuntimeException("程序业务出现异常!"); }
public static void m8() { } }
|
正如前面提到的,仅仅使用注解来标记程序元素对程序是不会有仼何影响的,这也是Java
注解的条重要原则。为了让程序中的这些注解起作用,接下来必须为这些注解提供一个注解处理工具。
程序 反射运行注解修饰的方法
下面的注解处理工具会分析目标类,如果目标类中的方法使用了@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
| import java.lang.reflect.*;
public class ProcessorTest { public static void process(String clazz) throws ClassNotFoundException { int passed = 0; int failed = 0; for (Method m : Class.forName(clazz).getMethods()) { if (m.isAnnotationPresent(Testable.class)) { try { m.invoke(null); passed++; } catch (Exception ex) { System.out.println("方法" + m + "运行失败,异常:" + ex.getCause()); failed++; } } } System.out .println("共运行了:" + (passed + failed) + "个方法,其中:\n" + "失败了:" + failed + "个,\n" + "成功了:" + passed + "个!"); } }
|
ProcessorTest
类里只包含一个process(String clazz)
方法,该方法可接收一个字符串参数,该方法将会分析clazz
参数所代表的类,并运行该类里使用@Testable
修饰的方法
该程序的主类非常简单,提供主方法,使用ProcessorTest
来分析目标类即可。
程序 测试类
1 2 3 4 5 6
| public class RunTests { public static void main(String[] args) throws Exception { ProcessorTest.process("MyTest"); } }
|
运行上面程序,会看到如下运行结果:
1 2 3 4 5 6
| G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\14\14.3\01>java RunTests 方法public static void MyTest.m3()运行失败,异常:java.lang.IllegalArgumentException: 参数出错了! 方法public static void MyTest.m7()运行失败,异常:java.lang.RuntimeException: 程序业务出现异常! 共运行了:4个方法,其中: 失败了:2个, 成功了:2个!
|
通过这个运行结果可以看出,程序中的@Testable
起作用了,MyTest
类里以@Testable
注解修饰的方法都被测试了。
通过上面例子读者不难看出,其实注解十分简单,它是对源代码増加的一些特殊标记,这些特殊标记可通过反射获取,当程序获取这些特殊标记后,程序可以做出相应的处理(当然也可以完全忽略这些注解)。
使用注解来简化事件编程
前面介绍的只是一个标记注解,程序通过判断该注解存在与否来决定是否运行指定方法。下面程序通过使用注解来简化事件编程,在传统的事件编程中总是需要通过addActionListener()
方法来为事件源绑定事件监听器,本示例程序中则通过@ActionListenerFor
来为程序中的按钮绑定事件监听器。
程序 定义注解
1 2 3 4 5 6 7 8 9 10
| import java.lang.annotation.*; import java.awt.event.*;
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ActionListenerFor { Class<? extends ActionListener> listener(); }
|
定义了这个@ActionListenerFor
之后,使用该注解时需要指定一个listener
成员变量,该成员变量用于指定监听器的实现类。
下面程序使用@ActionListenerFor
注解来为两个按钮绑定事件监听器。
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
| import java.awt.event.*; import javax.swing.*;
public class AnnotationTest { private JFrame mainWin = new JFrame("使用注解绑定事件监听器"); @ActionListenerFor(listener = OkListener.class) private JButton ok = new JButton("确定"); @ActionListenerFor(listener = CancelListener.class) 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.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.pack(); mainWin.setVisible(true); }
public static void main(String[] args) { new AnnotationTest().init(); } }
class OkListener implements ActionListener { public void actionPerformed(ActionEvent evt) { JOptionPane.showMessageDialog(null, "单击了确认按钮"); } }
class CancelListener implements ActionListener { public void actionPerformed(ActionEvent evt) { JOptionPane.showMessageDialog(null, "单击了取消按钮"); } }
|
上面程序中定义了两个JButton
按钮,并使用@ActionListenerFor
注解为这两个按钮绑定了事件监听器,使用@ActionListenerFor
注解时传入了listener
元数据,该数据用于设定每个按钮的监听器实现类
正如前面提到的,如果仅在程序中使用注解是不会起任何作用的,必须使用注解处理工具来处理程序中的注解。程序中①处代码使用了ActionListenerInstaller
类来处理本程序中的注解,该处理器分析目标对象中的所有成员变量,如果该成员变量前使用了@ActionListenerFor
修饰,则取出该注解中的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 25 26 27 28 29 30 31 32 33 34
| import java.lang.reflect.*; import java.awt.event.*; import javax.swing.*;
public class ActionListenerInstaller { public static void processAnnotations(Object obj) { try { Class cl = obj.getClass(); for (Field f : cl.getDeclaredFields()) { f.setAccessible(true); ActionListenerFor a = f.getAnnotation(ActionListenerFor.class); Object fObj = f.get(obj); if (a != null && fObj != null && fObj instanceof AbstractButton) { Class<? extends ActionListener> listenerClazz = a.listener(); ActionListener al = listenerClazz.newInstance(); AbstractButton ab = (AbstractButton) fObj; ab.addActionListener(al); } } } catch (Exception e) { e.printStackTrace(); } } }
|
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
| import java.lang.reflect.*; import java.awt.event.*; import javax.swing.*;
public class ActionListenerInstaller { public static void processAnnotations(Object obj) { try { Class cl = obj.getClass(); for (Field f : cl.getDeclaredFields()) { f.setAccessible(true); ActionListenerFor a = f.getAnnotation(ActionListenerFor.class); Object fObj = f.get(obj); if (a != null && fObj != null && fObj instanceof AbstractButton) { Class<? extends ActionListener> listenerClazz = a.listener(); ActionListener al = listenerClazz.newInstance(); AbstractButton ab = (AbstractButton) fObj; ab.addActionListener(al); } } } catch (Exception e) { e.printStackTrace(); } } }
|
上面程序中的两行粗体字代码根据@ActionListenerFor
注解的元数据取得了监听器实现类,然后通过反射来创建监听器对象,接下来将监听器对象绑定到指定的按钮(按钮由被@ActionListenerFor
修饰的Field
表示)。
运行上面的AnnotationTest
程序,会看到如图14.3所示的窗口
单击“确定”按钮,将会弹出如图14.4所示的“单击了确认按钮”对话框:
这表明使用该注解成功地为ok
、cancel
两个按钮绑定了事件监听器。