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

对java annotation的理解

 
阅读更多

前言 

    我们在一些程序中经常会看到annotation,虽然平时自己定义和使用的机会不是很多。这些annotation看起来有点奇怪,我们在一些类、方法、成员变量等声明的地方加上了这么一个标记,实际上我们运行的程序里这些被标记的部分并没有出现什么特殊的改变。这些annotation到底是用在哪些地方呢?它有什么用呢?这里我们将对这几个方面做一个讨论。

初识annotation

    我们在JUnit, spring等框架中都见过一些annotation的标记。比如JUnit里面的@Test。在java se里面也有几个比较常见的,比如@Deprecated, @Override, @SuppressWarnings。这些annotation会对我们的程序产生哪种影响呢?我们先尝试一下@Deprecated这个的示例。如下是我们定义的一个类TestSample:

public class TestSample {
    private String sampleName;

    public TestSample(String sampleName) {
        this.sampleName = sampleName;
    }
    
    public void printName() {
        System.out.println(sampleName);
    }
}

    非常的简单,

    下面是我们使用这个类的代码:

public class TestMain {
    public static void main(String[] args) {
        TestSample sample = new TestSample("hello");
        sample.printName();
    }
}

 我们如果运行代码的时候,会发现结果输出如下:

hello

    一切都是很正常的输出。

    现在,如果我们在TestSample的类声明前加上annotation @Deprecated,再重新编译和运行代码的时候,结果会显示如下的信息:

 

Note: TestMain.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

 我们代码明明就加了这么一个annotation,其他地方都没有变,怎么会这样呢?让人感觉运行时的行为发生了改变。如果我们去查阅关于@Deprecated的官方说明就会发现,这个标记主要是用来声明它所标注的API已经废弃了。所以后续的代码里不应该使用它,否则编译器会提醒报错。从这里我们可以推测出我们标注的annotation相当于给当前的编译器或者虚拟机提供一个消息提示机制。当我们的代码被编译或者编译后被加载到jvm的时候,他们会检查我们提供的消息来做一些处理。这些annotation只是通知了使用这些API的环境,但是它们本身并没有改变这些API的内在行为。这些被通知到的环境却有可能根据我们提供的annotation对运行的代码做一些调整和改变。

    再看看我们列举的@Override,它平时看起来好像是可有可无的。我们在定义一个父类的时候,希望子类覆写它的方法,则会将这个标记加到子类实现的方法上。可是如果我们把它去掉也没关系。但是它带来的一个好处就是如果我们的父类和子类都发生了一些变化,当父类需要被覆写的方法名字变了,这样子类的名字也需要对应的变化。如果我们忘记修改子类的对应方法名,在不加这个@Override标记的时候是可以编译通过的。这样就带来了一个潜在的错误。而如果我们加了这个标记,则编译器会帮我们检查是否有一致的覆写方法。从这几个示例中我们现在可以发现,我们用annotation是为了让编译器或者虚拟机能够提供一些特定的功能支持。

自定义annotation

    前面我们看了几个annotation的应用,假定我们要自定义一个annotation该怎么做呢?一个常用的annotation定义如下: 

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)

public @interface MyAnnotation {
    public String name();
    public String value();
}

    下面我们来分析一下这部分代码的意思。我们定义的annotation名是MyAnnotation,它采用了一个类似于定义interface的声明,只是这里在interface前面增加了一个@。这里表示我们定义了这么一个annotation。而前面的@Retention这个标注则用来定义这个annotation可用的范围。这里的RUNTIME表示我们的annotation必须在运行的时候可访问的。也就是说我们这些标记会编译之后在装载到jvm中时也需要加载进去,运行的时候才能够访问到它。除了这里标注的RUNTIME,还有其他几种可用范围,比如RetentionPolicy.CLASS, RetentionPolicy.SOURCE。RetentionPolicy.ClASS表示annotation只存在于.class文件中,在运行的时候不存在。这也是可用范围的默认值。而RetentionPolicy.SOURCE表示annotation只存在于源文件中,并不会被编译后放到.class文件中来。

    接着的标注是@Target,它表示我们定义的这个annotation可以使用到哪些元素类型中。比如前面我们可以将@Deprecated用到类的声明或者方法的声明。那么如果我们定义的annotation想用到这些地方,就需要在这里考虑。annotation不能随便乱放的。这里的Element.TYPE则表示它可以应用于任何类型。

有了前面的定义解释之后,我们使用这个annotation的代码则如下:

 

@MyAnnotation(name="someName",  value = "Hello World")
public class TheClass {
}

    这部分则相当于告诉使用该部分的其他代码,我们在这里做了一个这样的标记。如果我这个定义annotation的一方和使用该方法的一方达成了一致。他们使用的代码可以根据这个annotation做一些特殊的操作。

    当我们定义好了这些annotation之后,我们其他的代码是怎么来识别和使用这些annotation呢?这里一般就要用到反射了。以前面我们定义的annotation为例,我们使用它的代码如下:

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

    这部分代码的运行结果如下:

name: someName
value: Hello World

    ok,有了前面这些说明,相信我们已经对annotation的基本用法已经了解了。后面我们针对一些具体应用的场景再做进一步的讨论。

一种annotation的应用

    我们先来思考一个JUNIT相关的问题。我们知道,在Junit里,当我们要运行一个测试方法,我们需要将被定义的测试方法加一个annotation @Test。我们每个方法一般来说都是对一组目标值或者执行行为的断言。

在普通的定义里,我们定义的普通方法和需要测试框架运行的方法有什么差别呢?我们可以定义一个不带@Test annotation的方法,在运行测试的时候这方法不会作为测试方法运行。这至少说明了,junit框架需要通过我们的@Test annotation来识别该方法是否作为测试方法来运行。

    结合我们具体的应用场景来说,我们通常会定义若干个类,里面会有某些希望作为测试方法执行的方法,这些方法的名字并没有特别的限制。所以,要知道某些方法是否为我们需要放到junit框架里执行的我们可以通过反射的方式获取到使用了该annotation的方法。现在我们来模拟一下这个过程。

 

    首先我们要定义一个@Test这样的annotation。我们知道,这个annotation需要被执行框架使用,所以它的存在范围必须是runtime,另外,它只能用来修饰一个方法,表示它可以作为一个运行的测试方法,所以它的范围只能限制在METHOD。这部分的代码可以设计如下:

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {}

    假设我们定义一个添加@Test annotation的代码如下:

public class Foo {
    @Test public static void m1() { }
    public static void m2() { }
    @Test public static void m3() {
        throw new RuntimeException("Boom");
    }
    public static void m4() { }
    @Test public static void m5() { }
    public static void m6() { }
    @Test public static void m7() {
        throw new RuntimeException("Crash");
    }
    public static void m8() { }
}

    这里我们的m1, m3, m5, m7方法都是标注了@Test。

    那么我们执行这些测试方法的代码如下:

import java.lang.reflect.*;

public class RunTests {
   public static void main(String[] args) throws Exception {
      int passed = 0, failed = 0;
      for (Method m : Class.forName(args[0]).getMethods()) {
         if (m.isAnnotationPresent(Test.class)) {
            try {
               m.invoke(null);
               passed++;
            } catch (Throwable ex) {
               System.out.printf("Test %s failed: %s %n", m, ex.getCause());
               failed++;
            }
         }
      }
      System.out.printf("Passed: %d, Failed %d%n", passed, failed);
   }
}

    这部分代码里比较有特色的就是我们通过反射来判断读取的方法是否加注了@Test annotation,如果加了的话则运行该方法。这不就正好符合了我们找到测试方法并运行的期望么?至此,我们就可以猜到junit里面发现和执行测试方法的流程了。我们将Foo类作为参数传入该方法,执行如下的命令:

java RunTests Foo

    得到代码运行的结果如下:

Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom 
Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash 
Passed: 2, Failed 2

    有了我们前面对annotation应用的描述,我们也可以将他们使用的场景扩展到其他应用。比如说AOP,加入我们希望某个方法的执行之前或者之后要执行某个特定的方法。我们就可以给这个方法添加一个特定的标注,比如@ExecuteBefore。在处理这些方法的时候读取到这个标注的就先根据指定的参数去执行那个方法,等执行完毕之后再执行该方法。这样,一个AOP的实现就完成了。关于AOP具体实现的讨论大家可以去参考一些关于spring详细实现的讨论。 

总结

    annotation在程序里相当于提供了一种metadata,通过设置的这些元数据。使用程序的编译和运行环境可以根据需要做一些特定的处理。使用这些程序的代码可以利用反射等机制来读取到这些metadata然后做一些特定的行为。正因为有了这么一种灵活性,我们一些AOP,JUNIT测试方法执行等概念的实现都可以通过这种机制实现。java的annotation和其他语言的特性有极大的相似点。比如在C#中的attribute, Python中的decorator,他们都是为了达到同样的目的。

参考材料

http://www.vogella.com/articles/JavaAnnotations/article.html

http://tutorials.jenkov.com/java-reflection/annotations.html

http://tutorials.jenkov.com/java/annotations.html

 

http://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics