| 构件 | io.github.microsphere-projects:microsphere-lang-model(release 线上为 0.3.20-SNAPSHOT;已发布标签 0.3.20) |
| 包 | io.microsphere.lang.model.element(1 个类型)与 io.microsphere.lang.model.util(15 个类型)——共 16 个主源类型 |
| 包装对象 | javax.lang.model —— Element、TypeMirror、DeclaredType、AnnotationMirror、AnnotationValue、Elements、Types、Messager、ProcessingEnvironment |
| JDK 级别 | 整个 reactor 中不存在任何 maven.compiler.* / <release> 属性;字节码级别继承自外部构建父工程 microsphere-build:0.3.16。CI 在 Temurin JDK 8 / 11 / 17 / 21 / 25 上验证 |
| 编译期依赖 | microsphere-java-core(microsphere-lang-model/pom.xml:24-28,版本 ${revision}) |
| 仅测试依赖 | microsphere-jdk-tools(:44-49)、microsphere-annotation-test(:52-57),另有 JUnit 5、Logback、javax.ws.rs-api、jaxws-api、spring-context、spring-web |
| 被谁使用 | microsphere-annotation-processor —— 本模块是该配对中的构建期元数据一半 |
所有 file:line 引用均相对于 microsphere-lang-model/src/main/java/io/microsphere/(主源码)或
microsphere-lang-model/src/test/java/io/microsphere/(测试),读取自 release 分支工作树。首次提及之后简化为
lang/model/util/TypeUtils.java:60-88。跨模块引用保留模块名前缀,例如
microsphere-java-core/src/main/java/io/microsphere/reflect/TypeUtils.java:1543。
如果你要编写注解处理器,就需要本模块 —— 无论是 javax.annotation.processing.AbstractProcessor、构建期元数据生成器、API 文档抽取器,还是任何运行在 javac 之下、检查源码结构而非已加载类的程序。
在构建的这个阶段你并不拥有 Class、Method、Field。你拥有的是 Element、TypeMirror、ExecutableElement、AnnotationMirror 和 AnnotationValue,而向它们提问的唯一途径是从 ProcessingEnvironment 取得的两个 javax.lang.model.util 服务 Elements 与 Types。原始 API 的冗长体现在四个具体方面,本模块逐一消除:
- Kind 判断既无类型又不为空安全。
element.getKind() == ElementKind.CLASS会漏掉ENUM和RECORD,而ElementKind#isClass()遇到null会抛异常。ElementUtils提供空安全的isClass/isInterface/isField/isExecutable/isMember/isVariable/isInitializer(lang/model/util/ElementUtils.java:87-296)。 - 不存在
TypeMirror与java.lang.reflect.Type的比较。 没有任何Elements/Types方法能回答“这个TypeMirror是否就是java.lang.String?”。TypeUtils.isSameType可以,且有四个重载(lang/model/util/TypeUtils.java:177-266)。 - 注解属性提取是一趟四层手工遍历:
AnnotationMirror→getAnnotationType()→ 属性ExecutableElement→getElementValues()→AnnotationValue→getValue()—— 而且除非你重新读取ExecutableElement.getDefaultValue(),默认值在getElementValues()中是缺失的。AnnotationUtils.getElementValues完成整趟遍历并合并默认值(lang/model/util/AnnotationUtils.java:1509-1529)。 - 诊断需要两个通道。
Messager写入构建输出;Logger写入处理器自身的日志,而通常你希望用同一条消息模式同时得到两者。MessagerUtils.printMessage正是如此(lang/model/util/MessagerUtils.java:292-309)。
当你已拥有加载好的 Class 对象时不要使用本模块 —— 那属于 microsphere-java-core 中的 io.microsphere.reflect,它更快且不依赖 ProcessingEnvironment。同样,不要在 jlink 精简镜像的运行期使用它:javax.lang.model 位于 java.compiler 模块,可能不存在。
<dependency>
<groupId>io.github.microsphere-projects</groupId>
<artifactId>microsphere-lang-model</artifactId>
<version>0.3.20</version> <!-- newest published tag; 0.3.20-SNAPSHOT is this same line's working version -->
</dependency>| 事实 | 取值 | 证据(release) |
|---|---|---|
groupId |
io.github.microsphere-projects |
根 pom.xml:12 |
artifactId |
microsphere-lang-model |
microsphere-lang-model/pom.xml:14 |
| 版本属性 | ${revision} |
pom.xml:15 |
release 线上 ${revision} 的值 |
0.3.20-SNAPSHOT |
根 pom.xml:54 |
| 最新已发布标签 | 0.3.20 |
release 分支的发布线 |
| 构建父工程 | microsphere-build:0.3.16(外部) |
reactor 中不存在 maven.compiler.* / <release> 属性 |
| 编译依赖 | microsphere-java-core,${revision} |
pom.xml:24-28 |
| 测试依赖 | JUnit 5 :31-41、microsphere-jdk-tools :44-49、microsphere-annotation-test :52-57、Logback :60-64、javax.ws.rs-api :67-71、jaxws-api :74-78、spring-context :81-85、spring-web :87-91 |
同上 |
| CI 矩阵 | Temurin JDK 8, 11, 17, 21, 25 |
.github/workflows/maven-build.yml:25、distribution: 'temurin' :33、-Dsurefire.useSystemClassLoader=false :43 |
Important
microsphere-jdk-tools 与 microsphere-annotation-test 都是 <scope>test</scope>(pom.xml:48、:56)。它们不会出现在你的编译 classpath 上,你的处理器也不会传递性地得到它们。只有要运行本模块自身测试套件时才需要(见 §10 陷阱 1)。
microsphere-java-core 是唯一的 compile 作用域依赖,而本模块大量使用它:io.microsphere.util.*、io.microsphere.collection.*、io.microsphere.lang.function.*、io.microsphere.reflect.*、io.microsphere.json.JSONUtils、io.microsphere.text.FormatUtils、io.microsphere.logging.*、io.microsphere.constants.SymbolConstants 与 io.microsphere.annotation.* 全部来自那里。
| 问题 | 回答 | 证据 |
|---|---|---|
| 发布线 | release 分支,${revision} = 0.3.20-SNAPSHOT,已发布标签 0.3.20 |
根 pom.xml:54 |
全部 16 个类型的 @Since |
1.0.0 |
lang/model/util/ElementUtils.java:56、lang/model/util/TypeUtils.java:67、lang/model/util/AnnotationUtils.java:99、lang/model/element/StringAnnotationValue.java:30、lang/model/util/MessagerUtils.java:41、lang/model/util/ExecutableElementComparator.java:53 |
| 包命名空间 | javax.lang.model,绝不存在 jakarta.lang.model |
每个主源码都 import javax.lang.model.*,例如 lang/model/util/ElementUtils.java:24-28;src/main/java 中不存在任何 jakarta.* import |
javax.lang.model API 可用性 |
Element/Types/Elements/ElementVisitor/AnnotationValueVisitor 自 JDK 6 起存在;本模块所继承的基类 ElementKindVisitor6、SimpleAnnotationValueVisitor6、AbstractElementVisitor6 是 JDK 6 产物,并自 JDK 9 起被 ...8/...14 变体取代而废弃 |
lang/model/util/JSONElementVisitor.java:27-28,57、lang/model/util/JSONAnnotationValueVisitor.java:25,72、lang/model/util/ResolvableAnnotationValueVisitor.java:27,74 |
| 实际测试过的 JDK 范围 | 8、11、17、21、25 | .github/workflows/maven-build.yml:25 |
| Spring 版本 | 与 src/main/java 无关(无 Spring import);spring-context/spring-web 仅测试用 |
pom.xml:81-91;父工程属性 microsphere-java-parent/pom.xml:21-24(microsphere-bom.version 0.3.13 在 :22,spring-framework.version 7.0.9 在 :23)、BOM 导入 :31-37 与 :40-46、profile java8-16 :54-62 中 <jdk>[1.8,17)</jdk> 在 :57,将 Spring 固定为 5.3.39 在 :60 |
RECORD / BINDING_VARIABLE kind |
按名称匹配而非按枚举常量,因此模块在 JDK 8 上仍可编译 | lang/model/util/ElementUtils.java:358-359 |
Note
直接回答包命名空间问题:是的,本模块针对 javax.* 编译,而不是 jakarta.*。 JDK 从未重命名 javax.lang.model —— Jakarta EE 的命名空间变更只作用于 Jakarta EE API,不作用于 Java 语言模型。因此不存在迁移到“jakarta.lang.model”的路径;即便使用 jakarta.annotation 的处理器,也仍然通过本模块所包装的 javax.lang.model API 检查其元素。模块中唯一带 Jakarta 色彩的 import 都是测试作用域且仍是 javax 的(pom.xml:67-78)。
| 条目 | 值 | 依据 |
|---|---|---|
| 文档所在线 | release 分支,${revision} = 0.3.20-SNAPSHOT |
根 pom.xml:54 |
| 最新已发布版本 | 0.3.20,标签日期 2026-10-16 |
标签 0.3.20 的提交信息为 chore: merge main into release [skip ci],提交时间 2026-10-16T03:43:20Z |
| 上一版本 | 0.3.19,标签日期 2026-08-30 |
标签 0.3.19,提交时间 2026-08-30T12:51:37Z |
| 变更日志来源 | 仓库根目录的 release-notes.md |
本项目不发布 CHANGELOG.md;release-notes.md 的 v0.3.16–v0.3.20 段落写着 Release notes generation failed 并保留原始提交列表,因此下文一律以提交主题引用,并逐条与 release 源码核对。 |
0.3.20 在这一区域没有新增 API 面——0.3.19...0.3.20 区间是一次加固性发布。它实际改变了什么、以及这些改动触及本页源码的程度如下:
- 空值契约。 本页覆盖范围内 8 / 16 个主源码在签名上声明了
@Nonnull/@Nullable。没有单独点名本区域的提交,由全库性的34e27823 Refine nullability contracts across core APIs覆盖。 - 该区间没有其它改动触及本页覆盖的区域。
Note
如何读这份提交清单:该模块一半源码带注解——AnnotatedElementJSONElementVisitor、AnnotationUtils、ConstructorUtils、ElementUtils、FieldUtils、MemberUtils、MethodUtils、TypeUtils——但没有任何提交点名 microsphere-lang-model,契约改造在未单独提交的情况下覆盖了它。
ElementUtils 是一个继承 io.microsphere.util.Utils 的 interface(lang/model/util/ElementUtils.java:58),因此它是 static 方法的命名空间,而非可注入的服务。
| 方法 | 对以下值返回 true |
证据 |
|---|---|---|
isClass(ElementKind) |
CLASS、ENUM、RECORD(委托给 ElementKind#isClass()) |
:87-89 |
isInterface(ElementKind) |
INTERFACE、ANNOTATION_TYPE |
:118-120 |
isDeclaredType(ElementKind) |
上述两类之一 | :149-151 |
isField(ElementKind) |
FIELD、ENUM_CONSTANT |
:180-182 |
isExecutable(ElementKind) |
METHOD、CONSTRUCTOR、STATIC_INIT、INSTANCE_INIT(自有 switch,不是 ElementKind#isExecutable()) |
:216-227 |
isMember(ElementKind) |
isField || isExecutable |
:256-258 |
isInitializer(ElementKind) |
STATIC_INIT、INSTANCE_INIT |
:287-296 |
isVariable(ElementKind) |
ENUM_CONSTANT、FIELD、PARAMETER、LOCAL_VARIABLE、EXCEPTION_PARAMETER、RESOURCE_VARIABLE,外加按名称匹配的 BINDING_VARIABLE |
:347-362 |
toElementKind(ElementType) 将 TYPE/TYPE_USE 映射为 CLASS,其余走 ElementKind.valueOf(name),null 返回 OTHER(:389-400)。三个 matchesElementType 重载基于它构建 —— (ElementKind, ElementType) :430-432、(ElementKind, ElementType...) :461-472、(Element, ElementType...) :500-502 —— 而 matchesElementKind(Element, ElementKind) :526-528 是直接形式。其余一组是修饰符/过滤/签名:isPublicNonStatic :550-552、hasModifiers(Element, Modifier...) :578-589、filterElements(List, Predicate...) :622-633(AND 组合,返回 unmodifiableList)、matchParameterTypes(ExecutableElement, Type...) :656-659、matchParameterTypes(List<? extends VariableElement>, Type...) :682-684 以及 matchParameterTypeNames(List, CharSequence...) :714-733 —— 后者先比较参数个数,再逐个比较参数类型名。
取自 lang/model/util/ElementUtilsTest.java:100-136;echoMethod 是 TestServiceImpl#echo(String),在 beforeTest 中一次性解析(ElementUtilsTest.java:94-98):
// ElementUtilsTest.java:102-103
assertTrue(matchesElementKind(echoMethod, METHOD));
assertFalse(matchesElementKind(this.echoMethod, FIELD));
// ElementUtilsTest.java:114-117 — Integer#valueOf(String) is public *static* and must not match
methodsIn(getDeclaredMembers(testTypeElement)).forEach(method -> assertTrue(isPublicNonStatic(method)));
assertFalse(isPublicNonStatic(findMethod(getTypeElement(Integer.class), "valueOf", String.class)));
// ElementUtilsTest.java:127-129
List<VariableElement> fields = fieldsIn(getAllDeclaredMembers(testTypeElement.asType()));
assertTrue(hasModifiers(fields.get(0), PROTECTED));
// ElementUtilsTest.java:336-337 — parameter types compared by type name
assertTrue(matchParameterTypeNames(this.echoMethod.getParameters(), "java.lang.String"));
assertFalse(matchParameterTypeNames(this.echoMethod.getParameters(), "java.lang.Object"));来源:lang/model/util/ElementUtilsTest.java:100-136,335-338
TypeUtils(lang/model/util/TypeUtils.java:69)是模块中最大的类型:约上百个 static 方法,分为五族,多数成对出现,一个接收 Element,另一个接收 TypeMirror。
| 族 | 代表方法 | 证据 |
|---|---|---|
| 类型形态谓词 | isSimpleType、isSameType、isArrayType、isEnumType、isClassType、isPrimitiveType、isInterfaceType、isAnnotationType、isTypeElement、isDeclaredType |
:120-628(成对位于 :120/:149、:177/:203/:235/:261、:289/:314、:340/:367、:387/:408、:428/:448、:468/:489、:509/:530、:555/:579、:604/:628) |
| 转换器 | ofTypeElement(Element/TypeMirror/DeclaredType)、ofDeclaredType(Element/TypeMirror)、ofTypeMirrors、ofTypeElements、ofDeclaredTypes |
:652-789 |
| 父类型遍历 | getTypeElementOfSuperclass、getAllTypeElementsOfSuperclasses、getTypeElementsOfInterfaces、getAllTypeElements、findTypeElements、getDeclaredTypeOfSuperclass、getAllDeclaredTypes、findDeclaredTypes |
:1013-2272(getAllTypeElements :1169、findTypeElements :1360-1365) |
ProcessingEnvironment 桥接 |
getTypeMirrors(env, Type...)、getTypeMirror(env, Type)、getTypeElement(env, Type/TypeMirror/CharSequence)、getDeclaredType(env, Type/TypeMirror/CharSequence) |
:2287-2433 |
| 名称与 finder | toString(TypeMirror)、getTypeName(TypeMirror)、typeElementFinder(...) |
:2445-2505 |
基于名称的比较核心只有五行(:261-266),SIMPLE_TYPE_NAMES(:77-82)是本模块视作“简单类型”的不可变名称列表 —— isSimpleType 就是 SIMPLE_TYPE_NAMES.contains(type.toString())(:149-151)。两个 TypeElement 遍历函数在 :88 与 :94-97 声明为 public static 的 Function 常量。
static boolean isSameType(TypeMirror type, CharSequence typeName) {
if (type == null && typeName == null) {
return true;
}
return Objects.equals(valueOf(type), valueOf(typeName));
}测试基类 lang/model/util/UtilTest.java:55-92 各处使用 TypeUtils,把依赖 ProcessingEnvironment 的查找包起来,使单个测试不必重复它们。
// UtilTest.java:59-69
protected TypeMirror getTypeMirror(Type type) { return TypeUtils.getTypeMirror(processingEnv, type); }
protected TypeElement getTypeElement(Type type) { return TypeUtils.getTypeElement(processingEnv, type); }
// UtilTest.java:87-88 — Element[] built from Types via TypeMirror then TypeElement
return getTypeMirrors(types).stream().map(TypeUtils::ofTypeElement).toArray(Element[]::new);
// JSONElementVisitorTest.java:237-240 — a TypeMirror back to a TypeElement, then its type parameters
TypeMirror superclass = getTypeElement(StringArrayList.class).getSuperclass();
List<? extends TypeParameterElement> typeParameters = ofTypeElement(superclass).getTypeParameters();来源:lang/model/util/UtilTest.java:55-92、lang/model/util/JSONElementVisitorTest.java:236-245
AnnotationUtils(lang/model/util/AnnotationUtils.java:101)有四个值得记住的常量:VALUE_ATTRIBUTE_NAME = "value"(:106)、DEFAULT_ANNOTATION_VALUE_VISITOR —— 一个共享的 ResolvableAnnotationValueVisitor(:111)、EMPTY_ELEMENT_TYPE_ARRAY(:117)以及 WITH_DEFAULT = true(:119)。
| 你想要什么 | 调用 | 证据(lang/model/util/AnnotationUtils.java) |
|---|---|---|
| 指定注解的第一个 mirror | getAnnotation(AnnotatedConstruct, Class | CharSequence) |
:141-146、:169-175 |
| 仅直接存在的 mirror | getAnnotations(construct[, class | name]) |
:197-202、:226-231、:256-261 |
| 包含继承而来的 mirror | getAllAnnotations(Element | TypeMirror | ProcessingEnvironment, ...) |
:282-287 … :480-485 |
| 在层级中查找某个注解 | findAnnotation(TypeMirror | Element, Class | CharSequence) |
:509-514、:539-544、:572-577、:606-612 |
| 元注解(注解之上的注解) | findMetaAnnotation、isAnnotationPresent(存在或元存在) |
:640-645、:674-692、:719-724、:752-757 |
| 谓词过滤 | findAnnotations(直接)、findAllAnnotations(跨层级) |
:790-807、:822-827、:831-847、:863-875、:892-897、:914-919 |
| 全部属性值并合并默认值 | getElementValues(...) |
:1373-1375、:1425-1427、:1464-1466、:1509-1529 |
以 Map<String, Object> 形式返回 |
getAttributesMap(...) |
:1190-1192、:1235-1237、:1272-1274、:1315-1329 |
| 单个属性 | getElementValue / getAttribute / getValue |
:1571-1602、:1646-1656、:1695-1697、:1738-1741、:1773-1782、:1811-1813 |
| 比较 | matchesAnnotationType、matchesAnnotationTypeName、matchesAttributeMethod、matchesAttributeValue、matchesDefaultAttributeValue |
:947-952、:980-985、:1052-1054、:1081-1091、:1117-1119、:1145-1150 |
注解的 @Target |
getElementTypes(AnnotationMirror | DeclaredType) |
:1841-1843、:1871-1875 |
默认值合并规则只在一个循环里(:1518-1527):map 由 getDeclaredMethods(annotationType) 构建,annotation.getElementValues() 提供显式值,当 withDefault 为 true 且显式值为 null 时用 attributeMethod.getDefaultValue() 替补。既无显式值又无默认值的属性会缺席于结果 map。
StringAnnotationValue(lang/model/element/StringAnnotationValue.java:32-49)是手写的、基于 String 的 AnnotationValue 实现,因此你自己合成的值可以喂给只接受 AnnotationValue 的代码。它是 @Immutable(:32),持有一个 final String(:35,37-39),getValue() 返回它(:41-44),而 accept 硬编码了双重分派(:46-49):
@Override
public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) {
return v.visitString(this.value, p);
}由于 accept 绕开了 JDK 自身的类型 switch,StringAnnotationValue 可被任何 AnnotationValueVisitor 接受,包括 JDK 的和下一小节的两个访问器。
| 访问器 | 基类 | 返回 | 用途 | 证据 |
|---|---|---|---|---|
ResolvableAnnotationValueVisitor |
SimpleAnnotationValueVisitor6<Object, ExecutableElement> |
Object |
把属性值解析为运行期 Java 对象 | lang/model/util/ResolvableAnnotationValueVisitor.java:74 |
JSONAnnotationValueVisitor |
SimpleAnnotationValueVisitor6<StringBuilder, ExecutableElement> |
共享的 StringBuilder |
为某个属性追加 "name":value JSON |
lang/model/util/JSONAnnotationValueVisitor.java:72 |
ResolvableAnnotationValueVisitor 有三个构造器(:88-99),由两个布尔开关控制:classValuesAsString(:84)与 nestedAnnotationsAsMap(:86)。visitType 返回 TypeMirror 字符串或加载后的 Class(:146-149);visitEnumConstant 加载枚举类并调用 Enum.valueOf(:151-155);visitAnnotation 构建 LinkedHashMap<String, Object>,并且除非设置了 nestedAnnotationsAsMap 或 JDK 钩子缺失,会反射进入 sun.reflect.annotation.AnnotationParser.annotationForMap(:157-174,钩子在 :76-78 解析);visitArray 需要属性方法才能得知组件类型(:176-195)。
JSONAnnotationValueVisitor 是一个薄薄的写入器:每个标量 visit 方法都是一次 append(jsonBuilder, getAttributeName(attributeMethod), value) 调用(:81-133)。三处不同 —— visitType 写入类型名(:135-139);visitEnumConstant 写入简单名(:141-145);visitAnnotation 与 visitArray 用新的 StringBuilder 上的子 JSONAnnotationValueVisitor 递归,再通过 doAppend 追加完成片段(:147-184、:191-195)。visitUnknown 原样返回 builder(:186-189)。
Note
JSON 输出经由 io.microsphere.json 类型产生:该访问器 import 了 JSONUtils.append 与 JSONUtils.appendName(lang/model/util/JSONAnnotationValueVisitor.java:36-37),花括号/方括号/逗号符号取自 io.microsphere.constants.SymbolConstants(:31-35)。它们写入的是 io.microsphere.json 的字符串构建层,而不是 JSONObject/JSONArray 对象 —— 并且要注意,兄弟类 JSONElementVisitor 直接追加到裸 StringBuilder,完全没有 import 任何 io.microsphere.json 类型。appendName 会整体跳过空白名称(microsphere-java-core/src/main/java/io/microsphere/json/JSONUtils.java:500-508),这正是 visitArray 把属性方法传成 null 的原因(JSONAnnotationValueVisitor.java:176):数组元素必须无名渲染。参见 JSON。
JSONElementVisitor(lang/model/util/JSONElementVisitor.java:57)继承 ElementKindVisitor6<Boolean, StringBuilder>,兜底值设为 FALSE(:59-61)。它的 visit* 方法都是 final,实现“先判断支持再决定”的门闸:visitPackage :63-69、visitVariable :71-77、visitExecutable :79-85、visitType :87-101、visitTypeParameter :103-109。扩展点是 protected 钩子 supports(Element)(:130-132,默认 true)、按 kind 划分的 supportsXxx 家族(:143-197)以及 doVisitPackage / doVisitTypeParameter(:206-219);visitMembers(List, StringBuilder)(:111-119)会为每个被包含的元素重新进入 accept。
AnnotatedElementJSONElementVisitor(lang/model/util/AnnotatedElementJSONElementVisitor.java:88)就是“只处理被 X 注解的元素”的过滤器:其构造器一次性解析注解并对非法输入快速失败(:102-112),supports(Element)(:124-126)借助 getElementTypes 取得的 @Target 集合收窄遍历(:111)。
JSONAnnotationValueVisitorTest.java:50-56,128-134 —— 取得测试类型上 @TestAnnotation 的属性 map,挑一个属性,visit 它,比较 JSON 文本:
this.jsonBuilder = new StringBuilder();
this.visitor = new JSONAnnotationValueVisitor(jsonBuilder);
this.testAnnotationAttributes = getElementValues(testTypeElement, TestAnnotation.class);
void testVisit(String attributeName, String expectedJson) {
Map.Entry<ExecutableElement, AnnotationValue> elementValue = getElementValue(this.testAnnotationAttributes, attributeName);
StringBuilder jsonBuilder = visitor.visit(elementValue.getValue(), elementValue.getKey());
assertEquals(expectedJson, jsonBuilder.toString());
}断言的输出(:58-121)就是所产生结构的权威事实:
| 属性 | 产生的 JSON | Kind |
|---|---|---|
"z" |
"z":true |
boolean :59-61 |
"c" |
"c":"b" |
char,带引号 :69-71 |
"d" / "f" |
"d":1.0 / "f":1.0 |
double / float :73-81 |
"type" |
"type":"io.microsphere.test.service.GenericTestService" |
Class 值 → 类型名 :103-106 |
"timeUnit" |
"timeUnit":"HOURS" |
枚举常量 → 简单名 :108-111 |
"since" |
"since":{"module":"","value":"1.0.0"} |
嵌套注解 → 对象 :113-116 |
"properties" |
"properties":[{"name":"key","type":"java.lang.String",…},{"name":"key2",…}] |
注解数组 :118-121 |
| unknown | (无输出) —— builder 原样返回 | :123-126 |
来源:lang/model/util/JSONAnnotationValueVisitorTest.java:50-56,58-134
至于 JSONElementVisitor,JSONElementVisitorTest.java:57-137 展示了正确的子类化形态 —— 覆写 JDK 的 visitTypeAsClass / visitVariableAsField / visitExecutableAsMethod,而不是 final 的 visitType / visitVariable / visitExecutable:
this.visitor = new JSONElementVisitor() {
@Override protected boolean supports(Element e) { return supported; }
@Override public Boolean visitTypeAsClass(TypeElement e, StringBuilder sb) {
jsonBuilder.append("visitTypeAsClass"); return TRUE;
}
};
assertTrue(visitor.visitType(getTypeElement(testClass), jsonBuilder)); // JSONElementVisitorTest.java:215-219
assertTrue(jsonBuilder.toString().startsWith("visitTypeAsClass"));来源:lang/model/util/JSONElementVisitorTest.java:57-137,214-219
五个类型共用一套形态 —— getDeclaredX(仅本类型)/ getAllDeclaredX(整个层级)/ findX / findDeclaredX(s) / filterDeclaredX(s) / isX / matchesX —— 且每个都是接收 TypeElement 或 TypeMirror 的 static 方法 interface。
| 类型 | 包装对象 | 关键方法(证据,均位于 lang/model/util/) |
|---|---|---|
ClassUtils |
TypeMirror → java.lang.Class |
getClassName(TypeMirror) ClassUtils.java:57、loadClass(TypeMirror) :78、loadClass(String) :98 |
FieldUtils |
VariableElement |
getDeclaredField(Element/TypeMirror, String) FieldUtils.java:107,135、getDeclaredFields :161,187、getAllDeclaredFields :213,239、findField :267,294、findDeclaredFields :330,368、findAllDeclaredFields :407,445、filterDeclaredFields :488、isEnumMemberField :524、isNonStaticField :554、isField :580,618、getNonStaticFields :644,674、getAllNonStaticFields :704,734、equalsFieldName :760 |
MethodUtils |
ExecutableElement(方法) |
getDeclaredMethods MethodUtils.java:86,111、getAllDeclaredMethods :136,161、findDeclaredMethods :196,231、findAllDeclaredMethods :264,300,415,451、findPublicNonStaticMethods :338,373、filterDeclaredMethods :487、isMethod :522、isPublicNonStaticMethod :546、findMethod(6 个重载):575,604,635,664,699,728、getOverrideMethod(env, …) :764、filterMethods :804、getMethodName :841、getReturnTypeName :866、getMethodParameterTypeMirrors :896、getMethodParameterTypeNames :938、matches :967,994、matchesMethodName :1021、matchesMethod :1048,1089、getEnclosingElement :1127、methodPredicateForExcludedTypes :1152 |
ConstructorUtils |
ExecutableElement(构造器) |
getDeclaredConstructors(TypeElement/TypeMirror) ConstructorUtils.java:87,123、findConstructor :151,179、findDeclaredConstructors :220,239、filterDeclaredConstructors :278 |
MemberUtils |
任意 Element 成员 |
getDeclaredMembers(TypeMirror/TypeElement) MemberUtils.java:98,122、getAllDeclaredMembers :146,170、getDeclaredMembers(..., boolean includeHierarchicalTypes) :196,222、findDeclaredMembers :250,278,388,419、findAllDeclaredMembers :310,349 |
getMethodName(MethodUtils.java:841-843)与 getReturnTypeName(:866-868)是这一族中容忍 null 的访问器 —— method == null ? null : method.getSimpleName().toString()。ClassUtils.loadClass 是最容易让人意外的那座桥:它经由 io.microsphere.util.ClassLoaderUtils 解析,因此类必须位于处理器自身的 classpath 上,而不仅仅是在被编译的源码路径上。ResolvableAnnotationValueVisitor.visitType 与 visitEnumConstant 都依赖它(lang/model/util/ResolvableAnnotationValueVisitor.java:148,153),这就是解析同一编译轮次中生成的类的 Class 型属性会失败的原因。
Tip
TypeElement 与 TypeMirror 重载并非冗余。当你来自 Element.asType() 或注解的 DeclaredType 时用 TypeMirror 形式;当你已持有 TypeElement 时用后者。两者最终汇入同一遍历,且都在 null 时返回 emptyList()。
// ElementUtilsTest.java:97 — resolve a method by name + parameter type *names*
this.echoMethod = findMethod(testTypeElement, "echo", "java.lang.String");
// ExecutableElementComparatorTest.java:41-43 — overloads resolved by parameter Type
TypeElement typeElement = getTypeElement(Integer.class);
ExecutableElement byInt = findMethod(typeElement, "valueOf", int.class);
ExecutableElement byString = findMethod(typeElement, "valueOf", String.class);
// ExecutableElementComparatorTest.java:49-53 — 1-parameter vs 3-parameter append
TypeElement sb = getTypeElement(StringBuilder.class);
findMethod(sb, "append", char[].class);
findMethod(sb, "append", char[].class, int.class, int.class);来源:lang/model/util/ElementUtilsTest.java:94-98、lang/model/util/ExecutableElementComparatorTest.java:38-54
lang/model/util/LoggerUtils.java:30-53 声明了一个名为 "microsphere-lang-model" 的共享 Logger(:32,取自 io.microsphere.logging.LoggerFactory.getLogger)与五个 static 转发方法,签名均为 (String format, Object... args):trace :34、debug :38、info :42、warn :46、error :50。
lang/model/util/MessagerUtils.java:43 暴露五对方法 —— 每对包含一个 ProcessingEnvironment 重载和一个 Messager 重载:
| 级别 | Diagnostic.Kind |
MessagerUtils |
LoggerUtils 级别 |
证据 |
|---|---|---|---|---|
| note | NOTE |
printNote(env|messager, …) |
info |
:63-65、:103-105 |
| warning | WARNING |
printWarning(…) |
warn |
:127-129、:150-152 |
| mandatory warning | MANDATORY_WARNING |
printMandatoryWarning(…) |
warn |
:174-176、:197-199 |
| error | ERROR |
printError(…) |
error |
:221-223、:244-246 |
| 任意 | Kind(参数) |
printMessage(env|messager, kind, …) |
见下文 | :268-270、:292-309 |
整套映射就在一个方法里(:292-309):模式串先经 io.microsphere.text.FormatUtils.format(pattern, args) 格式化一次,发给 Messager,然后带着原始模式串与参数再发给 logger,以便日志后端自行完成参数化。ERROR → error,WARNING 与 MANDATORY_WARNING → warn,NOTE → info,而其余任何 kind(OTHER,以及未来 JDK 新增的 Diagnostic.Kind)→ debug(:306-307)。
Warning
占位符语法是 {},不是 {0},也不是 String.format。javadoc 写着“支持 String#format 语法”,示例用的是 {0}(lang/model/util/MessagerUtils.java:56-61、:78-82),但实现调用的是 FormatUtils.format,其默认占位符字面上就是 "{}"(microsphere-java-core/src/main/java/io/microsphere/text/FormatUtils.java:17、:38-39)。请写 "Hello, {}!" —— 测试就是这么写的(lang/model/util/MessagerUtilsTest.java:55-56)—— 否则你的参数永远不会被替换。
整条链路上没有任何 null 防护:printX(processingEnv, …) 会解引用 processingEnv.getMessager()(:64、:128、:175、:222、:269),而 printMessage(messager, …) 会解引用 messager.printMessage(...)(:294)并在 switch 中解引用 kind(:295)。见 §10 陷阱 8。
// MessagerUtilsTest.java:50-80
this.messager = this.processingEnv.getMessager();
assertDoesNotThrow(() -> printNote(this.processingEnv, "Hello, {}!", "printNote"));
assertDoesNotThrow(() -> printNote(this.messager, "Hello, {}!", "printNote"));
assertDoesNotThrow(() -> printWarning(this.processingEnv, "Hello, {}!", "printWarning"));
assertDoesNotThrow(() -> printMandatoryWarning(this.messager, "Hello, {}!", "printMandatoryWarning"));
assertDoesNotThrow(() -> printError(this.processingEnv, "Hello, {}!", "printError"));
// the generic form, with a Kind outside the four mapped ones
assertDoesNotThrow(() -> printMessage(this.messager, OTHER, "Hello, {}!", "printMessage"));来源:lang/model/util/MessagerUtilsTest.java:47-81
lang/model/util/ExecutableElementComparator.java:55 是一个 Comparator<ExecutableElement>,构造器为私有(:62),只暴露一个公有单例 INSTANCE(:60)—— 因此调用方始终共享同一个无状态实例。文档所述规则(:27-33)按四步实现:同一性/equals 捷径 if (e1.equals(e2)) return 0;(:68-70);接着简单名按字典序,由 CharSequenceComparator.INSTANCE.compare(e1.getSimpleName(), e2.getSimpleName()) 完成(:73);然后参数个数 ps1.size() - ps2.size()(:77-80);最后逐参数类型名字典序,首个差异即返回,比较的是 asType().toString()(:82-89)。
结果是一个稳定的、以名称优先的排序:把重载聚在一起,并在每个重载组内按元数、再按参数类型名排序。当你从 getDeclaredMethods(...) 生成有序输出时应当使用它,以使生成的元数据在多次 javac 之间可复现。它并未使用 io.microsphere.lang.Prioritized:对全部 16 个主类型 grep 均无该接口的引用。开发者指南仍记录了 Prioritized 常量反转的警告,因为它影响配套的 annotation-processor 模块。
private final ExecutableElementComparator comparator = INSTANCE; // :23
// :40-43 — same name, same arity, different parameter types → compare type names
TypeElement typeElement = getTypeElement(Integer.class);
assertEquals(int.class.getName().compareTo(String.class.getName()),
comparator.compare(findMethod(typeElement, "valueOf", int.class),
findMethod(typeElement, "valueOf", String.class)));
// :48-53 — same name, different arity → the arity difference is returned verbatim
TypeElement sb = getTypeElement(StringBuilder.class);
assertEquals(-2, comparator.compare(findMethod(sb, "append", char[].class),
findMethod(sb, "append", char[].class, int.class, int.class)));
// :59-60 — the same method reached from two different TypeElements compares 0
assertEquals(0, comparator.compare(getMethod("close"),
findMethod(getTypeElement(AutoCloseable.class), "close")));来源:lang/model/util/ExecutableElementComparatorTest.java:21-65
- 你得到的每个
TypeMirror都要求一个活的ProcessingEnvironment。TypeUtils.getTypeElement(env, …)(lang/model/util/TypeUtils.java:2350-2381)、getDeclaredType(env, …)(:2401-2433)与getTypeMirror(env, …)(:2310)都委托给Elements.getTypeElement/Types.getDeclaredType,传入null时它们静默地返回null或emptyList()。AnnotatedElementJSONElementVisitor是唯一拒绝null的类型,靠构造器中三处assertNotNull(lang/model/util/AnnotatedElementJSONElementVisitor.java:103、:104、:110)。本模块的测试之所以能跑,是因为microsphere-annotation-test提供了真实的处理环境 ——UtilTest继承AbstractAnnotationProcessingTest(lang/model/util/UtilTest.java:20,48)。把测试搬到你自己的工程而不加该测试依赖,什么都解析不出来。 - 为当前轮次生成的类查找
TypeElement可能得到null。Elements.getTypeElement看不见尚未 enter 的源码,而AnnotatedElementJSONElementVisitor.java:110恰好把这种情况变成硬失败,消息为"The annotation can't be found by name : …"。请传入已存在的注解类。 isSameType是字符串比较,不是Types#isSameType调用。lang/model/util/TypeUtils.java:261-266比较String.valueOf(typeMirror)与期望名称,因此 (a) 它无需Types实例即可调用,(b) 对toString()不是规范二进制名的类型它不是等价的 —— 类型变量用法、capture 类型与内含类型会在javax.lang.model.util.Types.isSameType返回true的地方比较为false。对声明类型与原始类型它是可靠的。另需注意isSameType(null, null)返回true(:262-264),这条“null 相等”规则与所有兄弟谓词不同,后者对null一律返回false。isSimpleType拿toString()与名称列表比较。lang/model/util/TypeUtils.java:149-151—— 只要声明类型的名字恰好在SIMPLE_TYPE_NAMES(:77-82)中就是“简单类型”,与该类的实际来源无关。它既不依赖 classpath,又可能被 classpath 意外命中。getElementValues含默认值;getElementValue优先显式值。lang/model/util/AnnotationUtils.java:1509-1529会为缺席项合并getDefaultValue(),而对既无显式值又无默认值的属性直接跳过。getElementValue(annotation, name, true)(:1571-1602)只在withDefault && annotationValue == null分支重新扫描声明的方法(:1587-1595),且解析结果仍为null时返回null(:1597-1599)—— 于是“属性不存在”与“属性 = null”无法区分。getAttribute还多一重风险:它通过annotationValue.accept(DEFAULT_ANNOTATION_VALUE_VISITOR, attributeMethod)(:1781)分派,用的是共享静态实例,因此 §6.3 的两个特性开关取决于该实例的构造方式,而不是你的选择。- 访问器的默认方法继承自已废弃且实现不全的 JDK 类。
JSONElementVisitor继承ElementKindVisitor6(lang/model/util/JSONElementVisitor.java:28,57),JSONAnnotationValueVisitor/ResolvableAnnotationValueVisitor继承SimpleAnnotationValueVisitor6(lang/model/util/JSONAnnotationValueVisitor.java:25,72、lang/model/util/ResolvableAnnotationValueVisitor.java:27,74)。ElementKindVisitor6自 JDK 9 起废弃,而SimpleAnnotationValueVisitor6.visit()对 JDK 6 之后新增的 kind 会落到visitUnknown。具体地:JSONElementVisitor.visitType调用super.visitType(:93),后者分派到visitTypeAsRecord—— 而这里既没有覆写visitTypeAsRecord也没有doVisitType钩子,因此record没有输出且返回false,除非你自己覆写。 JSONElementVisitor不防护null元素。supports(Element)无条件返回true(:130-132),因此使用默认supports时,visitVariable(null, sb)/visitExecutable(null, sb)/visitType(null, sb)会穿过super.visitXxx落入ElementKindVisitor6,而后者会解引用元素。lang/model/util/JSONElementVisitorTest.java:146-251中所有*OnUnsupported用例之所以通过,仅仅是因为测试覆写supports返回false;它们在:177、:197、:250传入了null。务必覆写supports。Messager为null时MessagerUtils抛NullPointerException。printMessage无防护地调用messager.printMessage(kind, message)(lang/model/util/MessagerUtils.java:294),kind也在:295的switch中被解引用。ProcessingEnvironment重载更糟::64、:128、:175、:222、:269处的processingEnv.getMessager()在环境为null时直接 NPE。这与模块整体的静默降级策略相矛盾(见 §10.13)。请自行防护:if (processingEnv != null) printNote(processingEnv, …)。ExecutableElementComparator.compare同样不容忍 null。lang/model/util/ExecutableElementComparator.java:68的e1.equals(e2)在e1为null时 NPE;e1非空而e2 == null能熬过第一步,随后在:73的e2.getSimpleName()上 NPE。排序前请先剔除null。- 本模块不缓存任何
Element查找。 仅有的静态状态是ResolvableAnnotationValueVisitor中在类初始化时解析一次的static final字段 ——ANNOTATION_PARSER_CLASS与annotationForMapMethod(lang/model/util/ResolvableAnnotationValueVisitor.java:76-78)—— 加上不可变的SIMPLE_TYPE_NAMES列表(lang/model/util/TypeUtils.java:77-82)与两个TypeElement遍历Function常量(:88、:94-97)。TypeUtils.getTypeElement(env, name)、MemberUtils.getDeclaredMembers以及TypeUtils.getAllTypeElements(:1169)的层级遍历每次调用都重新计算。处理成千上万个类型时,请把TypeElement查找提升到你自己的Map里。 getTypeName通过反射伸手进TypeMirror。lang/model/util/TypeUtils.java:2456-2478调用invokeMethod(true, type, "getTypeArguments")(:2466),因为TypeMirror并未声明该方法,然后把裸的List<TypeMirror>包在<与>之间追加(:2470-2472)。于是有两个副作用:尖括号形式只有在元素声明了类型参数且该类型携带实参时才出现;渲染结果是 type mirror 的List.toString(),而非规范名称。需要纯字符串时请用toString(TypeMirror)(:2445-2448)。ResolvableAnnotationValueVisitor可能交给你一个无法加载的Class。visitType与visitEnumConstant调用ClassUtils.loadClass(:148、:153→lang/model/util/ClassUtils.java:78),而visitArray需要属性方法推导组件类型(lang/model/util/ResolvableAnnotationValueVisitor.java:177-195):(ArrayType) attributeMethod.getReturnType()(:193)是未检查转换,因此属性方法为null时,还没轮到转换就已经 NPE。JSONAnnotationValueVisitor处理数组元素时完全不需要属性方法,从而绕开了这一点。
本库的策略是静默降级 —— 输入缺失时返回 null、false、空列表或空 map,而不是抛异常。本模块中经核实的例外恰好是:
| 位置 | 行为 | 证据(lang/model/util/) |
|---|---|---|
AnnotatedElementJSONElementVisitor 构造器 |
processingEnv、annotationClassName 为 null 或注解无法解析时抛出 |
AnnotatedElementJSONElementVisitor.java:103,104,110 |
MessagerUtils |
Messager / ProcessingEnvironment / Kind 为 null 时 NPE |
MessagerUtils.java:64,128,175,222,269,294,295 |
ExecutableElementComparator.compare |
元素为 null 时 NPE |
ExecutableElementComparator.java:68,73 |
TypeUtils.findTypeElements |
声明 throws IllegalArgumentException(过滤元素为 null 时) |
TypeUtils.java:1356,1360-1365、:2034,2064 |
TypeUtils.typeElementFinder |
javadoc 声明 IllegalArgumentException:“if any parameter is invalid or if assertions fail” |
TypeUtils.java:2498-2505 |
ResolvableAnnotationValueVisitor.getComponentType |
对可能为 null 的属性方法做未检查 (ArrayType) 转换 |
ResolvableAnnotationValueVisitor.java:189-195 |
其余全部 —— ElementUtils、AnnotationUtils、MemberUtils、FieldUtils、MethodUtils、ConstructorUtils、ClassUtils、JSONAnnotationValueVisitor、StringAnnotationValue、LoggerUtils —— 都会降级。完整的抛出点清单见开发者指南。
- 注解处理器模块 —— 在
javac内部驱动本模块的框架;AnnotatedElementJSONElementVisitor是它天然的扩展点。 - JDK Tools 模块 —— 此处为测试作用域依赖(
pom.xml:44-49),提供 JVM 内的ToolProvider驱动,使本模块测试能获得真实的ProcessingEnvironment。 - Annotation Test 模块 —— 测试作用域的骨架
io.microsphere.test.annotation.processing.AbstractAnnotationProcessingTest,即UtilTest的父类(lang/model/util/UtilTest.java:20,48),也是io.microsphere.test.*夹具的来源。 - microsphere-java-core README —— 模块索引与发布线版本表。
- JSON ——
JSONUtils.append/appendName,即JSONAnnotationValueVisitor背后的输出层(lang/model/util/JSONAnnotationValueVisitor.java:36-37)。 - 开发者指南 —— 语言模型 —— 16 个类型的清单、访问器分派图、类型解析与
AnnotationValue树的源码分析,以及线程安全评审。 - 英文版本:English User Guide