简体   繁体   中英

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.

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.

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.

I know how to do this with Decorators in 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.

Nope, there's nothing ready-to-use. And AspectJ seems the only way to make it work in a more general manner. As JB Nizet noted - the annotation should have a parser to parse it.

However, I would advise for a better and simpler solution - the Builder pattern. 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 has a build() method that returns an instance of Foo
  • Foo has a constructor that takes only FooBuilder , and you assign each field there.

That way:

  • Foo is immutable, which is your end goal
  • 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. 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. Hence the builder comes handy to bridge that.

This question shows some resemblance with question 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. The only downside IMO is that lombok relies on com.sun.* classes.

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) . This will allow you to do what you need to with the set method:

You can also use JAXB's XmlAdapter mechanism to support immutable objects:

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. 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. 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. 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:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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