2. Android 组件化二Arounter 注解处理器:APT 自动生成路由表 vs 手写实现

摘要:

ARouter 组件化依赖 APT 注解处理器 在编译期自动扫描 @Route 注解,生成路由映射文件(如 ARouter$$Group$$app),实现跨模块路由发现。相比手动维护路由表:

  1. APT 优势:自动收集路由信息,避免硬编码;
  2. 手写处理器核心 :继承 AbstractProcessor,重写 process() 处理注解元素;
  3. 关键产出 :生成 JavaFile 注入 LogisticsCenter
    自动生成方案提升可维护性,减少人为错误,是大型项目首选。

一.Arountre APT生成了哪些文件?生成的路径

1.1 生成的java文件的路径

ruby 复制代码
module_cart\build\generated\ap_generated_sources\debug\out\com\alibaba\android\arouter\routes\ARouter$$Group$$cart.java

1.2.文件的内容有2种

1.2.1 ROOT文件作用:根路由表

  • 按组(group)组织的路由表

  • 每个分组对应一个文件,包含该分组下所有路由的注册信息

  • 在初始化时会被加载,用于建立路由表

typescript 复制代码
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER.
public class ARouter$$Root$$module_cart implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("cart", ARouter$$Group$$cart.class);
  }
}

1.2.2 Group文件的作用:分组路由类

  • 模块级别的路由根表
  • 包含该模块中所有分组的信息
  • 在初始化时会被调用,用于加载该模块的所有分组
typescript 复制代码
public class ARouter$$Group$$cart implements IRouteGroup {

  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/cart/cartActivity", RouteMeta.build(RouteType.ACTIVITY, CartActivity.class, "/cart/cartactivity", "cart", null, -1, -2147483648));
    atlas.put("/cart/cartFragment", RouteMeta.build(RouteType.FRAGMENT, CartFragment.class, "/cart/cartfragment", "cart", null, -1, -2147483648));
    atlas.put("/cart/service", RouteMeta.build(RouteType.PROVIDER, CartServiceImpl.class, "/cart/service", "cart", null, -1, -2147483648));
  }
}

工作原理

这些生成的文件共同构成了 ARouter 的路由表系统:

  1. 在应用启动时,ARouter 会通过反射加载所有 ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> R o o t Root </math>Root 文件

  2. 然后通过这些根文件加载各个分组的路由表ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> G r o u o p Grouop </math>Grouop 文件

二.APT原理

2.1 它的主要作用

APT (Annotation Processing Tool,注解处理器工具) 是 Java 提供的一种在编译期间处理源代码中注解的工具,它允许开发者通过自定义注解处理器在编译阶段生成额外的代码或文件。

2.2 运行的流程

  1. 编译开始:当编译器(javac)开始编译源代码时

  2. 调用处理器:编译器会查找并调用注册的注解处理器

  3. 处理注解:处理器扫描源代码中的特定注解

  4. 生成新代码:处理器根据注解信息生成新的代码或文件

  5. 再次编译:生成的新代码会被包含在编译过程中

2.3 如何实现自定义 APT

  1. 定义注解 (Java/Kotlin)

  2. 实现 AbstractProcessor 类

  3. 注册处理器 (通过 @AutoService 或 META-INF/services)

  4. 使用 javax.annotation.processing 包中的工具类处理元素

  5. 通过 Filer 接口生成新文件

2.4 AbstractProcessor 核心方法详解

1. init(ProcessingEnvironment processingEnv)
作用:初始化处理器
2. getSupportedAnnotationTypes()
作用:指定处理器支持的注解类型
3. getSupportedSourceVersion()
作用:指定支持的 Java 版本
4. process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
作用:实际处理注解的主要方法

三.Arounter 注解处理器APT源码RouteProcessor分析

3.1 4个module的关系!

注解处理器的执行是在编译的初始阶段,并且会有多个processor(查看所有注册的processor:intermediates/annotation_processor_list/debug/annotationProcessors.json)

