簡體   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