Java注释和处理器将方法标记为只能调用一次且仅一次?

发布于 2024-12-12 03:51:50 字数 620 浏览 0 评论 0原文

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

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

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

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

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

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

天涯沦落人 2024-12-19 03:51:50

不,没有任何现成可用的东西。 AspectJ 似乎是使其以更通用的方式工作的唯一方法。正如 JB Nizet 所指出的 - 注释应该有一个解析器来解析它。

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

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

这样:

  • Foo 是不可变的,这是您的最终目标
  • 它很容易使用。您只需设置您需要的字段。像这样的东西:

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

这样构建器就可以感知 JAXB。例如:

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

JAXB对象需要是可变的,而您的要求是不可变的对象。因此,构建器可以方便地弥补这一点。

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.

凉月流沐 2024-12-19 03:51:50

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

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

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

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.

大姐,你呐 2024-12-19 03:51:50

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

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

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:

审判长 2024-12-19 03:51:50

您可以使用注释来代替。

assert count++ != 0;

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

Instead of using an annotation you can use.

assert count++ != 0;

You would need one counter per method.

好听的两个字的网名 2024-12-19 03:51:50

我有类似的要求。长话短说,当您在 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: [email protected]() 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)

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: [email protected]() 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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文