如何使用AspectJ向自定义注释的类添加字段 [英] How to add a field to a custom-annotated class using AspectJ

查看:122
本文介绍了如何使用AspectJ向自定义注释的类添加字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们要使用Aspectj将字段添加到某些特定的类中

To add a field to some specific class with aspectj we do

package com.test;

public class MyClass {
    private String myField;
}

public aspect MyAspect
{
    private String MyClass.myHiddenField;
}

我们如何在使用自定义注释进行注释的类中添加字段?

How do we add a field to a class that is annotated with some custom annotation?

示例用法:如果用@CustomLoggable注释类,请添加Logger字段和一些方法.

example usage : if class is annotated with @CustomLoggable add a Logger field and some methods.

如果方法具有@ReadLocked批注,则类将具有ReentrantReadWriteLock字段并注入适当的逻辑,依此类推.

if method has the @ReadLocked annotation then class will have a ReentrantReadWriteLock field and the appropriate logic injected, etc.

推荐答案

实际上,您无法在注释类型上进行类型间声明(ITD),即,您需要知道具体的类名才能声明静态或非静态成员.或直接使用方法.

Actually you cannot make inter-type declarations (ITD) on annotation types, i.e. you need to know concrete class names in order to declare static or non-static members or methods directly.

通常的解决方法是:

  • 使用所需的所有方法创建一个接口.
  • 为每种接口方法提供实现.
  • 使每种带注释的类型都通过ITD实现接口.

现在,如果您还想将静态成员(例如记录器)添加到所有带注释的类型,那么再次,如果您不知道确切的类名,则需要使用变通方法:

Now if you also want to add a static member such as a logger to all annotated types, again if you do not know the exact class names you need to use a workaround:

  • 创建一个包含所需成员的方面.在此示例中,我们将其称为LoggerHolder.
  • 确保为每个目标类创建一个方面实例,而不是默认的单例方面实例.这是通过pertypewithin完成的.
  • 为了避免运行时异常,您必须不必直接通过Logger logger = ...初始化成员,而是需要懒惰地进行初始化,直到目标类型的静态初始化阶段完成.
  • 您还需要在方面提供类似LoggerHolder.getLogger()的访问器方法,并在必要时调用它.
  • 为了向最终用户隐藏所有丑陋的方面,我建议向上述ITD接口添加另一个访问器方法LoggableAspect.getLogger()(为方便起见,使用相同的方法名),并提供一种方法实现,以从中提取成员引用通过LoggerHolder.aspectOf(this.getClass()).getLogger()的长宽比实例.
  • Create an aspect holding the desired member(s). Let's call it LoggerHolder in this example.
  • Make sure that one aspect instance per target class is created instead of the default singleton aspect instance. This is done via pertypewithin.
  • In order to avoid runtime exceptions you must not initialise the members directly via Logger logger = ... but need to do it lazily, waiting until after the target type's static initialisation phase is finished.
  • You also need to provide an accessor method like LoggerHolder.getLogger() in the aspect and call it whenever necessary.
  • In order to hide all the ugly aspect stuff from the end user I recommend to add yet another accessor method LoggableAspect.getLogger() (same method name for convenience) to the ITD interface mentioned above and provide a method implementation extracting the member reference from the aspect instance via LoggerHolder.aspectOf(this.getClass()).getLogger().

注意:我在这里一次使用两个概念,将它们混合在一个应用程序中,因为您要求将静态成员和非静态方法都添加到带注释的类中:

Attention: I am using two concepts at once here, mixing them in one application because you asked for both static members and non-static methods added to annotated classes:

  • 通过ITD将帮助程序界面+实现添加到您的核心代码中
  • 持有人方面通过pertypewithin声明成员并与目标类相关联,以模拟静态成员
  • Helper interface + implementation added to your core code via ITD
  • Holder aspect declaring member(s) and associated with target classes via pertypewithin in order to emulate static members

现在这是一些示例代码:

Now here is some sample code:

注释:

package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface CustomLoggable {}

两个类,一个带有注释,一个不带有注释:

package de.scrum_master.app;

public class OrdinaryClass {
    public void doSomething() {
        System.out.println("Logging some action directly to console");
    }
}

package de.scrum_master.app;

import java.util.logging.Level;

@CustomLoggable
public class AnnotatedClass {
    public void doSomething() {
        getLogger().log(Level.INFO, "Logging some action via ITD logger");
        getLogger().log(Level.INFO, someOtherMethod(11));
    }
}

