Java注释和apt(基础)

时间:2011-01-28 15:08:58

标签: java annotations

我真的卷起袖子,试图第一次理解Java注释,并阅读了有关该主题的Sun,Oracle和Wikipedia文章。它们在概念上很容易理解,但我发现将拼图的所有部分放在一起很困难。

以下示例可能是糟糕的工程,但只是幽默我(这是示例!)。

假设我有以下课程:

public Widget
{
    // ...

    public void foo(int cmd)
    {
        switch(cmd)
        {
        case 1:
            function1();
            break;
        case 2:
            function2();
            break;
        case 3:
        default:
            function3();
            break;
        }
    }
}

现在,在我项目的其他地方,我有另一个类 SpaceShuttle ,它有一个名为 blastOff()的方法:

public class SpaceShuttle
{
    // ...

    public void blastOff()
    {
        // ...
    }
}

现在,我想配置一个名为 Widgetize 的注释,以便任何使用 @Widgetize 注释的方法都会在自己之前调用Widget :: foo(int)调用

@interface Widgetize
{
    int cmd() default 2;
}

现在让我们重新审视SpaceShuttle:

public class SpaceShuttle
{
    // ...

    @Widgetize(3)
    public void blastOff()
    {
        // Since we pass a cmd of "3" to @Widgetize,
        // Widget::function3() should be invoked, per
        // Widget::foo()'s definition.
    }
}

唉,我的问题!

  1. 我假设我需要定义一个注释处理器;一个Java类,它将指定在遇到@Widgetize(int)注释时要执行的操作,是吗?或者这是否发生在XML配置文件中,这些文件被送入 apt (就像ant读取build.xml文件的方式一样)?

  2. 编辑:如果我对上面问题#1中的这些注释处理器是正确的,那么如何“映射”/“注册”/将这些处理器告知apt?< / p>

  3. 在构建脚本中,通常在 javac之前运行,以便在编译之前进行基于注释的更改或代码生成? (这是一个最佳实践类型的问题)。

  4. 谢谢,我为我的代码示例道歉,他们的结果比我预期的要大得多(!)

5 个答案:

答案 0 :(得分:4)

这听起来更像是AOP(面向方面​​的编程)而不是注释。由于AOP使用注释来实现它的目标,因此这些主题经常被混淆。我不建议从头开始重新构建AOP,而是建议查找现有的AOP库,例如AspectJ

但是,要回答您的具体问题,有两种方法可以实现您的目标。

运行时方法

这是容器框架(如Spring)通常采用的方法。它的工作方式是,不要自己实例化你的类,而是要求容器提供你的类的实例。

容器具有检查任何RuntimeAnnotations类的逻辑(如@Widgetize)。然后,容器将动态创建类的代理,该代理首先调用正确的Widgetize方法,然后调用目标方法。

然后容器将该代理返回给原始请求者。请求者仍然会得到他要求的类(或接口)并且完全不知道容器添加的代理行为。

这也是AspectJ使用的行为。

增强方法

这是AspectJ采用的方法。说实话,我不知道它是如何工作的很多细节。不知何故,AspectJ将扫描您的类文件(字节代码),找出注释的位置,然后修改字节代码本身以调用代理类而不是实际的类。

这种方法的好处是您不需要使用容器。缺点是您现在必须在编译代码后执行此增强步骤。

答案 1 :(得分:2)

  

我认为我需要的地方   定义注释处理器;一个Java   将指定要执行的操作的类   @Widgetize(int)注释时   遇到了,是吗?或者这是否发生   比方说,得到的XML配置文件   成为apt(就像蚂蚁读的那样   build.xml文件)?

在Java 1.6中,定义注释的标准方法是通过ServiceLoader SPI处理它。

  

在构建文件中,通常会运行   在javac之前,所以基于注释   更改或代码生成发生   在编译之前? (这是最好的   实践型问题)。

APT必须在编译之前进行,因为它在源文件上运行(实际上在语法树上)。

答案 2 :(得分:1)

我经常使用Hibernate的方法拦截器。 Hibernate要求在每个查询中启动并提交事务。我没有拥有大量重复的代码,而是拦截每个Hibernate方法并在拦截器中启动事务。

我将AOP联盟方法拦截器与Google Guice结合使用。使用这些你使用你的Widgetise注释,然后使用Guice来说明你在哪里看到这个注释使用这个方法拦截器。以下Guice片段就是这样做的。

        bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), new TransactionalInterceptor);

拦截器捕获该方法,然后可以调用foo然后告诉方法拦截器继续调用原始方法。例如(以简化形式):

public class Interceptor implements MethodInterceptor {

    //PUT ANY DEPENDENCIES HERE IN A CONSTRUCTOR

    public Object invoke(MethodInvocation invocation) throws Throwable {

            //DO FOO HERE

            result = invocation.proceed();
        return result;
    }

}

这可能有点混乱,我需要一段时间才能理解它,但是一旦理解它就很简单。

答案 3 :(得分:0)

似乎您混淆的基础是错误的信念,即注释不仅仅是元数据。从JSE 1.5语言指南中查看此page。这是一个相关的片段:

  

注释不会直接影响   程序语义,但它们确实会影响   程序由工具处理的方式   和图书馆,可以反过来   影响运行的语义   程序。可以从中读取注释   源文件,类文件或   在运行时反思。

在您的代码中

    @Widgetize(3)
    public void blastOff()

不会导致Widget::function3()执行(请注意:在java中我们将成员引用为Widget.function3())注释只相当于机器可读的注释(元数据)。

答案 4 :(得分:0)

可以在编译时和运行时处理注释。要在运行时处理它们,需要使用反射API,如果它没有巧妙地完成,将会对性能产生影响。

也可以在编译时处理注释。目标是生成一个类,然后您的代码可以使用该类。处理器必须满足特定的接口,并且必须声明它们才能执行。

请查看以下文章,了解一个结构简单的示例:

Annotation processing 101