我真的卷起袖子,试图第一次理解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. } }
唉,我的问题!
我假设我需要定义一个注释处理器;一个Java类,它将指定在遇到@Widgetize(int)注释时要执行的操作,是吗?或者这是否发生在XML配置文件中,这些文件被送入 apt (就像ant读取build.xml文件的方式一样)?
编辑:如果我对上面问题#1中的这些注释处理器是正确的,那么如何“映射”/“注册”/将这些处理器告知apt?< / p>
在构建脚本中,通常在 javac之前运行,以便在编译之前进行基于注释的更改或代码生成? (这是一个最佳实践类型的问题)。
谢谢,我为我的代码示例道歉,他们的结果比我预期的要大得多(!)
答案 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,如果它没有巧妙地完成,将会对性能产生影响。
也可以在编译时处理注释。目标是生成一个类,然后您的代码可以使用该类。处理器必须满足特定的接口,并且必须声明它们才能执行。
请查看以下文章,了解一个结构简单的示例: