繁体   English   中英

Java注解和处理器来标记一个方法,以便它只能被调用一次?

[英]Java Annotation and Processor to mark a method as so it can be called once and only once?

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

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

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

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

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

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

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

不,没有什么可以立即使用的。 而 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 对象需要是可变的,而您的要求是一个不可变的对象。 因此,构建器可以方便地解决这个问题。

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

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

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

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

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

我也有类似的需求。 长话短说,当你在 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));
    }
  }
}

现在,因为我更喜欢在 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)

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

assert count++ != 0;

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM