【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;
    }
}
目录
相关文章
|
4月前
|
设计模式 Java
【TMF】源码分析 1.0 LatticeClassLoader
LatticeClassLoader扩展Java双亲委派模型,支持多自定义类加载器的委托加载。类加载失败后依次尝试自定义加载器,实现插件化容错;资源获取优先父加载器,支持单资源查找与多资源聚合,适用于插件系统、多租户隔离及SPI扩展,保障业务隔离与灵活扩展。
168 1
|
4月前
|
运维 监控 供应链
Alibaba交易平台TMF2.0介绍
2017双11交易峰值达32.5万笔/秒,面对高并发与复杂业务需求,阿里推出TMF2.0框架,通过业务与平台分离、全链路可视化、配置化发布等创新,实现需求开发周期缩短至12天,支撑多业务快速试错与复用,构建可配置、可监控、可运维的电商技术新体系。
642 5
Alibaba交易平台TMF2.0介绍
|
算法 5G
【5G NR】手机身份证号IMEI与IMEISV
手机上的“IMEI”是指国际移动设备识别码(IMEI),通常称为手机序列号。它用于识别移动网络中的各个独立手机和其他移动通信设备,相当于手机身份证号,它是全球唯一的。
1836 0
【5G NR】手机身份证号IMEI与IMEISV
|
4月前
|
缓存 前端开发 Java
【lattice】 lattice-dynamic-loading 深度源码分析
lattice-dynamic-loading 模块实现插件热加载与运行时扩展,通过独立类加载器保障隔离性,集成 Spring 生态,支持动态注册 Bean 与 MVC。基于策略模式、门面模式和 SPI 机制,实现高扩展性、可维护性与热部署能力,提升系统敏捷性。(239字)
150 1
|
22天前
|
缓存 监控 NoSQL
淘宝商品详情API性能优化:Java缓存策略与数据同步方案
你想针对 Java 调用淘宝商品详情 API 的场景,优化性能并设计可靠的缓存策略与数据同步方案,核心目标是减少 API 调用次数、降低延迟、保证数据一致性。我会从「缓存策略选型 → 多级缓存设计 → 数据同步方案 → 生产级优化落地」展开,结合淘宝 API 的特性(调用成本高、数据更新频率中等),给出可落地的企业级方案。
|
4月前
|
Dubbo Java 测试技术
【Lattice】设计原理
Lattice 是一个轻量级业务扩展调用框架,通过模块化架构实现复杂业务定制的高效管理。支持动态发现、加载与执行扩展,提供清晰的分层设计,集成 Spring、Dubbo 等主流技术,助力企业应用灵活扩展。
381 0
|
8月前
|
安全 芯片 Windows
U盘插上后显示为空?其实数据没丢,可以这样恢复
U盘变空并不等于数据丢失!本文详解U盘插入提示“格式化”、显示为空等常见问题的原因,教你如何在不格式化的前提下恢复数据,修复异常,并避免再次发生。内容涵盖逻辑错误识别、恢复软件使用步骤及U盘是否还能继续使用的判断方法,助你轻松应对U盘故障。
|
7月前
|
数据采集 缓存 前端开发
如何开发门店业绩上报管理系统中的商品数据板块?(附架构图+流程图+代码参考)
本文深入讲解门店业绩上报系统中商品数据板块的设计与实现,涵盖商品类别、信息、档案等内容,详细阐述技术架构、业务流程、数据库设计及开发技巧,并提供完整代码示例,助力企业构建稳定、可扩展的商品数据系统。
|
4月前
|
缓存 测试技术 双11
【Lattice】最佳实践
Lattice-Model 支持多租户SaaS、电商营销、ERP行业定制及微服务扩展,通过插件化实现业务隔离与动态加载,提升系统灵活性与可维护性。
205 0