(本文内容来源于疯狂Java讲义,感谢。)
注解(Annotation)
Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具,开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的“name=value”对中。
Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据。值得指出的是,Annotation不影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称APT(Annotation Processing Tool)。
1.基本Annotation
Annotation必须使用工具类来处理,工具负责提取Annotation里包含的元数据,工具还会根据这些元数据增加额外的功能。在系统学习新的Annotation语法之前,先看一下Java提供的5个基本Annotation的用法——使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素。
@Override
@Deprecated
@SuppressWarnings
@SafaVarargs
@FunctionalInterface
2.暂无
3.自定义Annotation
3.1定义Annotation
定义新的Annotation类型使用@interface关键字(在原有的interface关键字前增加@符号)定义一个新的Annotation类型与定义一个接口非常像,如下代码可定义一个简单的Annotation类型。
public @interface Test {
}
定义了该Annotation之后,就可以在程序的任何地方使用该Annotation,使用Annotation的语法非常类似于public 、final这样的修饰符,通常可用于修饰程序中的类、方法、变量、接口等定义。通常会把Annotation放在所有修饰符之前,而且由于使用Annotation时可能还需要为成员变量指定值,因而Annotation的长度可能较长,所以通常把Annotation另放一行,如下所示。
@Test
public class MyClass{
}
在默认情况下,Annotation可用于修饰任何程序元素,包括类、接口、方法等,如下程序使用@Test来修饰方法。
public class MyClass {
@Test
public void info(){
}
}
Annotation不仅可以是这种简单的Annotation,还可以带成员变量,Annotation的成员变量在Annotation定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型,如下可以定义一个有成员变量的Annotation。
public @interface MyTag {
String name();
int age();
}
上面定义Annotation的代码与定义接口的语法非常像,只是MyTag使用@interface关键字来定义,接口使用interface来定义。
一旦在Annotation里定义了成员变量之后,使用该Annotation时就应该为该Annotation的成员变量指定值,如下所示。
public class Test {
@MyTag(name="赵一伤", age=38)
public void info(){
}
}
也可以在定义Annotation的成员变量时为其指定初始值(默认值),指定成员变量的初始值可使用default关键字。如下代码定义了@MyTag Annotation,该Annotation里包含了两个成员变量:name和age,这两个成员变量使用default指定了初始值。
public @interface MyTag{
String name() default "Obama";
int age() default 45;
}
如果为Annotation的成员变量指定了默认值,使用该Annotation时则可以不为这些成员变量指定值而是直接使用默认值。
public class Test{
@MyTag
public void into(){}
}
当然也可以在使用MyTag Annotation时为成员变量指定值,如果为MyTag的成员变量指定了值,则默认值不会起作用。
根据Annotation是否可以包含成员变量,可以把Annotation分为如下两类。
标记Annotation: 没有定义成员变量的Annotation类型被称为标记。这种Annotation仅利用自身的存在与否来提供信息,如前面介绍的@Override、@Test等Annotation。
元数据Annotation: 包含成员变量的Annotation,因为它们可以接受更多的元数据,所以也被称为元数据Annotation。
3.2提取Annotation信息
使用Annotation修饰了类、方法、成员变量等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息。
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。Java5在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有如下几个实现类。
Class: 类定义
Constructor: 构造器定义
Field: 类的成员变量定义
Method: 类的方法定义
Package: 类的包定义
java.lang.reflect包下主要包含一些实现反射功能的工具类,从Java5开始,java.lang.reflect包所提供的反射API增加了读取运行时Annotation的能力。只有定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,该Annotation才会在运行时可见,JVM才会在装载*.class文件时读取保存在class文件中的Annotation。
AnnotatedElement接口是所有程序元素(如Class、Method、Constructor等)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor等)之后,程序就可以调用该对象的如下几个方法来访问Annotation信息。
<A extends Annotation> A getAnnotation(Class<A> annotationClass):
<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass):
Annotation[] getAnnotations():
Annotation[] getDeclaredAnnotations():
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):
<A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass):
<A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass):
下面的程序片段用于获取Test类的info方法里的所有注解,并将这些注解打印出来。
Annotation[] aArr = Class.forName("com.bu.ant.Test").getMethod("info").getAnnotations();
System.out.println(aArr.length);
for(Annotation an : aArr){
System.out.println(an);
}
如果需要获取某个注解里的元数据,则可以将注解强制类型转换为所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据。
MyClass mc = new MyClass();
Annotation[] annotations = mc.getClass().getMethod("info").getAnnotations();
for (Annotation tag : annotations){
if (tag instanceof MyClass){
System.out.println("tag is: "+tag);
System.out.println("tag.name()"+((MyTag)tag).name());
System.out.println("tag.age()"+((MyTag)tag).age());
}
}
3.3使用Annotation的示例
下面是两个使用Annotation的例子,第一个是标记型Annotation,第二个是元数据Annotation。
第一个
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Testable {
}
此程序定义了一个@Testable Annotation,定义该Annotation时使用了@Retention和@Target两个JDK的元注解,其中@Retention注解指定Testable注解可以保留到运行时(JVM可以提取到该Annotation的信息),而@Target注解指定@Testable只能修饰方法。
测试类如下:
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注解的一条重要原则。为了让程序中的这些注解起作用,接下来必须为这些注解提供一个注解处理工具。
处理工具类如下:
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 e) {
System.out.println("方法"+m+"运行失败,异常:"+e.getCause());
failed++;
}
}
}
System.out.println("共运行了:"+(passed+failed)+"个方法,其中:\n"+"失败了:"+failed+"个,\n"
+"成功了:"+passed+"个!");
}
}
ProcessTest类里只包含一个process(String clazz)方法,该方法可接收一个字符串参数,该方法将会分析clazz参数所代表的类,并运行该类里使用@Testable修饰的方法。
void fun3() throws ClassNotFoundException {
ProcessorTest.process("com.bu.ant.MyTest");
}
执行结果
方法public static void com.bu.ant.MyTest.m3()运行失败,异常:java.lang.IllegalArgumentException: 参数出错了
方法public static void com.bu.ant.MyTest.m7()运行失败,异常:java.lang.RuntimeException: 程序业务出现异常!
共运行了:4个方法,其中:
失败了:2个,
成功了:2个!
从结果可以看出,程序中的@Testable起作用了,MyTest类里面以@Testable注解修饰的方法都被测试了。
第二个(元数据注解)
通过使用Annotation来简化时间编程,在传统的事件编程中总是需要通过addActionListener()方法来为事件源绑定事件监听器,下面的示例则通过@ActionListenerFor来为程序中的按钮绑定事件监听器。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
Class<? extends ActionListener> listener();
}
定义了这个@ActionListenerFor之后,使用该注解时需要指定一个listener成员变量,该成员变量用于指定监听器的实现类。下面程序使用@ActionListenerFor注解来为2个按钮绑定事件监听器。
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();
}
}
public class OkListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "点击了确认按钮");
}
}
public class CancelListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "点击了取消按钮");
}
}
上面的程序中定义了2个JButton按钮,并使用@ActionListenerFor注解为这两个按钮绑定了事件监听器,使用@ActionListenerFor注解时传入了listener元数据,该数据用于设定每个按钮的监听器实现类。
下面是注解处理工具类,该工具分析对象中所有的成员变量,如果该成员变量前使用了@ActionListenerFor修饰,则取出该Annotation中的listener元数据,并根据该数据来绑定事件监听器。
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();
}
}
}
程序运行后
按钮点击后的效果
以上代码的两个监听器实现类原来是内部类,结果运行不成功,后提取出单独的类,才运行成功,不知道是什么原理。难道内部类没有默认的构造方法吗?
推荐一位大佬的通俗易懂的注解文章:https://blog.csdn.net/qq1404510094/article/details/80577555
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)