注解处理器和autoService的应用和javepoet !

3.2 注解处理器有几个 :

RouteProcessor,处理注解@Route、@Autowired注解,用于生成路由帮助类

InterceptorProcessor,处理@Interceptor注解,用于生成拦截器帮助列

AutowiredProcessor,处理@Autowired注解,用户生成处理activity/fragment变量的帮助类

base模块:

BaseProcessor 主要是在init()中做了各种工具的初始化,同时获取了key为 AROUTER_MODULE_NAME 的配置项 moduleName------module名字。我们注意重写的getSupportedOptions()方法,它和前面的介绍的@SupportedOptions作用是一致的。 AROUTER_MODULE_NAME 就是路由所在module的gradle文件中配置的:

3.3 RouteProcessor源码解析

typescript 复制代码
// 使用 Google AutoService 自动注册注解处理器
@AutoService(Processor.class)

// 声明处理的注解类型:@Route 和 @Autowired
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {

    // 按组名分组存储路由元数据(Key: group名称,Value: 路由元数据集)
    private Map<String, Set<RouteMeta>> groupMap = new HashMap<>();
    // 根路由表(Key: group名称,Value: 生成的Group类名),使用TreeMap保持顺序
    private Map<String, String> rootMap = new TreeMap<>();  
    // IProvider接口的类型标记(用于识别服务提供者)
    private TypeMirror iProvider = null;
    // 路由文档输出流(可选功能)
    private Writer docWriter;       
    /**
    * 初始化注解处理器
    * @param processingEnv 提供编译期工具类(Filer/Messager等)
    */

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        // 如果开启文档生成,创建JSON文档输出流
        if (generateDoc) {
            try {
                docWriter = mFiler.createResource(
                        StandardLocation.SOURCE_OUTPUT,
                        PACKAGE_OF_GENERATE_DOCS,
                        "arouter-map-of-" + moduleName + ".json"
                ).openWriter();
            } catch (IOException e) {
                logger.error("文档输出流创建失败: " + e.getMessage());
            }
        }

        // 获取IProvider接口类型(用于后续服务提供者识别)
        iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();
    }

    /**

    * 注解处理主入口
    * @param annotations 当前处理的注解类型集合
    * @param roundEnv 提供访问当前编译轮次元素的能力
    * @return 是否处理完成(true表示已处理,其他处理器无需再处理)
    */

    @Override

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // 获取所有被@Route注解标记的元素(类/接口)
        Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
        try {
            // 解析路由并生成代码
            this.parseRoutes(routeElements);
        } catch (Exception e) {
            logger.error("路由解析异常: " + e);
        }
        return true;
    }

    /**

    * 核心路由解析方法

    * @param routeElements 被@Route注解的元素集合

    */
    private void parseRoutes(Set<? extends Element> routeElements) throws IOException {

        // 1. 准备基础类型信息
        TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
        TypeMirror type_Service = elementUtils.getTypeElement(SERVICE).asType();
        TypeMirror fragmentTm = elementUtils.getTypeElement(FRAGMENT).asType();

        // 2. 构建方法参数类型(用于后续代码生成)
        ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))

        );

        // 3. 遍历所有路由元素进行分类处理

        for (Element element : routeElements) {
            Route route = element.getAnnotation(Route.class);
            RouteMeta routeMeta;
            // 处理Activity/Fragment类型路由
            if (types.isSubtype(tm, type_Activity) || ... ) {
                // 收集@Autowired字段信息(支持递归父类字段)
                Map<String, Integer> paramsType = new HashMap<>();
                Map<String, Autowired> injectConfig = new HashMap<>();
                injectParamCollector(element, paramsType, injectConfig);

                // 构建路由元数据
                routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                routeMeta.setInjectConfig(injectConfig);

            } 

            // 处理IProvider类型路由
            else if (types.isSubtype(tm, iProvider)) {
                routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
            }
            // 将路由按组分类存储
            categories(routeMeta);
        }

        // 4. 生成分组路由类(每个组生成一个类)
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {

            String groupName = entry.getKey();
            // 构建loadInto方法(填充路由表数据)
            MethodSpec.Builder loadIntoMethod = ...;
            // 使用JavaPoet生成类文件
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(NAME_OF_GROUP + groupName)
                    .addSuperinterface(IRouteGroup.class)
                    .addMethod(loadIntoMethod.build())
                    .build()

            ).writeTo(mFiler);
            // 记录组与生成类的映射关系
            rootMap.put(groupName, NAME_OF_GROUP + groupName);
        }

        // 5. 生成根路由表(汇总所有分组)
        JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
            TypeSpec.classBuilder(NAME_OF_ROOT + moduleName)
                .addSuperinterface(IRouteRoot.class)
                .addMethod(loadIntoMethodOfRootBuilder.build())
                .build()

        ).writeTo(mFiler);

        // 6. 可选:输出路由文档
        if (generateDoc) {
            docWriter.append(JSON.toJSONString(docSource));
            docWriter.close();
        }
    }

    /**

    * 递归收集@Autowired字段信息
    * @param element 当前类元素
    * @param paramsType 存储字段类型映射
    * @param injectConfig 存储注解配置
    *

    private void injectParamCollector(Element element, Map<String, Integer> paramsType, 

                                    Map<String, Autowired> injectConfig) {

        // 遍历类中所有字段
        for (Element field : element.getEnclosedElements()) {
            if (是字段 && 有@Autowired注解 && 不是IProvider类型) {
                Autowired config = field.getAnnotation(Autowired.class);
                String name = 获取字段名;
                paramsType.put(name, 字段类型编码);
                injectConfig.put(name, config);
            }
        }

        // 递归处理父类字段(排除Android系统类)
        if (父类存在 && 非Android类) {
            injectParamCollector(父类元素, paramsType, injectConfig);
        }
    }

    /**

    * 路由元数据校验
    * @param meta 路由元数据
    * @return 是否有效
    */

    private boolean routeVerify(RouteMeta meta) {

        // 路径必须以/开头
        if (!meta.getPath().startsWith("/")) return false;

        // 如果未指定group,从路径第一段提取
        if (StringUtils.isEmpty(meta.getGroup())) {
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            meta.setGroup(defaultGroup);
        }
        return true;
    }
}

