从RocketMQ对泛型的错误使用引起bug,谈泛型

485 阅读2分钟

相关背景

最近公司新项目用到了RocketMQ,为了更便捷的使用,便对RocketMQ的客户端进行了二次封装,在二次封装的过程中,出现了一个诡异问题,如下

public interface RocketMQListener<T> {  
    void onMessage(T message);  
}
public abstract class EnhanceMessageHandler<T extends BaseMessage> implements RocketMQListener<T>{  
    @Override  
    public final void onMessage(T message) {  
    // 一些其他操作  
    handleMessage(message);  
    // 一些其他操作  
    }  
    abstract void handleMessage(T message);
}
public class BusinessMessageHandler extends EnhanceMessageHandler<BusinessMessage>{  
    @Override  
    void handleMessage(BusinessMessage message) {  
    // 业务处理  
    }  
}

首先,定义了一个抽象类EnhanceMessageHandler继承RocketMQListener, 重写了onMessage方法,在其中进行一些操作,最终消息交给handleMessage方法来处理,该方法由具体的业务Hanndler来实现。

当完成这个功能时,项目启动失败,报错如下:

Caused by: java.lang.RuntimeException: parameterType:T of onMessage method is not supported

我又去rocketmq的github项目,找了一下相关issue,发现从去年6月开始多人遇到这个问题,但是一直未修复,不知道原因为何?

相关issue github.com/apache/rock…

于是,我自己翻看了一下RocketMq相关代码,找到了问题所在,要想弄清楚这个问题,就需要一些泛型的知识,对泛型比较熟的可以忽略本小结,直接看结论。

相关知识

jdk1.5引入了泛型,因此也在jdk中新增了一个接口 java.lang.reflect.Type, 在Class类中增加了三个方法getGenericSuperclass()、getGenericInterfaces()、getTypeParameters()

Type接口

Type是Java编程语言中所有类型的公共超接口。其中包括原始类型、参数化类型、数组类型、类型变量和原始类型。

其派生的子接口有: GenericArrayTypeParameterizedTypeTypeVariable<D>WildcardType

派生的类有: Class

派生的类或接口说明
java.lang.ClassJava 类 API,如 java.lang.String
java.lang.reflect.GenericArrayType泛型数组类型
java.lang.reflect.ParameterizedType泛型参数类型
java.lang.reflect.TypeVariable泛型类型变量,如Collection 中的 E
java.lang.reflect.WildcardType泛型通配类型

相关方法

getGenericSuperclass方法

    // 返回父类的Type类型
    public Type getGenericSuperclass()

getGenericInterfaces方法

    // 返回该类直接实现的接口的Type类型数组
    public Type[] getGenericInterfaces() {}

getTypeParameters方法

    // 返回该类上的泛型声明的TypeVariable数组
    public TypeVariable<Class<T>>[] getTypeParameters(){}

DEMO代码

通过运行下面一段代码,来熟悉一下相关API

public class App {  
    public static void main(String[] args) {  
      BusinessMessageHandler businessMessageHandler = new BusinessMessageHandler();  
        Class clazz = businessMessageHandler.getClass();  

        System.out.println();  
        System.out.println("======================EnhanceMessageHandler============================");  

        ParameterizedType enhanceMessageHandlerType = (ParameterizedType) clazz.getGenericSuperclass();  
        System.out.println("enhanceMessageHandlerType: " + enhanceMessageHandlerType);  

        TypeVariable enhanceMessageHandlerTypeVariable = ((Class) enhanceMessageHandlerType.getRawType()).getTypeParameters()[0];  
        System.out.println("enhanceMessageHandlerTypeVariable: " + enhanceMessageHandlerTypeVariable);  

        Type enhanceMessageHandlerActualTypeArgument = enhanceMessageHandlerType.getActualTypeArguments()[0];  
        System.out.println("enhanceMessageHandlerTypeArgument: " + enhanceMessageHandlerActualTypeArgument);  

        System.out.println();  
        System.out.println("=====================RocketMqListener================================");  

        ParameterizedType rocketMqListenerType = (ParameterizedType) ((Class) enhanceMessageHandlerType.getRawType()).getGenericInterfaces()[0];  
        System.out.println("rocketMqListenerType: " + rocketMqListenerType);  

        TypeVariable rocketMqListenerTypeVariable = ((Class) rocketMqListenerType.getRawType()).getTypeParameters()[0];  
        System.out.println("rocketMqListenerTypeVariable: " + rocketMqListenerTypeVariable);  

        // 获取ParameterizedType中的泛型参数  
        TypeVariable rocketMqListenerTypeArgument = (TypeVariable) rocketMqListenerType.getActualTypeArguments()[0];  
        System.out.println("rocketMqListenerTypeArgument: " + rocketMqListenerTypeArgument);
    }  
}

打印结果

image.png

getTypeParameter和getActualTypeArguments方法的区别是: getTypeParameter返回的是该Class生命的泛型类型,getActualTypeArguments是其该类的子类定义的该泛型的真正类型。

结论

在org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer该类中,使用getMessageType对RocketMQListener和RocketMQReplyListener上通过泛型声明的Message类型进行了解析。

image.png

其逻辑是:

  1. 首先找到RocketMQListener或RocketMQReplyListener接口的Type类型
  2. 调用该Type类型的getActualTypeArguments方法,获取ActualTypeArguments

在我们这个场景中,actualTypeArguments[0]是T,其真正的Class类型在EnhanceMessageHandler的获取ActualTypeArguments中,所以出错。