编译时能否自动检测指定包类并为泛型生成对应类型?
你的需求完全可以实现!因为你提到的所有必要信息(指定包下的类、泛型类型)在编译时都是明确的,我们可以借助**注解处理器(Annotation Processing)**或者构建阶段的代码生成工具,自动为包下的每个类生成对应的Callback<X>实现,完全不用手动逐个编写。
为什么反射行不通?
你说得对,Java的泛型是编译时擦除的,运行时Class对象里没有泛型类型信息。Gson(以及Retrofit的解析逻辑)需要明确的泛型类型才能正确反序列化JSON,反射生成的Callback实例没法提供这个编译时类型信息,所以这条路走不通。
核心思路:编译时自动生成代码
我们可以在编译阶段扫描指定包下的所有类,然后自动生成包含所有对应Callback实例的Java类。这样生成的代码里,泛型类型是硬编码的(比如Callback<MyClass1>),完全符合编译时类型要求,Gson也能正确解析。
下面是具体实现步骤:
1. 定义一个标记注解(可选但推荐)
首先创建一个注解,用来标记需要生成Callback的类(或者你也可以直接在处理器里指定包名扫描,不用注解):
@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) // 只在编译阶段保留,不会进入字节码 public @interface RetrofitCallbackTarget {}
给com.my.package下的MyClass1、MyClass2等类加上这个注解:
package com.my.package; @RetrofitCallbackTarget public class MyClass1 { // 类内容 }
2. 编写注解处理器
注解处理器会在编译时扫描被@RetrofitCallbackTarget标记的类,自动生成包含所有Callback的工具类。这里是核心代码片段:
@SupportedAnnotationTypes("com.your.annotation.package.RetrofitCallbackTarget") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class CallbackGeneratorProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 获取所有被标记的类 Set<? extends Element> targetClasses = roundEnv.getElementsAnnotatedWith(RetrofitCallbackTarget.class); if (targetClasses.isEmpty()) return false; // 创建要生成的Java文件 try (PrintWriter writer = new PrintWriter( processingEnv.getFiler().createSourceFile("com.your.utils.AutoCallbackHandler").openWriter() )) { // 写入包声明和导入语句 writer.println("package com.your.utils;"); writer.println(); writer.println("import retrofit2.Call;"); writer.println("import retrofit2.Callback;"); writer.println("import retrofit2.Response;"); writer.println("import com.my.package.*;"); writer.println(); // 生成工具类和方法 writer.println("public class AutoCallbackHandler {"); writer.println(" /** 自动为对应类型的Call创建并执行Callback */"); writer.println(" @SuppressWarnings(\"unchecked\")"); writer.println(" public static void handleCall(Call<?> call) {"); // 遍历每个目标类,生成对应的Callback代码块 for (Element clazz : targetClasses) { String className = clazz.getSimpleName().toString(); writer.println(" if (call instanceof Call<" + className + ">) {"); writer.println(" ((Call<" + className + ">) call).enqueue(new Callback<" + className + ">() {"); writer.println(" @Override"); writer.println(" public void onResponse(Call<" + className + "> call, Response<" + className + "> response) {"); writer.println(" // 这里写你的响应处理逻辑"); writer.println(" if (response.isSuccessful()) {"); writer.println(" " + className + " data = response.body();"); writer.println(" // 处理data,比如通知UI、存储等"); writer.println(" }"); writer.println(" }"); writer.println(); writer.println(" @Override"); writer.println(" public void onFailure(Call<" + className + "> call, Throwable t) {"); writer.println(" // 处理请求失败逻辑"); writer.println(" t.printStackTrace();"); writer.println(" }"); writer.println(" });"); writer.println(" }"); } writer.println(" }"); writer.println("}"); } catch (IOException e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "生成Callback工具类失败: " + e.getMessage()); } return true; } }
3. 配置注解处理器
你需要把这个处理器配置到你的构建工具中(比如Gradle):
dependencies { // 注解依赖 compileOnly 'com.your.annotation.package:your-annotation:1.0' // 注解处理器依赖 annotationProcessor 'com.your.processor.package:your-processor:1.0' }
4. 使用生成的工具类
现在,当你需要执行Call的时候,直接调用生成的方法即可:
// 比如你从Retrofit拿到的Call实例 Call<MyClass1> call1 = apiService.getMyClass1Data(); Call<MyClass2> call2 = apiService.getMyClass2Data(); // 统一处理,不用手动写Callback AutoCallbackHandler.handleCall(call1); AutoCallbackHandler.handleCall(call2);
额外优化:无需注解,直接扫描指定包
如果你不想给每个类加注解,可以修改注解处理器,直接扫描com.my.package下的所有类。这需要借助Filer或者第三方库(比如Google Auto的AutoService)来辅助扫描包路径,不过实现起来稍微复杂一点,但核心逻辑还是编译时生成代码。
替代方案:构建阶段代码生成
如果注解处理器对你来说太复杂,也可以用Gradle/Maven的自定义Task,结合FreeMarker/Velocity等模板引擎,在构建前扫描指定包下的类,然后生成对应的Java代码。这种方式更灵活,适合不熟悉注解处理器的开发者。
总结一下:通过编译时的代码生成,我们完美解决了泛型类型擦除的问题,同时实现了自动适配指定包下类的增删——只要重新编译,生成的代码就会自动更新,完全不用修改原有业务代码。
内容的提问来源于stack exchange,提问作者Jonas Herzog

