問題描述
經常有人問像這樣的 AspectJ 問題,所以我想在以后可以輕松鏈接到的地方回答.
我有這個標記注釋:
package de.scrum_master.app;導入 java.lang.annotation.Inherited;導入 java.lang.annotation.Retention;導入 java.lang.annotation.RetentionPolicy;@遺傳@Retention(RetentionPolicy.RUNTIME)公共@interface 標記{}
現在我像這樣注釋一個接口和/或方法:
package de.scrum_master.app;@Marker公共接口 MyInterface {無效一個();@Marker 無效二();}
這是一個小驅動程序,它也實現了接口:
package de.scrum_master.app;公共類應用程序實現 MyInterface {@覆蓋公共無效一個(){}@覆蓋公共無效二(){}公共靜態無效主要(字符串[]參數){應用程序應用程序=新應用程序();應用程序.one();application.two();}}
現在當我定義這個方面時,我希望它會被觸發
- 對于帶注釋的類的每個構造函數執行和
- 每次執行帶注釋的方法.
package de.scrum_master.aspect;導入 de.scrum_master.app.Marker;公共方面 MarkerAnnotationInterceptor {after() : 執行((@Marker *).new(..)) &&!within(MarkerAnnotationInterceptor) {System.out.println(thisJoinPoint);}after() : 執行(@Marker * *(..)) &&!within(MarkerAnnotationInterceptor) {System.out.println(thisJoinPoint);}}
不幸的是,方面沒有打印任何內容,就像類 Application
和方法 two()
沒有任何 @Marker
注釋一樣.為什么 AspectJ 不攔截它們?
這里的問題不是AspectJ,而是JVM.在 Java 中,注釋在
- 接口,
- 方法或
- 其他注釋
從不被
繼承- 實現類,
- 覆蓋方法或
- 使用帶注解的類.
注解繼承僅適用于類到子類,但前提是超類中使用的注解類型帶有元注解@Inherited
,參見JDK JavaDoc.
AspectJ 是一種 JVM 語言,因此可以在 JVM 的限制范圍內工作.這個問題沒有通用的解決方案,但是對于您希望模擬注釋繼承的特定接口或方法,您可以使用如下解決方法:
package de.scrum_master.aspect;導入 de.scrum_master.app.Marker;導入 de.scrum_master.app.MyInterface;/*** 一個已知的 JVM 限制是注解永遠不會從接口繼承* 實現類或從方法到覆蓋方法,請參見解釋* <a href=>https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Inherited.html>>JDK API</a>.* <p>* 這是一個手動的 AspectJ 小技巧.**/公共方面 MarkerAnnotationInheritor {//實現類應該繼承標記注解聲明 @type: MyInterface+ : @Marker;//覆蓋方法'two'應該繼承標記注解聲明@method : void MyInterface+.two() : @Marker;}
請注意:有了這個方面,您可以從接口和帶注釋的方法中刪除(文字)注釋,因為 AspectJ 的 ITD(類型間定義)機制會將它們添加回接口加上所有實現/覆蓋類/方法.
現在運行 Application
時的控制臺日志顯示:
執行(de.scrum_master.app.Application())執行(無效 de.scrum_master.app.Application.two())
順便說一句,您還可以將方面直接嵌入到界面中,以便將所有內容集中在一個地方.請注意將 MyInterface.java
重命名為 MyInterface.aj
以幫助 AspectJ 編譯器識別它必須在這里做一些工作.
package de.scrum_master.app;公共接口 MyInterface {無效一個();無效二();//由于 https://bugs.eclipse.org/bugs/show_bug.cgi?id=571104,此處不能省略靜態"公共靜態方面 MarkerAnnotationInheritor {//實現類應該繼承標記注解聲明 @type: MyInterface+ : @Marker;//覆蓋方法'two'應該繼承標記注解聲明@method : void MyInterface+.two() : @Marker;}}
2021-02-11 更新: 有人建議對后一種解決方案進行編輯,稱嵌套在接口 MyInterface
內的方面 MarkerAnnotationInheritor
是隱式 public static
,因此方面聲明中的修飾符可以省略.原則上這是正確的,因為默認情況下接口的成員(方法、嵌套類)始終是公共的,并且非靜態內部類定義在接口內部也沒有意義(沒有實例可以綁定它).不過,我喜歡在示例代碼中明確說明,因為并非所有 Java 開發人員都知道這些細節.
此外,如果我們省略 static
,當前版本 1.9.6 中的 AspectJ 編譯器會引發錯誤.我剛剛為這個問題創建了 AspectJ issue #571104.p>
Often people ask AspectJ questions like this one, so I want to answer it in a place I can easily link to later.
I have this marker annotation:
package de.scrum_master.app;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Marker {}
Now I annotate an interface and/or methods like this:
package de.scrum_master.app;
@Marker
public interface MyInterface {
void one();
@Marker void two();
}
Here is a little driver application which also implements the interface:
package de.scrum_master.app;
public class Application implements MyInterface {
@Override
public void one() {}
@Override
public void two() {}
public static void main(String[] args) {
Application application = new Application();
application.one();
application.two();
}
}
Now when I define this aspect, I expect that it gets triggered
- for each constructor execution of an annotated class and
- for each execution of an annotated method.
package de.scrum_master.aspect;
import de.scrum_master.app.Marker;
public aspect MarkerAnnotationInterceptor {
after() : execution((@Marker *).new(..)) && !within(MarkerAnnotationInterceptor) {
System.out.println(thisJoinPoint);
}
after() : execution(@Marker * *(..)) && !within(MarkerAnnotationInterceptor) {
System.out.println(thisJoinPoint);
}
}
Unfortunately the aspect prints nothing, just as if class Application
and method two()
did not have any @Marker
annotation. Why does AspectJ not intercept them?
The problem here is not AspectJ but the JVM. In Java, annotations on
- interfaces,
- methods or
- other annotations
are never inherited by
- implementing classes,
- overriding methods or
- classes using annotated annotations.
Annotation inheritance only works from classes to subclasses, but only if the annotation type used in the superclass bears the meta annotation @Inherited
, see JDK JavaDoc.
AspectJ is a JVM language and thus works within the JVM's limitations. There is no general solution for this problem, but for specific interfaces or methods you wish to emulate annotation inheritance for, you can use a workaround like this:
package de.scrum_master.aspect;
import de.scrum_master.app.Marker;
import de.scrum_master.app.MyInterface;
/**
* It is a known JVM limitation that annotations are never inherited from interface
* to implementing class or from method to overriding method, see explanation in
* <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Inherited.html">JDK API</a>.
* <p>
* Here is a little AspectJ trick which does it manually.
*
*/
public aspect MarkerAnnotationInheritor {
// Implementing classes should inherit marker annotation
declare @type: MyInterface+ : @Marker;
// Overriding methods 'two' should inherit marker annotation
declare @method : void MyInterface+.two() : @Marker;
}
Please note: With this aspect in place, you can remove the (literal) annotations from the interface and from the annotated method because AspectJ's ITD (inter-type definition) mechanics adds them back to the interface plus to all implementing/overriding classes/methods.
Now the console log when running the Application
says:
execution(de.scrum_master.app.Application())
execution(void de.scrum_master.app.Application.two())
By the way, you could also embed the aspect right into the interface so as to have everything in one place. Just be careful to rename MyInterface.java
to MyInterface.aj
in order to help the AspectJ compiler to recognise that it has to do some work here.
package de.scrum_master.app;
public interface MyInterface {
void one();
void two();
// Cannot omit 'static' here due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=571104
public static aspect MarkerAnnotationInheritor {
// Implementing classes should inherit marker annotation
declare @type: MyInterface+ : @Marker;
// Overriding methods 'two' should inherit marker annotation
declare @method : void MyInterface+.two() : @Marker;
}
}
Update 2021-02-11: Someone suggested an edit to the latter solution, saying that the aspect MarkerAnnotationInheritor
nested inside interface MyInterface
is implicitly public static
, so the modifiers in the aspect declaration could be omitted. In principle this is true, because members (methods, nested classes) of interfaces are always public by default and a non-static inner class definition would not make sense inside an interface either (there is no instance to bind it to). I like to be explicit in my sample code, though, because not all Java developers might know these details.
Furthermore, currently the AspectJ compiler in version 1.9.6 throws an error if we omit static
. I have just created AspectJ issue #571104 for this problem.
這篇關于使用 AspectJ 模擬接口和方法的注釋繼承的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!