简体   繁体   English

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

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

I need to be able to mark methods so that they throw a RuntimeException if they are called more than once.我需要能够标记方法,以便在它们被多次调用时抛出 RuntimeException。

I am trying to enforce some single assignment semantics and the number of parameters to my class is too large to put in a single constructor and I need to be able to make these classes JAXB aware as well, so the objects need to be mutable but I want to enforce single assignment semantics.我试图强制执行一些单一的赋值语义,并且我的类的参数数量太大而无法放入单个构造函数中,并且我需要能够让这些类也知道JAXB ,因此对象需要是可变的,但我想要强制执行单一赋值语义。

I am pretty sure I can do this with Aspects, but I would really like to be able to use my own Annotations processor instead.我很确定我可以用 Aspects 做到这一点,但我真的希望能够使用我自己的 Annotations 处理器。

I know how to do this with Decorators in Python.我知道如何使用 Python 中的装饰器来做到这一点。

How do I write an Annotation processor that can intercept calls to the annotated method at runtime and not just at compile time?如何编写一个注释处理器,它可以在运行时拦截对注释方法的调用,而不仅仅是在编译时?

I think I am on to something with with Dynamic Proxies intercepting the method calls, I just need to figure out how to integrate them with my Annotation processor.我想我正在使用动态代理拦截方法调用,我只需要弄清楚如何将它们与我的注释处理器集成。

Dynamic Proxies require you to use an Interface, that is way to cumbersome, I have a CGLib MethodInterceptor working now, much less requirements on what gets intercepted and decorated, at the expense of adding a dependency.动态代理要求您使用接口,这很麻烦,我现在有一个CGLib MethodInterceptor工作,对拦截和装饰的要求要少得多,代价是添加依赖项。

Nope, there's nothing ready-to-use.不,没有什么可以立即使用的。 And AspectJ seems the only way to make it work in a more general manner.而 AspectJ 似乎是让它以更通用的方式工作的唯一方法。 As JB Nizet noted - the annotation should have a parser to parse it.正如 JB Nizet 所指出的 - 注释应该有一个解析器来解析它。

However, I would advise for a better and simpler solution - the Builder pattern.但是,我会建议一个更好、更简单的解决方案 - Builder 模式。 What does it look like:它是什么样子的:

  • you have a FooBuilder (it may also be a static inner class) which is mutable and has a setter and getter for each of the fields你有一个FooBuilder (它也可能是一个静态内部类),它是可变的,每个字段都有一个 setter 和 getter
  • FooBuilder has a build() method that returns an instance of Foo FooBuilder有一个build()方法,它返回一个Foo的实例
  • Foo has a constructor that takes only FooBuilder , and you assign each field there. Foo有一个只FooBuilder的构造函数,你可以在那里分配每个字段。

That way:那样:

  • Foo is immutable, which is your end goal Foo是不可变的,这是你的最终目标
  • It is easy to use.它很容易使用。 You only set the fields that you need.您只需设置您需要的字段。 Something like:就像是:

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

That way the builder can be JAXB-aware.这样,构建器就可以识别 JAXB。 For example:例如:

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

JAXB objects need to be mutable, and your requirement is an immutable object. JAXB 对象需要是可变的,而您的要求是一个不可变的对象。 Hence the builder comes handy to bridge that.因此,构建器可以方便地解决这个问题。

This question shows some resemblance with question Applying CGLib Proxy from a Annotation Processor .这个问题与问题Applying CGLib Proxy from a Annotation Processor有一些相似之处。

If you want to be able to change the behavior of the original source code in an annotation processor have a look at how http://projectlombok.org/ achieves this.如果您希望能够在注释处理器中更改原始源代码的行为,请查看http://projectlombok.org/如何实现这一点。 The only downside IMO is that lombok relies on com.sun.* classes. IMO 唯一的缺点是 lombok 依赖于 com.sun.* 类。

Since I need this kind of stuff myself I wonder if someone knows of a better way to achieve this, still using annotation processors.由于我自己需要这种东西,我想知道是否有人知道更好的方法来实现这一点,仍然使用注释处理器。

You can configure JAXB to use field (instance variable) access using @XmlAccessorType(XmlAccessType.FIELD) .您可以使用@XmlAccessorType(XmlAccessType.FIELD)将 JAXB 配置为使用字段(实例变量)访问。 This will allow you to do what you need to with the set method:这将允许您使用 set 方法执行所需的操作:

You can also use JAXB's XmlAdapter mechanism to support immutable objects:您还可以使用 JAXB 的XmlAdapter机制来支持不可变对象:

I had a similar requirement.我也有类似的需求。 Long story short when you inject components in Spring the cyclic dependency situation like A depends on B and B depends on A is perfectly fine, but you need to inject these components as fields or setters.长话短说,当你在 Spring 中注入组件时,像 A 依赖 B 和 B 依赖 A 这样的循环依赖情况非常好,但是你需要将这些组件作为字段或设置器注入。 Constructor injection causes a stack overflow.构造函数注入导致堆栈溢出。 Therefore I had to introduce a method init() for these components, which unlike constructors might be erroneously called more than once.因此,我不得不为这些组件引入一个方法init() ,与构造函数不同,它可能会被错误地调用多次。 Needless to say boilerplate code like:毋庸置疑,样板代码如下:

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

started to emerge everywhere.开始到处出现。 Since this is nowhere close to being a critical spot of the application, I made a decision to introduce an elegant thread-safe one-liner solution favoring conciseness over speed:由于这远不是应用程序的关键点,我决定引入一个优雅的线程安全的单行解决方案,它更倾向于简洁而不是速度:

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));
    }
  }
}

Now, since I prefer building applications within DI frameworks it might sound natural to declare Guard as a component, then inject it, and call an instance method requireCalledOnce instead.现在,因为我更喜欢在 DI 框架内构建应用程序,所以将Guard声明为组件,然后注入它,并调用实例方法requireCalledOnce听起来很自然。 But due to its universal flavor, static reference yields more sense.但是由于它的通用性,静态引用产生了更多的意义。 Now my code looks like this:现在我的代码如下所示:

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

and here is an exception upon the second invocation of init of the same object:这是第二次调用同一对象的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)

Instead of using an annotation you can use.您可以使用而不是使用注释。

assert count++ != 0;

You would need one counter per method.每个方法需要一个计数器。

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

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