ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AutoValue 扩展机制完全指南:使用与编写 AutoValueExtension

AutoValue 扩展机制完全指南:使用与编写 AutoValueExtension 代码生成开发工具【免费下载链接】autoA collection of source code generators for Java.项目地址https://gitcode.com/gh_mirrors/auto/auto点击查看免费下载导读AutoValue 是 Java 生态中知名的源代码生成器本仓库value模块即其实现它通过注解处理自动为AutoValue类生成equals、hashCode、toString以及构造器、Builder 等样板代码。但固定功能无法覆盖所有需求因此 AutoValue 提供了可插拔的扩展Extension机制任何开发者都可以编写一个AutoValueExtension将其放入编译期的processorpath即可为被AutoValue注解的类注入新的生成行为。本文以仓库内 value/userguide/extensions.md 为骨架结合 AutoValueExtension 源码、AutoValueProcessor 源码 与真实扩展实现Memoize、Serializable完整讲解扩展的加载方式、编写步骤、子类链生成原理与增量注解处理约定读完你就能动手写出自己的 AutoValue 扩展。一、扩展机制概述AutoValue 为何需要扩展默认情况下AutoValue 为每个AutoValue类生成一个直接子类如AutoValue_Foo其中包含属性字段、构造器、以及equals/hashCode/toString的实现。但有些新功能无法通过修改 AutoValue 核心生成器实现例如按需缓存方法结果、为不可序列化字段生成序列化代理、把Parcelable接口的抽象方法落地为具体实现。扩展机制解决的正是这类需求允许第三方在 AutoValue 生成代码的过程中介入额外生成新的子类来覆写或补充行为。原文档开篇即点明AutoValue can be extended to implement new features for classes annotated withAutoValue.也就是说扩展并不是修改 AutoValue 本身而是像插件一样挂在编译管线中与AutoValueProcessor协同工作。从源码看AutoValueProcessor通过ServiceLoader风格加载所有扩展见 AutoValueProcessor.java#L100-L108 的extensionsFromLoader方法并在处理每个AutoValue类时逐一询问扩展是否参与生成applicable。这一整套插件即类、类即 JAR、JAR 即服务的设计就是本文要展开的核心。二、使用扩展三步让扩展生效原文档指出每个扩展就是一个类只要满足两个条件即可在编译时自动运行该扩展类位于编译器的processorpath上与AutoValueProcessor同路径该扩展类能被ServiceLoader机制发现具体约束见下文ServiceLoader 三大约束。因此对一个使用者而言使用扩展通常只需要三步把扩展所在的 JAR 依赖加入项目的 annotation processor 路径Maven 中使用annotationProcessorPathsGradle 中使用annotationProcessor配置在AutoValue类上按扩展文档要求添加触发注解如果有例如Memoized或SerializableAutoValue正常编译扩展生成的子类会自动进入类层级无需手工引用。原文档特别提醒Some extensions are triggered by their own annotations... others may be triggered in other ways. Consult the extensions documentation for usage instructions.——扩展的触发方式各不相同有的靠专属注解如Memoized有的靠实现特定接口如SerializableAutoValueExtension要求类实现Serializable使用前务必阅读对应扩展的文档。仓库内就内置了多个真实扩展可作为开箱即用的示范扩展触发方式功能源码位置MemoizeExtension方法上标注Memoized为标注方法生成线程安全的缓存字段与覆写实现MemoizeExtension.javaSerializableAutoValueExtension类实现Serializable且标注SerializableAutoValue为含不可序列化字段的类生成序列化代理SerializableAutoValueExtension.javaToPrettyStringExtension类标注ToPrettyString生成美化输出方法ToPrettyStringExtension.java其中MemoizeExtension与SerializableAutoValueExtension都标注了AutoService(AutoValueExtension.class)且声明为ISOLATING增量类型正是标准扩展写法的教科书范例。三、编写扩展继承 AutoValueExtension编写一个扩展的核心就是写一个继承com.google.auto.value.extension.AutoValueExtension的类。该抽象类位于 value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java其中只有一个必须实现的方法public abstract String generateClass( Context context, String className, String classToExtend, boolean isFinal);其余方法均提供默认实现可按需覆写。一个最小可用的扩展如下public final class MyExtension extends AutoValueExtension { Override public String generateClass( Context context, String className, String classToExtend, boolean isFinal) { // 返回 null 表示本扩展不生成任何类 return null; } }3.1 核心生命周期方法扩展在一次生成周期中会被 AutoValueProcessor 依次调用以下方法调用顺序见 AutoValueProcessor.java#L340-L369 的applicableExtensions与writeExtensions方法默认行为说明applicable(Context)返回false判断本扩展是否参与当前类的处理。返回false后该扩展在本次处理中不再被调用mustBeFinal(Context)返回false声明本扩展生成的类必须是继承链中最末端的 final 类。同一上下文中只允许一个扩展返回true多个扩展同时要求 final 会触发编译错误consumeProperties(Context)返回空集声明由扩展接管某些属性按属性名这些属性将从 Builder、构造器、toString/equals/hashCode中移除consumeMethods(Context)返回空集声明由扩展实现某些抽象方法按ExecutableElementAutoValue 不再尝试实现它们也不再对其报无法处理的警告consumeBuilderMethods(Context)返回空集与上类似针对AutoValue.Builder中的抽象方法generateClass(...)抽象方法必须实现返回生成的类源码字符串返回null表示不生成incrementalType(ProcessingEnvironment)UNKNOWN声明扩展的增量注解处理类型见第五节getSupportedOptions()读取SupportedOptions注解声明扩展支持的编译选项会被合并进 AutoValueProcessor 的 supported options关键语义consumeProperties与consumeMethods。原文档对扩展机制的目标场景描述得非常清楚AutoValue 会把AutoValue类中的每个无参抽象方法都当作属性 getter来生成实现。但有些抽象方法并非属性——例如 Android 的Parcelable接口有int describeContents()和void writeToParcel(Parcel, int)它们既不是属性也不是合法 getter。扩展可以通过consumeProperties返回{describeContents}让describeContents不再被当作属性塞进 Builder 和构造器源码注释中对此有完整举例见 AutoValueExtension.java#L429-L438通过consumeMethods返回writeToParcel对应的ExecutableElement让 AutoValue 不再抱怨这个既不是属性 getter 也不是 Builder 转换方法的抽象方法。从源码看这两个入口分别对应AutoValueProcessor中的methodsConsumedByExtensions与builderMethodsConsumedByExtensionsAutoValueProcessor.java#L371 起。而且消费是有严格校验的消费一个不存在的属性/方法、消费一个已被其他扩展消费的成员、消费一个非抽象方法都会导致编译错误——这些错误场景在 ExtensionTest.java 中有完整测试用例如testCantConsumeTwice、testCantConsumeNonExistentProperty、testCantConsumeConcreteMethod。3.2 Context扩展获取信息的窗口generateClass的第一个参数是AutoValueExtension.Context接口它封装了一次代码生成周期的全部上下文信息AutoValueExtension.java#L81-L213主要包括方法返回内容processingEnvironment()注解处理环境可用getMessager()输出警告/错误packageName()生成类所在包名autoValueClass()被AutoValue注解的类TypeElementfinalAutoValueClassName()继承链末端类的全限定名如foo.bar.AutoValue_Bazproperties()有序的属性名 → getterExecutableElement映射propertyTypes()属性名 → 最终TypeMirror映射推荐用这个而非 getter 返回类型因为泛型替换后类型可能不同如interface ParentT { T bar(); }中的bar在Foo implements ParentString中实际类型是StringabstractMethods()类中全部抽象方法含被消费的builderAbstractMethods()Builder 类中的全部抽象方法无 Builder 时为空集classAnnotationsToCopy(TypeElement)配合AutoValue.CopyAnnotations返回需拷贝到子类的类注解methodAnnotationsToCopy(ExecutableElement)需拷贝到覆写方法上的方法注解builder()若存在AutoValue.Builder返回OptionalBuilderContext其中BuilderContextAutoValueExtension.java#L216-L318进一步提供了builderType()、toBuilderMethods()、builderMethods()、buildMethod()、autoBuildMethod()、setters()、propertyBuilders()等信息供扩展生成 Builder 子类时使用。有一点要特别注意源码 Javadoc 有明确说明在applicable()阶段调用builder()会得到Optional.empty()。如果扩展需要在applicable中根据 Builder 信息决策应当先让applicable返回true然后在generateClass中自行判断——若最终不需要生成代码返回null即可。这种先申请、后确认的模式比在applicable里读取 Builder 更灵活。四、ServiceLoader 注册让扩展被编译器发现原文档强调AutoValueExtension依赖 Java 标准的ServiceLoader机制发现扩展这意味着你的扩展类必须满足三大约束类必须是 public 的且提供 public 无参构造器类的全限定名必须出现在名为META-INF/services/com.google.auto.value.extension.AutoValueExtension的文件中该文件必须位于编译器classpath或processorpath上的某个 JAR 内。该约束在 AutoValueExtension.java#L36-L41 的 Javadoc 中同样有完整陈述。4.1 用 AutoService 简化注册手工维护META-INF/services文件容易出错AutoValue 官方推荐使用 AutoService 注解自动生成注册文件。只需在扩展类上标注import com.google.auto.service.AutoService; import com.google.auto.value.extension.AutoValueExtension; AutoService(AutoValueExtension.class) public final class MyExtension extends AutoValueExtension { // ... }编译后 AutoService 处理器会自动生成对应的META-INF/services文件。仓库内的MemoizeExtension、SerializableAutoValueExtension等真实扩展均采用这一写法见 MemoizeExtension.java#L80。4.2 加载失败的容错AutoValueProcessor在init()阶段通过SimpleServiceLoader.load(AutoValueExtension.class, loader)加载扩展并做了两层容错AutoValueProcessor.java#L114-L132捕获所有RuntimeException与Error若抛出ServiceConfigurationError典型原因processorpath中有损坏的 JAR会以[AutoValueExtensionsException]前缀输出警告然后静默降级为无扩展继续运行保证 AutoValue 核心功能不受影响。这一行为有专门的测试守护ExtensionTest.testBadJarDoesntBlowUp会构造一个内容为bogus line的伪造META-INF/services条目 JAR并断言编译仍成功且生成了AutoValue_BazExtensionTest.java#L683-L721。五、子类链扩展如何改写生成结果这是扩展机制最核心也最巧妙的设计。原文档的表述是Without extensions, AutoValue generates a subclass of theAutoValueclass. Extensions can work by generating a chain of subclasses, each of which alters behavior by overriding or implementing new methods.即扩展不是改写 AutoValue 的生成代码而是在其基础上再叠加一层子类。AutoValueExtension源码 Javadoc 给出了完整示意AutoValueExtension.java#L49-L67AutoValue abstract class Foo {...} // 手写的抽象类 abstract class $$AutoValue_Foo extends Foo {...} // AutoValue 处理器生成 abstract class $AutoValue_Foo extends $$AutoValue_Foo {...} // 第一个扩展生成 final class AutoValue_Foo extends $AutoValue_Foo {...} // 第二个扩展生成末端关于这条链有几点必须明确均有源码依据链首固定直接继承手写Foo的始终是 AutoValue 处理器自己生成的类链尾固定声明了mustBeFinal的扩展生成末端 final 类若无人声明链尾就是 AutoValue 处理器生成的类中间顺序未定义多个扩展的先后顺序不保证除首尾外不应对顺序做任何假设末端命名固定类名始终是AutoValue_Foo这也是Foo内部代码如new AutoValue_Foo(...)引用的类名非末端必须为 abstract只有链上最后一个类可以且必须若isFinal true声明为final其余一律应为abstract。从 AutoValueProcessor.java#L319-L338 的writeExtensions实现可以看到具体机制处理器按$前缀计数生成类名AutoValue_Foo、$AutoValue_Foo、$$AutoValue_Foo……依次调用每个扩展的generateClass把前一个扩展的输出作为后一个的父类扩展返回null则跳过。而 ExtensionTest.java#L521-L564 的testLastExtensionGeneratesNoCode等四个测试专门验证了中间某个扩展不生成代码时整条链依然正确闭合。5.1 构造器契约每个扩展生成的类都必须提供一个构造器其参数与context.propertyTypes()中的所有属性一一对应按顺序、参数名对应属性名并在构造器体内以相同参数调用super(...)。该构造器至少需要包可见性package-private。一个最小合法模板如下源自 AutoValueExtension.java#L490-L509 的 Javadoc 示例package package; finalOrAbstract class className extends classToExtend { className(constructorParameters) { super(constructorParameterNames); // 扩展自有的初始化逻辑 } // 覆写或新增的方法 }MemoizeExtension的构造器实现可作为实战参考MemoizeExtension.java#L201-L218它遍历context.propertyTypes()添加构造参数再对属性名做关键字规避处理generateIdentifier最后以super(参数列表)调用父类构造器。5.2 Builder 子类如果AutoValue类带有 Builder扩展还可以生成 Builder 的子类。此时需要检查BuilderContext.toBuilderMethods()若非空则 Builder 子类必须提供拷贝构造器形如finalOrAbstract class className extends classToExtend { ... static class Builder extends classToExtend.Builder { Builder() {} Builder(autoValueClass copyFrom) { super(copyFrom); } ... } }ExtensionTest中的BuilderExtensionExtensionTest.java#L847-L926演示了如何生成带doSomething()覆写的 Builder 子类testDoesntRaiseWarningForToBuilder则验证了 AutoValue 处理器自身生成的toBuilder()会调用new AutoValue_Baz.Builder而不是new Builder从而保证扩展的 Builder 子类真正生效。5.3 覆写什么因为 AutoValue 生成的类是链首扩展生成的子类可以覆写它的任何方法hashCode()、toString()、属性 getter 等。典型场景MemoizeExtension覆写被Memoized标注的方法含hashCode生成private transient volatile缓存字段与双重检查锁逻辑MemoizeExtension.java#L275-L465扩展还可以覆写equals如MemoizeExtension在 hashCode 被缓存时生成先比hashCode()再委托super.equals(that)的优化版本。六、增量注解处理声明扩展的增量类型从 Gradle 4.8 起支持增量注解处理AutoValueExtension为此提供了IncrementalExtensionType枚举AutoValueExtension.java#L331-L359取值含义UNKNOWN增量性未知会整体禁用增量注解处理默认值AGGREGATING聚合型输出可能依赖多个被注解的输入类ISOLATING隔离型输出只依赖当前AutoValue类及其依赖AutoValue 扩展最常见的选择覆写incrementalType(ProcessingEnvironment)即可声明例如MemoizeExtension返回ISOLATING。整个 AutoValueProcessor 的实际增量类型是所有已加载扩展中最宽松的那一个getSupportedOptions()中按枚举自然序取最小值UNKNOWN AGGREGATING ISOLATING见 AutoValueProcessor.java#L138-L142。因此只要有一个扩展返回UNKNOWN增量处理就会被整体关闭——这也是每个正式扩展都应认真声明增量类型的原因。七、测试你的扩展编写扩展后仓库提供了成熟的测试范式可直接借鉴。核心思路是用AutoValueProcessor(Iterable? extends AutoValueExtension testExtensions)构造器把扩展直接注入处理器AutoValueProcessor.java#L85-L88配合com.google.testing.compile的javac()API 在内存中编译并断言生成的源码Compilation compilation javac() .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension()))) .compile(javaFileObject); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile(foo.bar.AutoValue_Baz) .hasSourceEquivalentTo(expectedExtensionOutput);完整的测试套件在 ExtensionTest.java共 1416 行覆盖了基本生成、消费属性、Builder 协同、多扩展组合、错误校验重复消费、消费不存在的成员、损坏 JAR 容错、SupportedOptions选项透传等MemoizeExtension、SerializableAutoValueExtension也各有独立的测试目录见 MemoizedTest.java 与 SerializableAutoValueExtensionTest.java。八、编写扩展的实践要点清单综合原文档与仓库源码编写扩展时请重点核对以下事项继承与注册public final class继承AutoValueExtension提供 public 无参构造器用AutoService(AutoValueExtension.class)生成META-INF/services注册文件判断适用性覆写applicable(Context)按自己的触发条件返回布尔值若需要在applicable中获取 Builder 信息改为先返回truegenerateClass中再决定是否返回null实现 generateClass生成源码要包含与propertyTypes()一一对应的构造器并调用super(...)非末端类声明为abstract末端类isFinal true声明为final消费抽象成员对想接管实现的属性/方法通过consumeProperties/consumeMethods/consumeBuilderMethods声明返回的成员必须是Context中真实存在的抽象成员且不要与其他扩展冲突声明增量类型默认UNKNOWN会禁用增量编译尽量返回ISOLATING大多数 AutoValue 扩展属于此类声明支持的选项需要自定义-A编译选项时覆写getSupportedOptions()或用SupportedOptions注解标注测试先行参照ExtensionTest用内存编译断言生成源码覆盖正常路径与所有错误路径。原文档末尾的 TODO扩展分发方式、已知扩展列表在仓库中尚未补齐但本仓库自带的MemoizeExtension、SerializableAutoValueExtension、ToPrettyStringExtension三个扩展位于 value/src/main/java/com/google/auto/value/extension 下就是已知扩展的最佳实物样例既有 Javadoc 又有测试适合作为学习与二次开发的起点。参考与延伸阅读本文骨架文档value/userguide/extensions.md扩展 API 定义AutoValueExtension.java扩展编排与加载AutoValueProcessor.java上下文实现ExtensionContext.java内置扩展源码与测试value/src/main/java/com/google/auto/value/extension、ExtensionTest.java注册文件生成利器service 模块AutoService用户指南其他章节value/userguide/index.md赞分享代码生成开发工具【免费下载链接】autoA collection of source code generators for Java.项目地址https://gitcode.com/gh_mirrors/auto/auto点击查看免费下载相关推荐AutoValue 扩展机制深度指南使用、编写与源码剖析Extensions for AutoValueAutoValue 扩展机制深度指南使用、编写与源码剖析Extensions for AutoValue AutoValue 是 Google Auto开发工具代码生成Python MCP SDK 扩展Extensions机制完全指南编写、使用与拦截 tools/callPython MCP SDK 扩展Extensions机制完全指南编写、使用与拦截 tools/call 扩展Extensions是 MCP SDK人工智能MCP 服务MCP ClientsFrankenPHP 扩展开发完全指南使用 Go 编写 PHP 扩展模块FrankenPHP 扩展开发完全指南使用 Go 编写 PHP 扩展模块 FrankenPHP 允许开发者使用 Go 语言编写 PHP 扩展模块将高性能的原后端上一篇如何在3分钟内实现Rhino到Blender的无缝3D模型导入下一篇5分钟终极指南在Blender中完美导入Rhino 3dm文件的完整教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表