【TMF】 解析器底层原理分析

简介: 该注解处理器在编译期自动扫描特定注解(如@Extension、@Business),收集标记的类或方法,生成SPI配置文件,实现服务接口与实现类的自动注册,提升开发效率与准确性。

工作原理

1. 编译期扫描阶段

编译器在编译过程中会调用 process() 方法

通过 getProcessAnnotationClass() 指定要处理的注解类型(如 @Extension、@Business 等)

扫描源码中所有被该注解标记的元素(类或方法)

2. 信息收集阶段

支持两种注解位置:

类级别注解:直接标注在类上,注册该类

方法级别注解:标注在方法上,注册方法所在的类

将发现的实现类存储到 providers 映射表中

Key: 服务接口的全限定名

Value: 实现类的全限定名集合

3. 配置文件生成阶段

当 roundEnv.processingOver() 为 true 时(最后一轮处理):

读取已存在的 META-INF/services/接口全限定名 文件(如果有)

合并新发现的服务实现类

生成/更新 SPI 配置文件

文件格式:每行一个实现类的全限定名

关键技术点

增量编译支持:读取已有配置文件,避免覆盖之前注册的服务

异常隔离:捕获所有异常防止编译中断

内部类处理:使用 $ 分隔符构建二进制类名(如 Outer$Inner)

多轮处理机制:利用 Java 注解处理的多轮特性,最后一轮统一生成文件

应用场景

在 Lattice 框架中,这个处理器用于自动注册:

扩展点解析器(ExtensionAnnotationParser)

业务解析器(BusinessAnnotationParser)

Ability 解析器(AbilityAnnotationParser)

等其他 SPI 扩展点

这样就无需手动维护 META-INF/services 文件,提高了开发效率和准确性。

package org.hiforce.lattice.spi.annotation;

import com.google.common.base.Optional;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.hiforce.lattice.utils.ServicesFileUtils;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;

import static com.google.auto.common.MoreElements.getAnnotationMirror;

/**
 * Lattice框架的注解处理器基类,用于在编译期处理自定义注解
 * 实现了Java SPI机制的自动注册功能,将标注的类自动写入META-INF/services配置文件
 * 
 * 工作原理:
 * 1. 扫描源码中的特定注解(如@Extension、@Business等)
 * 2. 收集被注解标记的类信息
 * 3. 在编译完成时生成META-INF/services文件,实现SPI自动注册
 * 
 * @author Rocky Yu
 * @since 2022/9/15
 */
@SuppressWarnings("all")
public abstract class LatticeAnnotationProcessor extends AbstractProcessor {

    /**
     * 获取服务接口的Class对象
     * 子类需要实现此方法,指定要注册到SPI的接口类型
     * @return 服务接口的Class对象
     */
    public abstract Class<?> getServiceInterfaceClass();