如您所见,第二个类使用尚未在类中直接声明的两个方法:getLogger()someOtherMethod(int).它们都将在下面的ITD中声明,前者提供对伪静态成员的访问,后者只是您要在每个带注释的类上声明的另一种方法.

As you can see, the second class uses two methods which have not been declared directly within the class: getLogger() and someOtherMethod(int). Both of them will be declared via ITD further below, the former providing access to the pseudo-static member and the latter being just another method you want declared on each annotated class.

持有伪静态成员实例的方面:

package de.scrum_master.aspect;

import java.util.logging.Logger;
import de.scrum_master.app.CustomLoggable;

public aspect LoggerHolder
    pertypewithin(@CustomLoggable *)
{
    private Logger logger;

    after() : staticinitialization(*) {
        logger = Logger.getLogger(getWithinTypeName());
    }

    public Logger getLogger() {
        return logger;
    }
}

正如我之前所说,请注意pertypewithinstaticinitialization的用法.另一方便的事情是使用方面的getWithinTypeName()方法以获得用于命名记录器的目标类名称.

As I said earlier, please note the usage of pertypewithin and staticinitialization. Another convenient thing is to use the aspect's getWithinTypeName() method in order to get the target class name for naming the logger.

声明接口+实现并将其应用于所有目标类型:

package de.scrum_master.aspect;

import java.util.logging.Logger;
import de.scrum_master.app.CustomLoggable;

public aspect LoggableAspect {
    public static interface Loggable {
        Logger getLogger();
        String someOtherMethod(int number);
    }

    declare parents : (@CustomLoggable *) implements Loggable;

    public Logger Loggable.getLogger() {
        return LoggerHolder.aspectOf(this.getClass()).getLogger();
    }

    public String Loggable.someOtherMethod(int number) {
        return ((Integer) number).toString();
    }
}

为简单起见,我只是在方面内将接口声明为静态嵌套类型.您也可以单独声明该接口,但是在这里可以看到它的上下文,对我而言,这是更可取的.

For simplicity, I just declared the interface as a static nested type within the aspect. You can also declare the interface separately, but here you see it in its context which for me is preferable.

这里的关键是declare parents语句,该语句使每个目标类都实现该接口.最后的两个方法实现显示了如何提供常规"方法实现以及如何通过aspectOf从持有者方面访问记录器.

The key thing here is the declare parents statement making each target class implement the interface. The two method implementations at the end show how to provide "normal" method implementations as well as how to access the logger from the holder aspect via aspectOf.

具有入口点的驱动程序类:

最后但并非最不重要的一点是,我们要运行代码,看看它是否满足我们的要求.

Last, but not least, we want to run the code and see if it does what we want.

package de.scrum_master.app;

public class Application {
    public static void main(String[] args) {
        new OrdinaryClass().doSomething();
        new AnnotatedClass().doSomething();
    }
}

控制台输出:

Logging some action directly to console
Mrz 15, 2015 11:46:12 AM de.scrum_master.app.AnnotatedClass doSomething
Information: Logging some action via ITD logger
Mrz 15, 2015 11:46:12 AM de.scrum_master.app.AnnotatedClass doSomething
Information: 11

Voilà!记录工作正常,记录器有一个很好的名称de.scrum_master.app.AnnotatedClass,并且调用这两个接口方法均按预期工作.

Voilà! Logging works, the Logger has a nice name de.scrum_master.app.AnnotatedClass and calling the two interface methods works as expected.

替代方法:

由于AspectJ 1.8.2 注释处理受支持,另请参见此博客文章. IE.您可以使用APT来为每个带注释的类型生成一个方面,并直接引入静态成员和其他方法,而无需任何技巧,例如,每个类型的实例化,持有人方面实例和接口中的访问器方法成员.这是以额外的构建步骤为代价的,但是我认为这将是解决问题的非常简洁明了的方法.让我知道您是否在理解示例方面有困难,需要更多帮助.

Since AspectJ 1.8.2 annotation processing is supported, see also this blog post. I.e. you could use APT in order to generate one aspect per annotated type and introduce static members and additional methods directly without any tricks such as per-type instantiation, accessor methods members within holder aspect instances and interfaces. This comes at the cost of an additional build step, but I think it would be a very neat and straightforward way to solve your problem. Let me know if you have any difficulty understanding the examples and need more help.

这篇关于如何使用AspectJ向自定义注释的类添加字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