【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;
    }
}
目录
相关文章
|
存储 SQL 搜索推荐
业务系统架构实践总结
作者从2015年起至2022年,在业务平台(结算、订购、资金)、集团财务平台(应收应付、账务核算、财资、财务分析、预算)、本地生活财务平台(发票、结算、预算、核算、稽核)所经历的业务系统研发实践的一个总结。1.核心是面向复杂性业务支撑的实践经验(个人概念里的“复杂业务“,大概至少面向5类行业若干业务线且业态差异很大),文章不涉及性能、稳定性、资损防控、大数据离线研发,聚焦在线业务系统架构对多态业务的包容性、开放性、灵活性、可读性。2.文章较多强调”个人”两字,因为仅是我个人在实践上归纳总结的一些方式方法。3.实践经验主要来自两类,一类是接手旧系统,得以见识不一样的设计,文中“见过”特指。
3304 32
|
8月前
|
设计模式 Java
【TMF】源码分析 1.0 LatticeClassLoader
LatticeClassLoader扩展Java双亲委派模型,支持多自定义类加载器的委托加载。类加载失败后依次尝试自定义加载器,实现插件化容错;资源获取优先父加载器,支持单资源查找与多资源聚合,适用于插件系统、多租户隔离及SPI扩展,保障业务隔离与灵活扩展。
304 1
|
安全 Java
【深入理解同步器AQS】
【深入理解同步器AQS】
703 0
|
8月前
|
运维 监控 供应链
Alibaba交易平台TMF2.0介绍
2017双11交易峰值达32.5万笔/秒,面对高并发与复杂业务需求,阿里推出TMF2.0框架,通过业务与平台分离、全链路可视化、配置化发布等创新,实现需求开发周期缩短至12天,支撑多业务快速试错与复用,构建可配置、可监控、可运维的电商技术新体系。
1118 5
Alibaba交易平台TMF2.0介绍
|
8月前
|
缓存 前端开发 Java
【lattice】 lattice-dynamic-loading 深度源码分析
lattice-dynamic-loading 模块实现插件热加载与运行时扩展,通过独立类加载器保障隔离性,集成 Spring 生态,支持动态注册 Bean 与 MVC。基于策略模式、门面模式和 SPI 机制,实现高扩展性、可维护性与热部署能力,提升系统敏捷性。(239字)
327 1
|
8月前
|
Dubbo Java 测试技术
【Lattice】设计原理
Lattice 是一个轻量级业务扩展调用框架,通过模块化架构实现复杂业务定制的高效管理。支持动态发现、加载与执行扩展,提供清晰的分层设计,集成 Spring、Dubbo 等主流技术,助力企业应用灵活扩展。
814 0
|
8月前
|
缓存 测试技术 双11
【Lattice】最佳实践
Lattice-Model 支持多租户SaaS、电商营销、ERP行业定制及微服务扩展,通过插件化实现业务隔离与动态加载,提升系统灵活性与可维护性。
400 0
|
API 开发者
HarmonyOS:ArkTS Path 组件自学指南
在鸿蒙应用开发中,绘制复杂图形常面临传统布局方式难以满足需求的问题。ArkTS 的 Path 组件提供了解决方案,如同一把“神奇画笔”,支持通过灵活的命令和属性绘制直线、曲线、椭圆弧等多样图形。本文详细介绍了 Path 组件从 API Version 7 起的功能特性,包括 `commands`、`fill`、`stroke` 等核心属性,以及各类绘图命令如 `M`(移动)、`L`(直线)、`C`(贝塞尔曲线)等。结合示例代码,展示了如何绘制简单直线到复杂曲线图形,并拓展了颜色、透明度和线条样式的自定义方法。掌握 Path 组件,可为应用带来更丰富生动的视觉体验,助力开发者实现创意绘图需求。
592 21
|
开发工具 git
vscode推送项目到github仓库故障解决1
本文介绍了如何优雅解决本地仓库与远程仓库历史记录不一致的问题,并提供避免未来问题的最佳实践。核心在于理解问题根源(如历史记录差异和常见原因),采用推荐的解决方案(先本地初始化再关联远程仓库),并遵循一致的工作流程、团队协作规范及熟悉 Git 命令。通过强制推送或合并无关历史记录等方式处理现有冲突,同时养成良好习惯以预防类似问题。

热门文章

最新文章