反射事件总线需避免泛型方法,改用泛型接口以准确获取事件类型;注册时解析参数类型建立class到监听器的索引,post时精准匹配;缓存method需提前setaccessible,target建议用weakreference。

反射实现的事件总线,只要类型擦除没搞错、泛型边界写对、回调执行时没丢参数,就能支持动态参数回调;否则大概率在 invoke 那步抛 IllegalArgumentException 或静默失败。
Java 反射调用带泛型参数的方法时,为什么 Method.invoke 总报错?
根本原因是 Java 泛型擦除后,运行时方法签名里的参数类型是 Object,但你传进去的是具体类型(比如 String 或自定义类),而反射又严格校验参数类型是否匹配声明类型。尤其当监听器方法形参是泛型(如 <t> void onEvent(T event)</t>)时,反射拿不到真实 T,实际查到的是 Object,但你传 new User() 过去就可能不认。
- 实操建议:监听器接口方法**不要用泛型方法**,改用泛型接口(如
EventListener<user></user>),这样反射能通过getGenericInterfaces()提取实际类型参数 - 检查
method.getParameterTypes()返回的是否全是Object.class—— 是的话,说明泛型被擦除干净,必须靠接口层级补信息 - 别依赖
method.getGenericParameterTypes()直接转成运行时 Class,它返回的是Type,得用ParameterizedType解包才能拿到真正类型
如何让 register 和 post 支持任意参数数量和类型?
不能靠反射硬“猜”参数个数或类型,得把参数绑定逻辑前移到注册阶段:每个监听器方法在注册时,就解析出它期望接收的事件类型(从方法参数唯一确定),后续 post 时只往匹配类型的监听器发。
- 注册时用
method.getParameterTypes()[0]拿第一个参数类型作为事件类型(强制约定监听器方法只收一个事件对象) - 用
ConcurrentHashMap<class>, List<listenerwrapper>></listenerwrapper></class>做事件类型到监听器列表的索引,避免每次post都遍历所有监听器 - 如果真要支持多参数(如
onEvent(User u, long ts, String action)),那就放弃通用总线定位,改用注解 + AOP 或直接上Function<object void></object>手动拆包 —— 反射扛不住这种自由度
ListenerWrapper 里缓存 Method 和 target 有什么坑?
缓存本身没问题,但容易忽略两点:一是 Method.setAccessible(true) 必须在缓存前调用,否则私有方法调用直接 IllegalAccessException;二是 target 如果是局部对象或已 GC 的弱引用,invoke 会抛 NullPointerException 而不是提示“监听器已销毁”。
- 包装器构造时立即调用
method.setAccessible(true),别等到invoke时再试 - 缓存
target时,如果是 Activity/Fragment 等易销毁组件,用WeakReference包一层,调用前先ref.get() != null判空 - 别缓存
method.getParameterCount()这种可实时获取的值 —— 没必要,还可能因热修复或代理导致不一致
最麻烦的从来不是怎么调用,而是怎么安全地把「事件类型」从注册时的字节码信息,准确传递到 post 时的类型匹配逻辑里。漏掉 ParameterizedType 的一层强转,或者把 Class> 当 Type 用,后面全白搭。










