使用 AspectJ 模拟接口和方法的注解继承 [英] Emulate annotation inheritance for interfaces and methods with AspectJ

查看:30
本文介绍了使用 AspectJ 模拟接口和方法的注解继承的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

经常有人问这样的 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;@标记公共接口 MyInterface {空一个();@Marker void two();}

这是一个也实现了接口的小驱动程序应用程序:

package de.scrum_master.app;公共类应用程序实现 MyInterface {@覆盖public void one() {}@覆盖公共无效二(){}公共静态无效主(字符串 [] args){申请申请=新申请();application.one();application.two();}}

现在当我定义这个方面时,我希望它被触发

  • 对于带注释的类的每个构造函数的执行和
  • 每次执行一个带注释的方法.

package de.scrum_master.aspect;导入 de.scrum_master.app.Marker;公共方面 MarkerAnnotationInterceptor {after() : execution((@Marker *).new(..)) &&!within(MarkerAnnotationInterceptor) {System.out.println(thisJoinPoint);}after() : execution(@Marker * *(..)) &&!within(MarkerAnnotationInterceptor) {System.out.println(thisJoinPoint);}}

不幸的是,aspect 什么也没打印,就好像 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,这里不能省略 'static'公共静态方面 MarkerAnnotationInheritor {//实现类应该继承标记注解声明 @type: MyInterface+ : @Marker;//重写方法 'two' 应该继承标记注释声明@method : void MyInterface+.two() : @Marker;}}

更新 2021-02-11: 有人建议修改后一个解决方案,说嵌套在接口 MyInterface 中的方面 MarkerAnnotationInheritor 是隐式public static,因此可以省略方面声明中的修饰符.虽然原则上这是正确的,因为接口的成员(方法、嵌套类)在默认情况下始终是公共的,并且非静态内部类定义在接口内部也没有意义(没有将其绑定到的实例).不过,我喜欢在我的示例代码中明确说明,因为可能并非所有 Java 开发人员都知道这些细节.

此外,如果我们省略 static,目前 1.9.6 版的 AspectJ 编译器会抛出错误.我刚刚针对这个问题创建了 AspectJ 问题 #571104.>

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. While 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 these details might not be know to all Java developers.

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 模拟接口和方法的注解继承的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