【问题标题】:Advice inherited method from generic abstract class in concrete non-generic class建议从具体非泛型类中的泛型抽象类继承方法
【发布时间】:2019-01-22 19:26:32
【问题描述】:

例如我有如下界面

public interface Converter<I, O> {
    public O convert(I input);
}

实现该接口的抽象类

public abstract class AbstractIntegerConverter<T> implements Converter<T, Integer> {

    public Integer convert(T input) {
        // convert anything to int
    }

    public abstract Integer defaultValue();
}

以及具体实现

@Component
public class StringToIntegerConverter extends AbstractIntegerConverter<String> {

    @Override
    public Integer defaultValue() {
        // implementation
    }
}

我想建议将String 转换为任何东西的所有方法。我创建了以下方面

@Aspect
@Component
public class ConverterAspect {

    @Around("execution(* *..*.Converter+.convert(String))")
    public Object adviceStringConverter(ProceedingJoinPoint joinPoint) throws Throwable {
        // logic
    }
}

这不起作用。 Spring 不会为 StringToIntegerConverter 类创建代理。 但是,如果我从抽象类重写 convert(String input) 方法,它就会开始工作,Spring 会成功为 StringToIntegerConverter 创建代理并执行所有需要的逻辑。

@Component
public class StringToIntegerConverter extends AbstractIntegerConverter<String> {

    @Override
    public Integer convert(String input) {
        return super.convert(input);
    }

    @Override
    public Integer defaultValue() {
        // implementation
    }
}

为什么会这样?有什么方法可以定义切入点,这样我就不需要重写convert(String input) 方法了吗?

【问题讨论】:

    标签: java spring aop spring-aop


    【解决方案1】:

    AbstractIntegerConverter 中,方法签名不是public Integer convert(String input) 而是public Integer convert(T input),因此您的切入点* *..*.Converter+.convert(String) 不匹配。改用Object* 并通过args() 验证运行时类型,而不是使用编译时签名:

    @Around("execution(* *..Converter+.convert(*)) && args(input)")
    public Object adviceStringConverter(ProceedingJoinPoint joinPoint, String input) throws Throwable {
        System.out.println(joinPoint);
        return joinPoint.proceed();
    }
    

    使用我添加的日志行,您将在控制台上看到如下内容:

    execution(Integer de.scrum_master.app.AbstractIntegerConverter.convert(Object))
    

    看到了吗? convert(Object),不是convert(String),因此不匹配。

    P.S.:这是一个有趣的问题,不像大多数 AspectJ 或 Spring AOP 那样无聊。所以谢谢你。 :-)


    更新:

    至于您的后续问题,这是javap -s 打印的内容(方面已停用):

    javap -s AbstractIntegerConverter.class
    
    Compiled from "AbstractIntegerConverter.java"
    public abstract class de.scrum_master.app.AbstractIntegerConverter<T> implements de.scrum_master.app.Converter<T, java.lang.Integer> {
      public de.scrum_master.app.AbstractIntegerConverter();
        descriptor: ()V
    
      public java.lang.Integer convert(T);
        descriptor: (Ljava/lang/Object;)Ljava/lang/Integer;
    
      public abstract java.lang.Integer defaultValue();
        descriptor: ()Ljava/lang/Integer;
    
      public java.lang.Object convert(java.lang.Object);
        descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    }
    

    看到了吗?签名是convert(Object),我的方面的日志输出也显示。

    javap -s StringToIntegerConverter.class
    
    Compiled from "StringToIntegerConverter.java"
    public class de.scrum_master.app.StringToIntegerConverter extends de.scrum_master.app.AbstractIntegerConverter<java.lang.String> {
      public de.scrum_master.app.StringToIntegerConverter();
        descriptor: ()V
    
      public java.lang.Integer defaultValue();
        descriptor: ()Ljava/lang/Integer;
    }
    

    具体转换器类中没有convert(String)convert(whatever) 方法。

    你现在相信我了吗?

    【讨论】:

    • 感谢您的回答。这是我目前正在使用的解决方案,但我对它不太满意。如果StringToIntegerConverter 扩展AbstractIntegerConverter&lt;String&gt;,那么它确实具有从AbstractIntegerConverter&lt;String&gt; 继承的Integer convert(String input) 方法。不是吗?那么为什么方面看不到呢?我想这与类型擦除有关,但 StringToIntegerConverter 是一个具体的非泛型类,所以这种行为对我来说真的很奇怪。
    • 不,它没有。我不知道您认为继承是如何工作的,但不是通过将字节码复制到继承的类中。该方法仍然在抽象类中定义,因此是通用的,即方法签名实际上包含Object,而不是String,因为您可以使用javap -c 轻松检查。因此,我的解决方案不是您应该不满意的肮脏解决方法,而是正确的解决方案,因为您以这种方式编写类。我方面的日志输出也证明了这一点:AbstractIntegerConverter.convert(Object).
    • 为了您的方便,我在答案中添加了javap -s 输出(不是那么冗长),希望您现在可以接受。
    • 我不知道继承在底层是如何工作的,但我确信当我们扩展一个泛型类时,我们指定的类型参数会以某种方式保存。然后我们可以在运行时从具体继承者那里获取泛型类型参数,如Super Type Tokens 中所述。所以我认为有一种方法可以告诉 AspectJ 也利用这些信息。
    • 这里是generics in AspectJ的使用说明,供大家参考。 AspectJ 检查字节码,您要匹配的方法的字节码具有通用参数类型,因此适用类型擦除。这也解释了为什么直接在具体子类中定义方法会起作用,因为在那里你给它一个特定的参数类型。
    猜你喜欢
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 1970-01-01
    • 2012-03-01
    • 1970-01-01
    • 2023-04-03
    • 2021-07-05
    • 1970-01-01
    相关资源
    最近更新 更多