如上所示,(忽略了参数处理以及路由Doc逻辑)总共有三个步骤:

  • 准备工作,遍历routeElements并创建对应RouteMeta,接着按group分组后 存入 groupMap
  • 遍历groupMap,创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句
  • 遍历rootMap生成方法体语句后创建根帮助类,创建Provider帮助类
核心功能总结
  1. 路由表生成
  • 根据@Route注解生成分组路由类(ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> G r o u p Group </math>Groupxxx)和根路由表(ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> R o o t Root </math>Rootmodule)。

  • 支持Activity/Fragment/Service/IProvider四种路由类型。

  1. 依赖注入支持
  • 通过@Autowired自动收集字段信息,生成参数映射表,实现编译时类型检查。
  1. 模块化设计
  • 每个模块独立生成路由表,通过moduleName区分,避免冲突。
  1. 文档化输出(可选)
  • 生成arouter-map-of-module.json文件,记录路由路径、参数等元信息。
  1. 懒加载优化
  • 按组加载路由(IRouteGroup),减少初始化性能开销。

这个处理器是ARouter实现"编译时注册,运行时使用"机制的核心,通过APT在编译期完成所有路由收集和代码生成,避免了运行时反射的性能损耗

四.手写的Arounter APT注解处理器RouteProcessor

前面说了这么多,要明白APT的工作原理,一定要实战,下面手写2个项目,Arounter和bufferknite

Java library 1).要定义注解模块 moduleBufferKnifeAntotations

2).注解处理器模块 moduleBufferKnife

4.1 比如要生成下面这个文件,ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> R o o t Root </math>Rootmodule_cart.java

java 复制代码
package com.alibaba.android.arouter.routes;
import com.alibaba.android.arouter.facade.template.IRouteGroup;
import com.alibaba.android.arouter.facade.template.IRouteRoot;
import java.lang.Class;
import java.lang.Override;
import java.lang.String;
import java.util.Map;


/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$module_cart implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("cart", ARouter$$Group$$cart.class);
  }
}

具体步骤:添加注解和注解处理器

typescript 复制代码
@AutoService(Processor.class)
public class RouteProcessor extends AbstractProcessor {
    private static final String PACKAGE_NAME = "com.alibaba.android.arouter.routes";
    private static final String CLASS_PREFIX = "ARouter$$Root$$";
    private static final String GROUP_CLASS_PREFIX = "ARouter$$Group$$";

    // 存储模块名与对应Group类的映射
    private final Map<String, String> rootMap = new TreeMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 初始化时可以添加日志等操作
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(Route.class.getCanonicalName());
    }
  
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
 
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 1. 收集所有@Route注解的元素
        Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);

        // 2. 按模块分组(这里简化处理,假设所有cart模块的路由都有特定标记)
        for (Element element : routeElements) {
            Route route = element.getAnnotation(Route.class);
            if (route.path().startsWith("/cart/")) {  // 简单通过路径判断模块
                String groupName = "cart";
                rootMap.put(groupName, GROUP_CLASS_PREFIX + groupName);
            }
        }

        // 3. 生成Root类
        generateRootClass("module_cart");  // 假设模块名为module_cart
        return true;

    }
    /**
    * 生成Root路由类
    * @param moduleName 模块名称
    */

    private void generateRootClass(String moduleName) {
        // 1. 准备必要的类型
        ClassName iRouteGroup = ClassName.get(IRouteGroup.class);
        ClassName classClass = ClassName.get(Class.class);

        // 2. 构建方法参数类型: Map<String, Class<? extends IRouteGroup>>
        ParameterizedTypeName mapTypeName = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        classClass,
                        WildcardTypeName.subtypeOf(iRouteGroup)
                ));

        // 3. 构建loadInto方法
        MethodSpec loadIntoMethod = MethodSpec.methodBuilder("loadInto")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(mapTypeName, "routes")
                .addCode(buildMethodBody())
                .build();

        // 4. 构建类
        TypeSpec rootClass = TypeSpec.classBuilder(CLASS_PREFIX + moduleName)
                .addJavadoc("DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER.\n")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(IRouteRoot.class)
                .addMethod(loadIntoMethod)
                .build();

        // 5. 写入文件
        try {
            JavaFile.builder(PACKAGE_NAME, rootClass)
                    .build()
                    .writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
    * 构建方法体代码
    */
    private String buildMethodBody() {
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, String> entry : rootMap.entrySet()) {
            builder.append("routes.put(\"")
                    .apend(entry.getKey())
                    append("\", ")
                    .append(entry.getValue())
                   .append(".class);\n");
        }
        return builder.toString();
    }
}

4.2 APT JavaPoet AutoService SPI 4者的关系

四者关系对比表

技术/工具 角色定位 所属阶段 典型应用场景
APT 编译时注解处理工具,扫描并处理源码中的注解 编译期 生成路由表、依赖注入代码等
JavaPoet 代码生成库,提供类型安全的 Java 代码构建 API 编译期(配合APT) 动态生成 .java 文件(如 ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> R o o t Root </math>Rootxxx)
AutoService 自动生成 META-INF/services/ 配置,简化 SPI 实现类的注册 编译期 自动注册注解处理器(如 RouteProcessor)
SPI 服务发现机制,通过接口配置实现类动态加载 运行期 加载 IRouteRoot 和 IRouteGroup 实现类

生成的目录在哪个位置:

ARouter 通过autoservicee注册生成的文件,在哪个目录