    /**
     * 日志输出方法,仅在debug模式下输出
     * @param msg 日志消息
     */
    private void log(String msg) {
        if (processingEnv.getOptions().containsKey("debug")) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
        }
    }

    /**
     * 输出错误信息到编译器
     * @param msg 错误消息
     * @param element 出错的元素
     * @param annotation 出错的注解
     */
    private void error(String msg, Element element, AnnotationMirror annotation) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element, annotation);
    }

    /**
     * 输出致命错误,会导致编译失败
     * @param msg 错误消息
     */
    private void fatalError(String msg) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR: " + msg);
    }

    // 错误提示常量:没有为元素提供服务接口
    public static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!";

    /**
     * 服务提供者映射表
     * Key: 服务接口的全限定类名(如:org.hiforce.lattice.spi.annotation.ExtensionAnnotationParser)
     * Value: 实现类的全限定类名集合(如:org.hiforce.lattice.annotation.parser.DefaultExtensionAnnotationParser)
     * 用于收集所有需要注册到SPI的实现类
     */
    private final Multimap<String, String> providers = HashMultimap.create();

    /**
     * 注解处理的入口方法,由编译器调用
     * @param annotations 本轮处理的注解类型集合
     * @param roundEnv 当前处理轮次的环境信息
     * @return true表示这些注解已被处理,其他处理器不应再处理它们
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            // 调用实际的处理逻辑
            return processImpl(annotations, roundEnv);
        } catch (Exception e) {
            // 捕获所有异常,防止异常传播到编译器导致编译中断
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            fatalError(writer.toString());
            return true;
        }
    }

    /**
     * 注解处理的核心实现方法
     * 注解处理分为多轮进行,最后一轮用于生成配置文件
     * @param annotations 本轮处理的注解类型集合
     * @param roundEnv 当前处理轮次的环境信息
     * @return 总是返回true
     */
    private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver()) {
            // 最后一轮处理:生成META-INF/services配置文件
            generateConfigFiles();
        } else {
            // 常规处理轮次:扫描和收集被注解标记的类
            processAnnotations(annotations, roundEnv);
        }

        return true;
    }

    /**
     * 获取要处理的注解类型
     * 子类需要实现此方法,指定要处理哪个注解(如@Extension、@Business等)
     * @return 注解的Class对象
     */
    public abstract Class<? extends Annotation> getProcessAnnotationClass();

    /**
     * 返回此处理器支持的注解类型集合
     * @return 包含注解全限定类名的不可变集合
     */
    @Override
    public final ImmutableSet<String> getSupportedAnnotationTypes() {
        return ImmutableSet.of(getProcessAnnotationClass().getName());
    }

    /**
     * 处理注解标记的元素,收集需要注册的服务实现类
     * 支持两种注解位置:
     * 1. 类级别注解:直接标注在类上
     * 2. 方法级别注解:标注在方法上,注册方法所在的类
     * 
     * @param annotations 注解类型集合
     * @param roundEnv 当前轮次环境
     */
    @SuppressWarnings("all")
    private void processAnnotations(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv) {

        // 获取所有被目标注解标记的元素(类或方法)
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(getProcessAnnotationClass());

        log(annotations.toString());
        log(elements.toString());

        // 遍历所有被注解标记的元素
        for (Element e : elements) {
            TypeElement providerImplementer = null;
            
            // 情况1:注解标注在类上
            if (e instanceof TypeElement) {
                providerImplementer = (TypeElement) e;
            }
            
            // 情况2:注解标注在方法上,获取方法所在的类
            if (e.getKind() == ElementKind.METHOD && e instanceof ExecutableElement) {
                ExecutableElement executableElement = (ExecutableElement) e;
                Element element = executableElement.getEnclosingElement();
                // 确保外围元素是类类型
                if (element.getKind() == ElementKind.CLASS && element instanceof TypeElement) {
                    providerImplementer = (TypeElement) (executableElement.getEnclosingElement());
                }
            }
            
            // 如果无法确定实现类,跳过此元素
            if (providerImplementer == null) {
                continue;
            }
            
            // 获取注解镜像对象,用于错误报告
            Optional<AnnotationMirror> optional = getAnnotationMirror(e, getProcessAnnotationClass());
            if (optional.isPresent()) {
                AnnotationMirror annotationMirror = optional.get();
                // 获取服务接口类
                Class<?> serviceClass = getServiceInterfaceClass();
                if (null == serviceClass) {
                    error(MISSING_SERVICES_ERROR, e, annotationMirror);
                    continue;
                }
                // 将实现类注册到providers映射表中
                // Key: 服务接口全限定名, Value: 实现类全限定名
                providers.put(serviceClass.getName(), getBinaryName(providerImplementer));
            }
        }
    }

    /**
     * 生成META-INF/services配置文件,实现Java SPI机制的自动注册
     * 
     * 工作流程:
     * 1. 读取已存在的服务配置文件(如果有)
     * 2. 合并新发现的服务实现类
     * 3. 写入更新后的配置文件
     * 
     * 生成的文件格式:META-INF/services/接口全限定名
     * 文件内容:每行一个实现类的全限定名
     */
    private void generateConfigFiles() {
        // 获取文件操作对象,用于创建和读取资源文件
        Filer filer = processingEnv.getFiler();

        // 遍历所有收集到的服务接口
        for (String providerInterface : providers.keySet()) {
            // 构建SPI配置文件路径:META-INF/services/接口全限定名
            String resourceFile = "META-INF/services/" + providerInterface;
            log("Working on resource file: " + resourceFile);
            try {
                // 使用TreeSet保证实现类列表有序
                SortedSet<String> allServices = Sets.newTreeSet();
                try {
                    // 尝试读取已存在的服务配置文件
                    // 这样可以支持增量编译,保留之前注册的服务
                    FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
                            resourceFile);
                    log("Looking for existing resource file at " + existingFile.toUri());
                    Set<String> oldServices = ServicesFileUtils.readServiceFile(existingFile.openInputStream());
                    log("Existing service entries: " + oldServices);
                    allServices.addAll(oldServices);
                } catch (IOException e) {
                    // 文件不存在时会抛出IOException
                    // 这是正常情况,说明是首次编译或配置文件尚未创建
                    log("Resource file did not already exist.");
                }

                // 获取本次编译新发现的服务实现类
                Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
                
                // 如果所有新服务都已存在,无需重新生成文件
                if (allServices.containsAll(newServices)) {
                    log("No new service entries being added.");
                    return;
                }

                // 合并新旧服务实现类
                allServices.addAll(newServices);
                log("New service file contents: " + allServices);
                
                // 创建或覆盖SPI配置文件
                FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
                        resourceFile);
                OutputStream out = fileObject.openOutputStream();
                // 将所有服务实现类写入文件,每行一个类名
                ServicesFileUtils.writeServiceFile(allServices, out);
                out.close();
                log("Wrote to: " + fileObject.toUri());
            } catch (IOException e) {
                fatalError("Unable to createPluginConfig " + resourceFile + ", " + e);
                return;
            }
        }
    }

    /**
     * 获取类型元素的二进制名称(全限定类名)
     * 处理内部类时使用$分隔符,例如:com.google.Foo$Bar
     * 而不是源码中的点分隔:com.google.Foo.Bar
     * 
     * @param element 类型元素
     * @return 二进制格式的全限定类名
     */
    private String getBinaryName(TypeElement element) {
        return getBinaryNameImpl(element, element.getSimpleName().toString());
    }

    /**
     * 递归构建二进制类名的实现方法
     * 通过向上遍历外围元素,构建完整的类名
     * 
     * @param element 当前类型元素
     * @param className 累积的类名(从内向外构建)
     * @return 完整的二进制类名
     */
    private String getBinaryNameImpl(TypeElement element, String className) {
        // 获取外围元素(可能是包或外部类)
        Element enclosingElement = element.getEnclosingElement();

        // 情况1:外围元素是包,说明已到达顶层类
        if (enclosingElement instanceof PackageElement) {
            PackageElement pkg = (PackageElement) enclosingElement;
            // 处理默认包(无名包)的情况
            if (pkg.isUnnamed()) {
                return className;
            }
            // 返回:包名.类名
            return pkg.getQualifiedName() + "." + className;
        }

        // 情况2:外围元素是类,说明当前类是内部类
        TypeElement typeElement = (TypeElement) enclosingElement;
        // 递归向上,使用$连接外部类和内部类
        // 例如:Outer$Inner$DeepInner
        return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
    }

    /**
     * 返回此注解处理器支持的Java源代码版本
     * @return Java 8版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }
}
目录
相关文章
|
2月前
|
人工智能 监控 Java
零代码改造 + 全链路追踪!Spring AI 最新可观测性详细解读
Spring AI Alibaba 通过集成 OpenTelemetry 实现可观测性,支持框架原生和无侵入探针两种方式。原生方案依赖 Micrometer 自动埋点,适用于快速接入;无侵入探针基于 LoongSuite 商业版,无需修改代码即可采集标准 OTLP 数据,解决了原生方案扩展性差、调用链易断链等问题。未来将开源无侵入探针方案,整合至 AgentScope Studio,并进一步增强多 Agent 场景下的观测能力。
1455 32
|
14天前
|
机器人 数据挖掘 API
一个销售数据分析机器人的诞生:看 Dify 如何在 DMS 助力下实现自动化闭环
Dify 作为一款低代码 AI 应用开发平台,凭借其直观的可视化工作流编排能力,极大降低了大模型应用的开发门槛。
289 15
一个销售数据分析机器人的诞生:看 Dify 如何在 DMS 助力下实现自动化闭环
|
2月前
|
SQL 人工智能 运维
一场由AI拯救的数据重构之战
本文以数据研发工程师小D的日常困境为切入点,探讨如何借助AI技术提升数据研发效率。通过构建“数研小助手”智能Agent,覆盖需求评估、模型评审、代码开发、运维排查等全链路环节,结合大模型能力与内部工具(如图治MCP、D2 API),实现影响分析、规范检查、代码优化与问题定位的自动化,系统性解决传统研发中耗时长、协作难、维护成本高等痛点,推动数据研发向智能化跃迁。
245 29
一场由AI拯救的数据重构之战
|
2月前
|
人工智能 监控 安全
提效40%?揭秘AI驱动的支付方式“一键接入”系统
本项目构建AI驱动的研发提效系统,通过Qwen Coder与MCP工具链协同,实现跨境支付渠道接入的自动化闭环。采用多智能体协作模式,结合结构化Prompt、任务拆解、流程管控与安全约束,显著提升研发效率与交付质量,探索大模型在复杂业务场景下的高采纳率编码实践。
387 26
提效40%?揭秘AI驱动的支付方式“一键接入”系统
|
2月前
|
人工智能 运维 Cloud Native
直播|均降 40% 的 GPU 成本,大规模 Agent 部署和运维的捷径是什么?
10月28日19:30,阿里云云原生AgentRun与你《极客有约》。
201 28
|
2月前
|
人工智能 安全 API
近期 AI 领域的新发布所带来的启示
2024 年以来,AI 基础设施的快速发展过程中,PaaS 层的 AI 网关是变化最明显的基建之一。从传统网关的静态规则和简单路由开始,网关的作用被不断拉伸。用户通过使用网关来实现多模型的流量调度、智能路由、Agent 和 MCP 服务管理、AI 治理等,试图让系统更灵活、更可控、更可用。国庆期间 AI 界发布/升级了一些产品,我们在此做一个简报,从中窥探下对 AI 网关演进新方向的启示。
369 30
|
24天前
|
人工智能 运维 监控
Flink 智能调优:从人工运维到自动化的实践之路
作者:黄睿 阿里云智能集团产品专家 本文基于阿里云 Flink 平台的实际实践经验整理,希望能为广大流计算从业者提供有价值的参考。
196 26
Flink 智能调优:从人工运维到自动化的实践之路
|
2月前
|
安全 Linux iOS开发
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
340 53
Binary Ninja 5.1.8104 (macOS, Linux, Windows) - 反编译器、反汇编器、调试器和二进制分析平台
|
17天前
|
SQL 分布式计算 DataWorks
【跨国数仓迁移最佳实践7】基于 MaxCompute 多租的大数据平台架构
本系列文章将围绕东南亚头部科技集团的真实迁移历程展开,逐步拆解 BigQuery 迁移至 MaxCompute 过程中的关键挑战与技术创新。本篇为第七篇,基于MaxCompute 多租的大数据平台架构。 注:客户背景为东南亚头部科技集团,文中用 GoTerra 表示。
171 27