[英]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 和 getterFooBuilder
有一個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.