ARouter 通过 AutoService 注册生成的文件(即注解处理器注册文件)位于 编译输出的 JAR 文件内部,而不是源代码目录中。具体位置如下:

核心位置(在 arouter-compiler JAR 中)

这个目录是什么目录?

(查看所有注册的processor:intermediates/annotation_processor_list/debug/annotationProcessors.json)

compiler的module,里面找到的内容就是

在源码红,进行编译! 并不会在我们的项目中,而是在阿里巴巴的架包中!

com.alibaba.android.arouter.compiler.processor.RouteProcessor,aggregating

com.alibaba.android.arouter.compiler.processor.AutowiredProcessor,aggregating

com.alibaba.android.arouter.compiler.processor.InterceptorProcessor,aggregating

关键结论

AutoService 生成的文件位于 arouter-compiler 的 JAR 包内

路径固定为:META-INF/services/javax.annotation.processing.Processor

4.2.1四者协作关系

APT 作为基础工具,处理注解。

JavaPoet 被 APT 调用,生成 Java 代码。

AutoService 利用 APT + JavaPoet:

扫描 @AutoService 注解。

生成 SPI 配置文件(替代手动编写)。

SPI 是最终目标:通过配置文件实现运行时动态加载服务。

4.2.2 对比下使用javapoet和不使用javapoet的api有说明区别

1.导入处理 原生都只能用write,字符串拼接

2.处理复杂结构(泛型、注解等)时非常棘手

  1. 格式化与缩进 javapoet的缩进

  2. Lambda 表达式支持

4.2.3. 详细分工说明

(1) APT(Annotation Processing Tool)
  • 作用:在编译期扫描和处理注解(如 @Route)
  • 关键行为:
  • 通过 AbstractProcessor 子类(如 RouteProcessor)实现注解处理逻辑
  • 调用 JavaPoet API 生成代码
  • 示例:
typescript 复制代码
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {

    // 处理@Route注解并生成代码

}
(2) JavaPoet
  • 作用:替代字符串拼接,以类型安全的方式生成 Java 代码
  • 核心对象:
  • TypeSpec:生成类/接口
  • MethodSpec:生成方法
  • JavaFile:输出文件
  • 生成代码示例:
less 复制代码
JavaFile.builder("com.example", 

    TypeSpec.classBuilder("HelloWorld")

        .addMethod(MethodSpec.methodBuilder("main")

            .addStatement("System.out.println($S)", "Hello JavaPoet!")

            .build())

        .build())

    .build()

    .writeTo(filer);
(3) AutoService
  • 作用:自动为注解处理器生成 SPI 注册文件
  • 解决的问题:
  • 手动编写 META-INF/services/javax.annotation.processing.Processor 文件易出错
  • 使用方式:
scala 复制代码
@AutoService(Processor.class)

public class MyProcessor extends AbstractProcessor { ... }
  • 生成文件内容:

com.example.MyProcessor

(4) SPI(Service Provider Interface)
  • 作用:运行时动态发现和加载实现类
  • ARouter中的应用:
  • 通过 META-INF/services/com.alibaba.android.arouter.facade.template.IRouteRoot 文件
  • 加载所有模块的 ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> R o o t Root </math>Rootxxx 实现类
  • 关键代码:
ini 复制代码
ServiceLoader<IRouteRoot> loader = ServiceLoader.load(IRouteRoot.class);
for (IRouteRoot root : loader) {
    root.loadInto(Warehouse.groupsIndex);
}

4.3. 四者协同工作示例

  1. 编译期:
  • APT 通过 RouteProcessor 处理 @Route 注解

  • JavaPoet 生成 ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> R o o t Root </math>Rootmodule_cart.java 等文件

  • AutoService 生成处理器注册信息

  1. 运行时:
  • ARouter 初始化时通过 SPI 机制加载所有 IRouteRoot 实现类
  • 调用 loadInto() 方法注册路由表

spi

