Annotation(注解)是JDK5.0版本开始引入的,是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会直接影响到程序的语义,只是作为注解(标识)存在,可以通过反射机制编程实现对这些元数据的访问。
在Java开发过程中,我们过去比较常见的方式是将软件的各种配置参数存贮在XML文件中,程序运行时从XML配置文件中读取出各配置参数进行运行时环境配置。这个方式使用程序运行灵活的同时也带来一个问题:就是XML配置文件越来越多,不易管理。注解引进后,减少配置、使用注解替代部分XML配置慢慢变成一种趋势。从某种角度来讲,注解具有XML配置的功能,它可有不同的预定义属性,属性值可以在声明该标时指定。在代码中使用注解,就相当于把一部分元数据从XML文件移到了代码本身,并在代码中进行管理和维护。这使得配置文件变得越来越少,而规范约定和注解代替了一些繁琐的配置信息。我们可以在许多Java开源项目中看到这种“零配置”的思维。
那么如何来自定义和使用注解呢(这里主要说明运行时使用注解)?
要实现一个自定义注解,首先需要通过关键字@interface来定义一个注解标记。例:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String name() default "abc";
MyEnum type() default MyEnum.A;
enum MyEnum {
A, B
}
}
在上例中,通过元注解来描述该注解的使用范围( @Target )、生命周期( @Retention ),并定义了一个注解标记。我们对@Target、@Retention进行一个简单说明:
@Target 用于描述注解的使用范围(即:被描述的注解可以用在什么地方),其取值有:
取值 |
描述 |
CONSTRUCTOR |
构造器声明 |
FIELD |
域声明 |
LOCAL_VARIABLE |
局部变量声明 |
METHOD |
方法声明 |
PACKAGE |
包声明 |
PARAMETER |
参数声明 |
TYPE |
类或接口声明 |
@Retention 用于描述注解的生命周期(即:被描述的注解在什么范围内有效),其取值有:
取值 |
描述 |
SOURCE |
注解将被编译器丢弃 |
CLASS |
注解在class文件中可用,但会被VM丢弃 |
RUNTIME |
将在运行期保留注解,可通过反射机制读取注解的信息 |
定义好注解标记后,我们就可以在代码中使用这个注解了,如:
@ MyAnnotation (type="B”, name="Hello World")
public void anyMethod() {
... ...
}
但要使用注解标记工作起来,我们还需要编写这个注解标记的处理器(注解处理器是一段用于解释或处理自定义注解标记的代码),它是通过java的反射机制来进行解析。如下:
public class MyAnnotationTest {
@MyAnnotation(name = "a", type = MyAnnotation.MyEnum.B)
public void execute() {
System.out.println("method");
}
public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
MyAnnotationTest annotationTest = new MyAnnotationTest();
//获取MyAnnotationTest的Class实例
Class<MyAnnotationTest> c = MyAnnotationTest.class;
//获取需要处理的方法Method实例
Method method = c.getMethod("execute", new Class[]{});
//判断该方法是否包含MyAnnotation注解
if (method.isAnnotationPresent(MyAnnotation.class)) {
//获取该方法的MyAnnotation注解实例
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
//执行该方法
method.invoke(annotationTest, new Object[]{});
//获取myAnnotation
String value = myAnnotation.name();
System.out.println(value);
}
//获取方法上的所有注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
以上代码仅仅是示意如何使用反射来得到注解信息。
在实际的应用中,我们可以采用注解来描述数据,大致过程如此:创建一个JavaBean,使用注解来描述这个JavaBean的语义。这个用法类似于用XML来描述一个数据,每个字段注解对应于XML的属性元素,字段的取值对应于XML的属性值,当然数据本身也可用XML来进行描述。这种方式常见于接口间的数据描述,最常见的如数据库连接信息。
另一种用法,就是类似于使用XML来配置运行时所需要的数据,比如我们常见的Spring MVC的注解配置方式,如:
@RequestMapping(value = {"xxx"}, method = {org.springframework.web.bind.annotation.RequestMethod.POST})
表示POST请求输入为xxx时,调用该方法。如果我们在XML中进行配置,就类似于:
<value>xxx</value><method>POST</method><invoke>xxx.xxx(xxx类的xxx方法)</invoke>
这种方式在过去的开发中,我们是常见的。比如网管软件开发时,对多网元多版本进行控制时,我们过去就常将网元类型、版本和它的处理方法配置在一个配置文件中,而这种方式也可用注解方式来解决。
另外,就是在某些API方法中使用注解对API的调用条件进行限制等。
当然这些处理都需要一个注解标记处理框架。需要注意的是,Annoation的表达能力有限,不如XML的强,所以在应用时应该考虑使用那种方式更简便。
附:
java5.0在java.lang包中定义了3种标准的annotation类型:A. Override,B. Deprecated,C. SuppressWarnings。并且还定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明:@Target、@Retention、@Documented、@Inherited。