5

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

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

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

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

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

我想我正在使用动态代理拦截方法调用,我只需要弄清楚如何将它们与我的注释处理器集成。

动态代理要求您使用接口,这很麻烦,我现在有一个CGLib MethodInterceptor工作,对拦截和装饰的要求要少得多,但会增加依赖项。

4

5 回答 5

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 对象需要是可变的,而您的要求是不可变对象。因此,构建器可以方便地弥补这一点。

于 2011-10-28T10:14:33.933 回答
2

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

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

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

于 2012-04-25T06:45:57.603 回答
1

您可以将 JAXB 配置为使用@XmlAccessorType(XmlAccessType.FIELD). 这将允许您使用 set 方法执行所需的操作:

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

于 2011-10-28T10:38:34.107 回答
0

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

assert count++ != 0;

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

于 2011-10-28T10:17:56.433 回答
0

我有类似的要求。长话短说,当您在 Spring 中注入组件时,像 A 依赖 B 和 B 依赖 A 这样的循环依赖情况非常好,但是您需要将这些组件作为字段或设置器注入。构造函数注入导致堆栈溢出。因此,我不得不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));
    }
  }
}

Guard现在,由于我更喜欢​​在 DI 框架中构建应用程序,因此将其声明为组件,然后注入它,然后调用实例方法可能听起来很自然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)
于 2020-07-23T22:38:12.237 回答