我们希望通过 SPI 自动发现所有实现类,而不需要硬编码。

为什么需要 SPI 机制?

  1. 解决硬编码问题

无 SPI:

csharp 复制代码
// 硬编码实现

PaymentService service;
if ("alipay".equals(type)) {
    service = new AlipayService();
} else if ("wechat".equals(type)) {
    service = new WechatPayService();
} // 添加新支付需修改此处

有 SPI:

// 动态发现实现

less 复制代码
for (PaymentService service : ServiceLoader.load(PaymentService.class)) {
    if (service.supports(type)) {
        service.pay(amount);
    }
}

动态扩展

作用就是:无需修改主程序即可添加新功能

注解处理器是抽象类! 不是接口

所以SPI的2个作用

1.接口

2.抽象类,注解处理器!

autoServices:的作用!

autoServices:为什么要创建和维护 META-INF/services 文件。

AutoService 不是必需的,但它通过自动化 SPI 注册文件的生成:

AutoService 的优点

简化 SPI 配置:AutoService 自动生成 META-INF/services 文件,减少了手动维护注册文件的工作。

避免手动注册:不再需要手动在 META-INF/services 中创建文件和列出实现类。

编译时生成:所有的注册信息在编译时生成,避免了运行时的性能开销。

与 ServiceLoader 配合:与 Java 的 ServiceLoader 配合使用,实现插件式架构和服务发现机制。

AutoService 源码解析

就是可以直接找到所有接口实现,不用提前知道,也不用new处理,

问题: spi,如果有多个实现,怎么办

看架构图的区别:

一个是面向接口

一个是面向接口的实现

第二: 要使用具体的某个实现!

可以通过胚子和的方式进行实现!

在 SPI 机制中,当有多个服务实现时,你确实需要一种方法来选择使用哪个服务。以下是完整的解决方案:

配置文件选择服务

config.properties

ini 复制代码
payment.service=alipay
public static void main(String[] args) {

    // 读取配置文件
    Properties prop = new Properties();
    try (InputStream input = Main.class.getClassLoader().getResourceAsStream("config.properties")) {
        prop.load(input);
    } catch (IOException ex) {
        ex.printStackTrace();
    }

    String selectedService = prop.getProperty("payment.service", "alipay")
    ServiceLoader<PaymentService> services = ServiceLoader.load(PaymentService.class);
    for (PaymentService service : services) {
        // 假设使用方案一的服务标识方法
        if (service.getServiceName().equals(selectedService)) {
            service.pay(100.0);
            break;
        }
    }
}

问题:android 项目中有哪些典型的使用了spi

  1. 图片加载库解耦

定义ImageLoader接口,SPI动态加载Fresco/Glide等实现,替换时仅需修改配置文件

2.APT注解处理器

如Dagger、ARouter编译器,通过SPI发现并执行注解处理逻辑

3.ARouter路由框架

通过SPI自动注册路由表,结合APT生成META-INF/services文件,实现模块间跳转和服务的零配置注入

// autoService的使用demo 和没用使用autoService的demo

// 注解处理器的使用demo 和javapoet的作用

// 三者一起的作用! -------

**autoService的作用

typescript 复制代码
// PaymentService.java
public interface PaymentService {
    void pay(double amount);
}

@AutoService(PaymentService.class)
public class AlipayService implements PaymentService{
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付: ¥" + amount);
    }
}

// WechatPayService.java
@AutoService(PaymentService.class)
public class WechatPayService implements PaymentService {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付: ¥" + amount);
    }
}

/**

 * @Author pengcaihua
 * @Date 15:35
 * @describe
 */

public class SpiFactory {

 
    /***

    * 有 SPI, 并且是通过autoService实现的
    * 1).不需要因为添加实现类,改动代码
    * 2).不需要手动注册服务
    * @param args
    */

    public static void autoServiceMain(String[] args) {
        ServiceLoader<PaymentService> services =
                ServiceLoader.load(PaymentService.class);
        for (PaymentService service : services) {
            service.pay(100.0);
        }
    }


