Java Annotation和Processor将方法标记为只调用一次?

时间:2011-10-28 09:57:38

标签: java annotations jaxb dynamic-proxy cglib

我需要能够标记方法,以便在多次调用它们时抛出RuntimeException。

我正在尝试强制执行一些单一的赋值语义,并且我的类的参数数量太大而无法放入单个构造函数中,我需要能够使这些类JAXB也知道,所以对象需要是可变的,但我想强制执行单一赋值语义。

我很确定我可以用Aspects做到这一点,但我真的希望能够使用我自己的Annotations处理器。

我知道如何使用Python中的装饰器来做到这一点。

如何编写一个Annotation处理器,它可以在运行时拦截对带注释方法的调用,而不仅仅是在编译时?

我认为我正在使用Dynamic Proxies拦截方法调用,我只需要弄清楚如何将它们与我的Annotation处理器集成。

动态代理要求您使用界面,这是繁琐的方式,我现在有CGLib MethodInterceptor工作,对截获和装饰的内容要求更少,但代价是添加了依赖性。

5 个答案:

答案 0 :(得分:3)

不,没有什么可以随时使用的。 AspectJ似乎是让它以更一般的方式工作的唯一方法。正如JB Nizet所指出的那样 - 注释应该有一个解析器来解析它。

但是,我会建议一个更好,更简单的解决方案 - Builder模式。它看起来像什么:

  • 你有一个FooBuilder(它也可能是一个静态的内部类),它是可变的,每个字段都有一个setter和getter
  • FooBuilder有一个build()方法,可返回Foo
  • 的实例
  • Foo有一个只占FooBuilder的构造函数,并在那里分配每个字段。

那样:

  • Foo是不可变的,这是您的最终目标
  • 易于使用。您只需设置所需的字段。类似的东西:

    Foo foo = new Foo.FooBuilder().setBar(..).setBaz(..).build();
    

这样构建器可以识别JAXB。例如:

FooBuilder builder = (FooBuilder) unmarshaller.unmarshal(stream);
Foo foo = builder.build();

JAXB对象需要是可变的,并且您的需求是一个不可变对象。因此,构建器可以很方便地构建它。

答案 1 :(得分:2)

这个问题与问题Applying CGLib Proxy from a Annotation Processor有些相似。

如果您希望能够在注释处理器中更改原始源代码的行为,请查看http://projectlombok.org/如何实现此目的。 IMO唯一的缺点是lombok依赖于com.sun。*类。

由于我自己需要这种东西,我想知道是否有人知道更好的方法来实现这一点,仍然使用注释处理器。

答案 2 :(得分:1)

您可以使用@XmlAccessorType(XmlAccessType.FIELD)将JAXB配置为使用字段(实例变量)访问。这将允许您使用set方法执行所需操作:

您还可以使用JAXB的XmlAdapter机制来支持不可变对象:

答案 3 :(得分:0)

您可以使用。而不是使用注释。

assert count++ != 0;

每种方法需要一个计数器。

答案 4 :(得分:0)

我有类似的要求。长话短说,当您在Spring中注入组件时,像A依赖于B以及B依赖于A的循环依赖关系情况就很好,但是您需要将这些组件作为字段或setter注入。构造函数注入会导致堆栈溢出。因此,我不得不为这些组件引入方法init(),与构造函数不同的是,它们可能被错误多次调用。不用说样板代码,例如:

private volatile boolean wasInit = false;
public void init() {
  if (wasInit) {
    throw new IllegalStateException("Method has already been called");
  }
  wasInit = true;
  logger.fine("ENTRY");
  ...
}

开始无处不在。由于这几乎不是应用程序的关键点,因此我决定引入一种优雅的线程安全的单线解决方案,该方法倾向于简洁而不是速度:

public class Guard {
  private static final Map<String, Object> callersByMethods = new ConcurrentHashMap<String, Object>();
  
  public static void requireCalledOnce(Object source) {
    StackTraceElement[] stackTrace = new Throwable().getStackTrace();
    String fullClassName = stackTrace[1].getClassName();
    String methodName = stackTrace[1].getMethodName();
    int lineNumber = stackTrace[1].getLineNumber();
    int hashCode = source.hashCode();
    // Builds a key using full class name, method name and line number
    String key = new StringBuilder().append(fullClassName).append(' ').append(methodName).append(' ').append(lineNumber).toString();
    System.out.println(key);

    if (callersByMethods.put(key, source) != null) {
      throw new IllegalStateException(String.format("%s@%d.%s() was called the second time.", fullClassName, hashCode, methodName));
    }
  }
}

现在,由于我更喜欢​​在DI框架内构建应用程序,因此将Guard声明为组件,然后注入它,然后调用实例方法requireCalledOnce听起来很自然。但是由于其通用的风味,静态引用产生了更多的意义。现在我的代码如下:

private void init() {
  Guard.requireCalledOnce(this);
  ...
}

这是对同一对象的init的第二次调用的例外:

Exception in thread "main" java.lang.IllegalStateException: my.package.MyComponent@4121506.init() was called the second time.
    at my.package.Guard.requireCalledOnce(Guard.java:20)
    at my.package.MyComponent.init(MyComponent.java:232)
    at my.package.MyComponent.launch(MyComponent.java:238)
    at my.package.MyComponent.main(MyComponent.java:48)