    /***
    * 有 SPI:
    * 1).不需要因为添加实现类,改动代码
    * @param args
    */

    public static void main(String[] args) {
        ServiceLoader<PaymentService> services =
                ServiceLoader.load(PaymentService.class);
        for (PaymentService service : services) {
            service.pay(100.0);
        }
    }

  


    /***
    * 无 SPI
    * 需要硬编码并且改动调用类
    * @param args
    */

    public static void main2(String[] args) {
        // 硬编码实现
        PaymentService service;
        String type="";
        if ("alipay".equals(type)) {
            service = new AlipayService();
        } else if ("wechat".equals(type)) {
            service = new WechatPayService();
        } // 添加新支付需修改此处
    }

添加依赖
    annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
    implementation 'com.google.auto.service:auto-service:1.0.1'

  

4.4 APT的实战练习,手写bufferknite

-------------------------------手写bufferknite------------------------------------------

4.4.1.2个千万不要搞反了!

java 复制代码
dependencie {
    implementation project(':annotations') // 注解定义模块
    annotationProcessor project(':compiler') // 注解处理器模块
}

确保:

使用了 annotationProcessor 而不是 compileOnly 或 implementation

模块路径正确(:annotations 和 :compiler 是实际模块名)

注解处理器没有工作,怎么排查?

init方法,然后在分析process()

如果process方法没执行,可能是使用的地方没用用到注释

多个处理器,发现另外一个没有生效!

因为生成的类名不能为空!

2个注解处理器的内容,需要注意!

4.4.2.还有一个就是这个,注释的类型,用"*",所以的可能有问题!

还有就是,这个类型getSupportedAnnotationTypes匹配错了,也不会生成文件的! 注: PengProcessor PROCESSOR INIT 注: TestProcessor PROCESSOR INIT

但是会执行init方法,不会执行process方法!

typescript 复制代码
 public Set<String> getSupportedAnnotationTypes() {
        return Set.of(
                "android.annotation.SuppressLint",
                "androidx.annotation.NonNull"
        );
    }

4.4.3.如果这个写错了也是有问题的,不会编译成功!

@SupportedSourceVersion(SourceVersion.RELEASE_8) // 添加源版本支持

4.4.4 bufferKnite的源码

dart 复制代码
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ViewBinderProcessor extends AbstractProcessor {

    private Elements elementUtils;
    private Messager messager;
    private Filer filer;
    private boolean processed = false;

    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        elementUtils = env.getElementUtils();
        messager = env.getMessager();
        filer = env.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        // 防止重复处理
        if (processed || annotations.isEmpty()) {
            return false;
        }

        messager.printMessage(Diagnostic.Kind.NOTE, "ViewBinderProcessor 开始处理");

        try {
            // 收集所有使用@BindView的元素
            Map<TypeElement, List<Element>> bindViewMap = new HashMap<>();
            for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
                if (element.getKind() != ElementKind.FIELD) {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@BindView 只能用于字段", element);
                    continue;
                }

                TypeElement enclosingClass = (TypeElement) element.getEnclosingElement();
                bindViewMap.computeIfAbsent(enclosingClass, k -> new ArrayList<>()).add(element);

                messager.printMessage(Diagnostic.Kind.NOTE, "找到 @BindView: " +
                        enclosingClass.getSimpleName() + "." + element.getSimpleName());
            }

            // 收集所有使用@OnClick的元素
            Map<TypeElement, List<Element>> onClickMap = new HashMap<>();
            for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
                if (element.getKind() != ElementKind.METHOD) {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@OnClick 只能用于方法", element);
                    continue;
                }

                TypeElement enclosingClass = (TypeElement) element.getEnclosingElement();
                onClickMap.computeIfAbsent(enclosingClass, k -> new ArrayList<>()).add(element);

                messager.printMessage(Diagnostic.Kind.NOTE, "找到 @OnClick: " +
                        enclosingClass.getSimpleName() + "." + element.getSimpleName());
            }

            // 为每个类生成绑定类
            for (TypeElement enclosingClass : bindViewMap.keySet()) {
                generateBinderClass(
                        enclosingClass,
                        bindViewMap.get(enclosingClass),
                        onClickMap.getOrDefault(enclosingClass, Collections.emptyList())
                );
            }

            processed = true;
            messager.printMessage(Diagnostic.Kind.NOTE, "ViewBinderProcessor 完成处理");
            return true;

        } catch (Exception e) {
            messager.printMessage(Diagnostic.Kind.ERROR, "处理失败: " + e);
            e.printStackTrace();
            return false;
        }
    }

    private void generateBinderClass(TypeElement enclosingClass,
                                     List<Element> bindViewElements,
                                     List<Element> onClickElements) throws IOException {
        // 获取包名和类名
        String packageName = elementUtils.getPackageOf(enclosingClass).getQualifiedName().toString();
        String className = enclosingClass.getSimpleName().toString();
        String binderClassName = className + "_ViewBinder";

        messager.printMessage(Diagnostic.Kind.NOTE, "生成: " + packageName + "." + binderClassName);

        // 创建绑定方法
        MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addParameter(ClassName.get(enclosingClass), "target");

        // 处理@BindView
        for (Element element : bindViewElements) {
            String fieldName = element.getSimpleName().toString();
            int viewId = element.getAnnotation(BindView.class).value();
            String viewType = element.asType().toString(); // 使用字符串形式

            bindMethodBuilder.addStatement("$T view_$L = target.findViewById($L)",
                            ClassName.bestGuess(viewType), fieldName, viewId)
                    .addStatement("target.$L = view_$L", fieldName, fieldName);
        }

        // 处理@OnClick
        for (Element element : onClickElements) {
            ExecutableElement method = (ExecutableElement) element;
            int viewId = element.getAnnotation(OnClick.class).value();
            String methodName = method.getSimpleName().toString();

            // 创建监听器
            bindMethodBuilder.addStatement("target.findViewById($L).setOnClickListener(v -> target.$L(v))",
                    viewId, methodName);
        }

        // 构建类
        TypeSpec binderClass = TypeSpec.classBuilder(binderClassName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(bindMethodBuilder.build())
                .build();

        // 写入文件
        JavaFile.builder(packageName, binderClass)
                .build()
                .writeTo(filer);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of(
                "com.example.modulebufferknifeantotations.BindView",
                "com.example.modulebufferknifeantotations.OnClick"
        );
    }
}

手写的Arouter和bufferKnife的项目地址:github.com/pengcaihua1...

相关推荐
一只小风华~1 小时前
Web前端:JavaScript和CSS实现的基础登录验证功能
前端
90后的晨仔1 小时前
Vue Router 入门指南:从零开始实现前端路由管理
前端·vue.js
LotteChar1 小时前
WebStorm vs VSCode:前端圈的「豆腐脑甜咸之争」
前端·vscode·webstorm
90后的晨仔1 小时前
零基础快速搭建 Vue 3 开发环境(附官方推荐方法)
前端·vue.js
洛_尘2 小时前
Java EE进阶2:前端 HTML+CSS+JavaScript
java·前端·java-ee
孤独的根号_2 小时前
Vite背后的技术原理🚀:为什么选择Vite作为你的前端构建工具💥
前端·vue.js·vite
吹牛不交税2 小时前
Axure RP Extension for Chrome插件安装使用
前端·chrome·axure
薛定谔的算法2 小时前
# 前端路由进化史:从白屏到丝滑体验的技术突围
前端·react.js·前端框架
拾光拾趣录3 小时前
Element Plus表格表头动态刷新难题:零闪动更新方案
前端·vue.js·element
Adolf_19934 小时前
React 中 props 的最常用用法精选+useContext
前端·javascript·react.js