模块: microsphere-java-core · 包: io.microsphere.concurrent、io.microsphere.process、
io.microsphere.management、io.microsphere.management.builder、io.microsphere.security · 23 个主源码类型 ·
分支: release(0.3.20-SNAPSHOT)
本页是
并发、进程、管理与安全 —— 用户指南
的源码级配套文档。所有路径都相对于 microsphere-java-core/src/main/java/io/microsphere/(主干源码)或
microsphere-java-core/src/test/java/io/microsphere/(测试)。
这五个包并不是同一个子系统。它们是四个小子系统,恰好在两处相互咬合:process 通过 management 读取
RuntimeMXBean,而 management 从 process 解析 PID。这条相互依赖的边是本组中唯一真正的耦合,
而且它指向一个出人意料的方向 —— 解析器链条的消费者与解析器本身不在同一个包里。
graph TD
CONCURRENT["concurrent"]
PROCESS["process"]
MANAGEMENT["management"]
BUILDER["management.builder"]
SECURITY["security"]
LANG["lang:Prioritized、Wrapper、DelegatingWrapper"]
UTIL["util:Utils、ClassUtils、ExceptionUtils、Assert、ServiceLoaderUtils、StringUtils、ArrayUtils、ClassLoaderUtils、ObjectUtils、ShutdownHookUtils"]
REFLECT["reflect:MethodUtils、FieldUtils"]
COLLECTION["collection:MapUtils、ListUtils"]
LOGGING["logging:Logger、LoggerFactory"]
IO["io:FastByteArray*、IOUtils"]
SUPPORT["constants + text:SymbolConstants、FormatUtils"]
ANNOTATION["annotation:Nonnull、Nullable、Immutable、ConfigurationProperty"]
CONCURRENT --> LANG
CONCURRENT --> UTIL
CONCURRENT --> LOGGING
PROCESS --> LANG
PROCESS --> UTIL
PROCESS --> REFLECT
PROCESS --> COLLECTION
PROCESS --> LOGGING
PROCESS --> IO
PROCESS --> SUPPORT
PROCESS --> ANNOTATION
PROCESS --> MANAGEMENT
PROCESS --> CONCURRENT
MANAGEMENT --> PROCESS
MANAGEMENT --> UTIL
MANAGEMENT --> REFLECT
MANAGEMENT --> COLLECTION
MANAGEMENT --> LOGGING
MANAGEMENT --> ANNOTATION
MANAGEMENT --> BUILDER
BUILDER --> MANAGEMENT
BUILDER --> UTIL
BUILDER --> REFLECT
BUILDER --> COLLECTION
BUILDER --> ANNOTATION
SECURITY --> UTIL
SECURITY --> ANNOTATION
每条边背后都有一句 import 语句,并逐一核对过:
| 边 | 证据 |
|---|---|
concurrent → lang |
concurrent/DelegatingBlockingQueue.java:19(DelegatingWrapper)、:30(Wrapper.tryUnwrap) |
concurrent → util |
concurrent/ExecutorUtils.java:20-21、:28-29;concurrent/DelegatingBlockingQueue.java:31 |
concurrent → logging |
concurrent/ExecutorUtils.java:19、:27 |
process → lang |
process/ProcessIdResolver.java:19、process/ClassicProcessIdResolver.java:19 |
process → reflect |
process/ModernProcessIdResolver.java:22-23;process/VirtualMachineProcessIdResolver.java:26-28 |
process → management |
process/ClassicProcessIdResolver.java:24、process/VirtualMachineProcessIdResolver.java:25 —— 两者都用 JmxUtils.getRuntimeMXBean |
process → concurrent |
process/ProcessExecutor.java:17-18 —— newThreadFactory、shutdownOnExit |
process → io / constants / text |
process/ProcessExecutor.java:4-5、:19-20、:23 |
management → process |
management/ManagementUtils.java:3、:9 |
management → util |
management/ManagementUtils.java:4-5、:10;management/JmxUtils.java:24、:65-67 |
management ↔ management.builder |
management/JmxUtils.java:23(使用 MBeanParameterInfoBuilder)与 management/builder/MBeanExecutableInfoBuilder.java:21、:33(使用 JmxUtils) |
management.builder → reflect |
management/builder/MBeanInfoBuilder.java:37(MethodUtils.isIsMethod) |
security → util / annotation |
security/SecurityUtils.java:20-23、:27 |
给评审者的两点观察:
management与management.builder构成一个包级循环。 两个方向都不是类循环,因此 JDK 可以顺利编译, 但这两个包必须放在一起推理:JmxUtils.signature(Parameter[])(management/JmxUtils.java:577-582)经由 builder 包 构造MBeanParameterInfo对象,而构建器又回调进JmxUtils.descriptorForElement/descriptorForAnnotations(management/builder/MBeanExecutableInfoBuilder.java:33、management/builder/MBeanParameterInfoBuilder.java:28)。concurrent/CustomizedThreadFactory.java与concurrent/DelegatingScheduledExecutorService.java没有从io.microsphere导入任何东西 —— 它们是自足的 JDK 适配层。这就是为什么它们是本组中最可以安全照搬的类型。
组外恰好只有三个主源码文件依赖它:
| 消费者 | 使用了什么 |
|---|---|
io/StandardFileWatchService.java:44-46 |
CustomizedThreadFactory.newThreadFactory、ExecutorUtils.shutdown、ExecutorUtils.shutdownOnExit |
util/ClassLoaderUtils.java:45 |
JmxUtils.getClassLoadingMXBean |
util/ClassPathUtils.java:17 |
JmxUtils.getRuntimeMXBean |
没有任何 Spring 模块消费这些包,也没有其他 microsphere-* 模块从公开 API 面读取它们 —— 所以这里的改动是被封闭住的,
唯一的例外是 JmxUtils(没有被任何反射使用,但被上面三个消费者直接调用)。
| # | 类型 | 声明位置 | 种类 | 继承 / 实现 | 职责 |
|---|---|---|---|---|---|
| 1 | CustomizedThreadFactory |
concurrent/CustomizedThreadFactory.java:40 |
具体类 | ThreadFactory |
命名、守护、带优先级的线程创建 |
| 2 | ExecutorUtils |
concurrent/ExecutorUtils.java:40 |
抽象类(Utils) |
Utils |
优雅关闭 + 退出时关闭执行器 |
| 3 | DelegatingBlockingQueue<E> |
concurrent/DelegatingBlockingQueue.java:55 |
具体类 | BlockingQueue<E>、DelegatingWrapper |
带包装器压平的转发队列 |
| 4 | DelegatingScheduledExecutorService |
concurrent/DelegatingScheduledExecutorService.java:56 |
具体类 | ScheduledExecutorService |
可热替换的调度门面 |
| 5 | ProcessIdResolver |
process/ProcessIdResolver.java:54 |
接口 | Prioritized |
supports() / current() 策略契约 |
| 6 | ModernProcessIdResolver |
process/ModernProcessIdResolver.java:50 |
具体类 | ProcessIdResolver |
经反射调用 ProcessHandle#pid(),prio 1 |
| 7 | VirtualMachineProcessIdResolver |
process/VirtualMachineProcessIdResolver.java:62 |
具体类 | ProcessIdResolver |
经字段反射访问 sun.management.VMManagementImpl,prio 5 |
| 8 | ClassicProcessIdResolver |
process/ClassicProcessIdResolver.java:69 |
具体类 | ProcessIdResolver |
解析 RuntimeMXBean#getName() 的 pid@host,prio 9 |
| 9 | ProcessManager |
process/ProcessManager.java:40 |
单例类 | —— | 进行中的子进程注册表 |
| 10 | ProcessExecutor |
process/ProcessExecutor.java:58 |
具体类 | —— | 带超时的子进程执行 |
| 11 | ManagementUtils |
management/ManagementUtils.java:20 |
抽象类(Utils) |
Utils |
PID 门面;驱动解析器链条 |
| 12 | JmxUtils |
management/JmxUtils.java:87 |
类(私有构造器 :654-655) |
Utils |
ManagementFactory 缓存 + 属性/元数据自省 |
| 13 | MBeanAttribute |
management/MBeanAttribute.java:59 |
具体类 | —— | 不可变的(info, value)配对 |
| 14 | MBeanDescribableBuilder<B> |
management/builder/MBeanDescribableBuilder.java:34 |
抽象类 | —— | description + descriptor,声明 build() :61 |
| 15 | MBeanFeatureInfoBuilder<B> |
management/builder/MBeanFeatureInfoBuilder.java:32 |
抽象类 | MBeanDescribableBuilder<B> |
增加 name;build() → MBeanFeatureInfo :50-52 |
| 16 | MBeanAttributeInfoBuilder |
management/builder/MBeanAttributeInfoBuilder.java:34 |
具体类 | MBeanFeatureInfoBuilder<…> |
增加 type/read/write/is |
| 17 | MBeanParameterInfoBuilder |
management/builder/MBeanParameterInfoBuilder.java:40 |
具体类 | MBeanFeatureInfoBuilder<…> |
增加 type;parameter(Parameter) :66-74 |
| 18 | MBeanExecutableInfoBuilder<B> |
management/builder/MBeanExecutableInfoBuilder.java:48 |
抽象类 | MBeanFeatureInfoBuilder<…> |
参数/签名,from(Executable) :76-84 |
| 19 | MBeanConstructorInfoBuilder |
management/builder/MBeanConstructorInfoBuilder.java:33 |
具体类 | MBeanExecutableInfoBuilder<…> |
→ MBeanConstructorInfo :39-41 |
| 20 | MBeanOperationInfoBuilder |
management/builder/MBeanOperationInfoBuilder.java:38 |
具体类 | MBeanExecutableInfoBuilder<…> |
returnType + 内嵌 enum Impact :81-116 |
| 21 | MBeanNotificationInfoBuilder |
management/builder/MBeanNotificationInfoBuilder.java:40 |
具体类 | MBeanDescribableBuilder<…> |
通知 types :43-56 |
| 22 | MBeanInfoBuilder |
management/builder/MBeanInfoBuilder.java:48 |
具体类 | MBeanDescribableBuilder<MBeanInfoBuilder> |
聚合;mbeanInfo(BeanInfo) :162-185 |
| 23 | SecurityUtils |
security/SecurityUtils.java:39 |
抽象类(Utils) |
Utils |
java.security.policy 的 get/set |
容易被忽略的结构性说明:
MBeanInfoBuilder与MBeanNotificationInfoBuilder继承的是MBeanDescribableBuilder, 而不是MBeanFeatureInfoBuilder—— 在 JDK 中MBeanInfo与MBeanNotificationInfo并不属于MBeanFeatureInfo超族。任何假设"包里所有东西都有name()"的人都会在这两个类型上出错。ProcessIdResolver是本组中除它所实现的 JDK 接口之外唯一的接口;其余全是类,而其中四个类 (ExecutorUtils、ManagementUtils、SecurityUtils、JmxUtils)是不可实例化的静态工具持有者, 拥有私有构造器(concurrent/ExecutorUtils.java:144-145、management/ManagementUtils.java:60、security/SecurityUtils.java:75-76、management/JmxUtils.java:654-655)。ProcessIdResolver声明的是Long current()(装箱),而ManagementUtils.getCurrentProcessId()返回long。 正是这个装箱使得 §3 中的filter(Objects::nonNull)步骤成为可能。
整个仓库中不存在任何名为 ProcessIdResolvers 的工厂类。组装发生在 ManagementUtils 内部的一个表达式里:
static final long currentProcessId = resolveCurrentProcessId(); // :22
private static long resolveCurrentProcessId() {
return loadServicesList(ProcessIdResolver.class) // :25
.stream()
.filter(ProcessIdResolver::supports) // :27
.map(ProcessIdResolver::current) // :28
.filter(Objects::nonNull) // :29
.findFirst() // :30
.orElse(UNKNOWN_PROCESS_ID); // :31
}loadServicesList 即 ServiceLoaderUtils.loadServicesList(Class)(util/ServiceLoaderUtils.java:382-384),
它委托给具备类加载器层次感知的那条路径,最终落在 loadServicesAsList(Class, ClassLoader)(:873-893)。
由这条路径可以得出三点后果:
- 实现来自
META-INF/services,而本组恰好只贡献了一个这样的文件:microsphere-java-core/src/main/resources/META-INF/services/io.microsphere.process.ProcessIdResolver, 其中列出三个解析器。management与security没有注册任何东西。 - 返回的列表被包装为不可修改(
util/ServiceLoaderUtils.java:468-470),因此调用方无法改动这条链条。 - 若找不到任何实现,
loadServicesAsList会抛出IllegalArgumentException(:882-886), 而不是返回空列表。由于resolveCurrentProcessId()运行在静态初始化器中 (management/ManagementUtils.java:22),该异常会在ManagementUtils首次被触碰时表现为ExceptionInInitializerError。
loadServicesAsList 使用 sort(serviceList, COMPARATOR) 排序(util/ServiceLoaderUtils.java:890),
其中 COMPARATOR 是 Prioritized.COMPARATOR(lang/Prioritized.java:61-76),自然序为
Integer.compare(getPriority(), other.getPriority())(lang/Prioritized.java:102-105)。
因此是优先级数值升序,最小者在前 —— 并且请记住库中取反的常量:MAX_PRIORITY = Integer.MIN_VALUE
(lang/Prioritized.java:81)、MIN_PRIORITY = Integer.MAX_VALUE(:86)、NORMAL_PRIORITY = 0(:91)。
三个解析器围绕 NORMAL_PRIORITY 拉开间距:
| 解析器 | 表达式 | 值 | 引用 |
|---|---|---|---|
ModernProcessIdResolver |
NORMAL_PRIORITY + 1 |
1 | process/ModernProcessIdResolver.java:73-76 |
VirtualMachineProcessIdResolver |
NORMAL_PRIORITY + 5 |
5 | process/VirtualMachineProcessIdResolver.java:99-102 |
ClassicProcessIdResolver |
NORMAL_PRIORITY + 9 |
9 | process/ClassicProcessIdResolver.java:91-94 |
这些间隔(1/5/9)为第三方解析器留出了插入空间而无需重新编号,而且测试把每个数字都精确钉死
(process/ModernProcessIdResolverTest.java、process/VirtualMachineProcessIdResolverTest.java、
process/ClassicProcessIdResolverTest.java 都断言 getPriority()),所以重新排布间距就是一次破坏性变更。
service 文件内部的声明顺序无关紧要 —— 排序发生在加载之后。
flowchart TD
A["ManagementUtils 首次被触碰"] --> B["静态初始化器 :22"]
B --> C["ServiceLoaderUtils.loadServicesList(ProcessIdResolver.class) :25"]
C --> D{"是否找到任何实现?"}
D -- "否" --> E["IllegalArgumentException :882-886<br/>→ ExceptionInInitializerError"]
D -- "是" --> F["按 Prioritized.COMPARATOR 排序 :890<br/>Modern 1 → VirtualMachine 5 → Classic 9"]
F --> G["filter(ProcessIdResolver::supports) :27"]
G --> H{"Modern.supports()<br/>PROCESS_HANDLE_CLASS != null :58-61"}
H -- "JDK 8:false" --> I["下一个:VirtualMachine.supports()<br/>JVM_FIELD != null :83-86"]
H -- "JDK 9+:true" --> J["map(current) :28<br/>ProcessHandle.current().pid() :65-66"]
J --> K{"非 null? :29"}
K -- "是" --> L["findFirst :30 → currentProcessId"]
K -- "否" --> I
I -- "true" --> M["getFieldValue + VMManagementImpl#getProcessId :91-92"]
I -- "false(jvm 字段缺失)" --> N["Classic.supports()<br/>isNumeric(pid@host 前缀) :77-80"]
N -- "true" --> O["valueOf(processIdValue) :84"]
N -- "false" --> P["orElse(UNKNOWN_PROCESS_ID) :31 → -1"]
M --> L
O --> L
filter/map 这对操作是惰性的,因此低优先级解析器的 current() 只有在每一个更高优先级的解析器
要么 supports() 失败、要么返回 null 之后才会执行。在 JDK 11+ 上,这意味着
VirtualMachineProcessIdResolver.current() 与 ClassicProcessIdResolver.current() 根本不会被调用。
Warning
supports() 守护的是选择,而不是执行。在不被支持的解析器上调用 current() 并不是一次文档所述的空操作:
ModernProcessIdResolver.current() 会把一个 null 类传给 invokeStaticMethod
(process/ModernProcessIdResolver.java:65)并抛出 IllegalArgumentException,JDK 8 上的这一行为被测试钉死
(process/ModernProcessIdResolverTest.java:43)。同样,一个在选择过程中抛异常的解析器也会穿透 :31 处的
orElse(UNKNOWN_PROCESS_ID) 降级 —— 这条链条吸收的是 null,不是异常。
management/ManagementUtils.java:48-51 的 Javadoc 对兜底的承诺比代码实际给出的更宽。
sequenceDiagram
participant Caller
participant MU as ManagementUtils
participant SLU as ServiceLoaderUtils
participant MOD as ModernProcessIdResolver
participant VM as VirtualMachineProcessIdResolver
participant CLS as ClassicProcessIdResolver
Caller->>MU: getCurrentProcessId()
Note over MU: 类初始化只发生一次,:22
MU->>SLU: loadServicesList(ProcessIdResolver.class) :25
SLU->>SLU: META-INF/services 查找 + sort(COMPARATOR) :890
SLU-->>MU: [Modern, VirtualMachine, Classic](不可修改,:468-470)
MU->>MOD: supports() :27
MOD-->>MU: JDK 9+ 上为 true / JDK 8 上为 false
alt 被支持且非 null
MU->>MOD: current() :28
MOD-->>MU: Long pid → findFirst() :30
else 不被支持或为 null
MU->>VM: supports() / current()
VM-->>MU: Long pid,或跳过
MU->>CLS: supports() / current()
CLS-->>MU: Long pid,或跳过
end
MU-->>Caller: 缓存的 long(若全部失败则为 UNKNOWN_PROCESS_ID = -1)
三个解析器都在静态初始化器中完成自己的探测,而不是惰性进行:
PROCESS_HANDLE_CLASS = resolveClass(...)(process/ModernProcessIdResolver.java:56)、
JVM_FIELD = findField(getRuntimeMXBean(), "jvm")(process/VirtualMachineProcessIdResolver.java:81)以及
runtimeName / processIdValue(process/ClassicProcessIdResolver.java:73-75)。
当 ServiceLoader 实例化每个实现时会触发类初始化,因此单次 getCurrentProcessId() 调用要为
全部三次探测买单 —— 包括对 java.lang.ProcessHandle 的 Class.forName 以及对 RuntimeMXBean
实现类的 getDeclaredField 遍历 —— 然后才选出胜出者。这是每个 JVM 一次性的、由类初始化锁保证的代价,
但它解释了为什么这条链条没有惰性短路。
本组的主体源码中不存在任何版本判断。在 23 个文件里搜索 JavaVersion、JAVA_VERSION_、SystemUtils
或 isCurrentJavaVersion 一无所获;版本谓词只出现在测试里
(process/ModernProcessIdResolverTest.java:26 使用 testCurrentJavaVersion(GE, JAVA_VERSION_9))。
因此这里的分派是能力探测,共三种方式,每种都由 JVM 自己回答:
| 机制 | 探测 | 选择信号 | 调用路径 | 在 JDK 8 上 |
|---|---|---|---|---|
| 类是否存在 | resolveClass("java.lang.ProcessHandle")(process/ModernProcessIdResolver.java:54-56) |
supports() = nonNull(PROCESS_HANDLE_CLASS)(:58-61) |
先 MethodUtils.invokeStaticMethod(..., "current"),再 MethodUtils.invokeMethod(handle, ..., "pid")(:65-66) |
类不存在 → 跳过 |
| 字段是否存在 | FieldUtils.findField(getRuntimeMXBean(), "jvm")(process/VirtualMachineProcessIdResolver.java:81) |
supports() = nonNull(JVM_FIELD)(:83-86) |
getFieldValue(true, runtimeMXBean, JVM_FIELD) → invokeMethod(true, jvm, "getProcessId")(:91-92) |
这就是为 JDK 8 设计的路径 |
| 数据形状 | substringBefore(getRuntimeMXBean().getName(), AT)(process/ClassicProcessIdResolver.java:73-75) |
supports() = isNumeric(processIdValue)(:77-80) |
valueOf(processIdValue)(:84)—— 无反射 |
最后手段 |
为什么用反射而不是直接调用 ProcessHandle.current().pid():该构件只编译一次,字节码级别从外部父 POM
microsphere-build:0.3.16 继承(反应堆中不存在任何 maven.compiler.* / <release> 属性),
而 CI 矩阵从 JDK 8 起步(java: [ '8' , '11' , '17' , '21' , '25' ]、distribution: 'temurin',
.github/workflows/maven-build.yml:25、:33)。对 java.lang.ProcessHandle 的编译期引用会让 JDK 8 构建失败,
所以 ModernProcessIdResolver 通过 io.microsphere.reflect.MethodUtils 触达它
(process/ModernProcessIdResolver.java:22-23),代码中从不点名这个类型。
维护者需要记在心上的后果:
- 在 JDK 9+ 上,排除
VirtualMachineProcessIdResolver.supports()的并不是supports()本身 —— 在整个矩阵的 HotSpotsun.management.RuntimeImpl上jvm字段都存在,所以supports()很可能返回true。 它被优先级为 1 的解析器遮蔽了。假如ModernProcessIdResolver.current()哪天真返回了null,链条会落到getFieldValue(true, ...)(:91),而它在 JDK 16+ 上需要--add-opens,否则抛出异常。 真正安全的是优先级顺序,而不是那个卫兵。 VirtualMachineProcessIdResolver.current()对getFieldValue的结果解引用时没有 null 检查, 随后调用processId.longValue()(:91-96),所以字段值缺失时产生的是NullPointerException, 而不是一次干净的落空继续。JmxUtils.descriptorForAnnotations同样依赖反射式分派 —— 它通过MethodUtils.invokeMethod(annotation, method)调用注解的成员方法(management/JmxUtils.java:609,import 在:64),因此无论哪个版本都能在 JDK 的注解代理对象上工作。
flowchart TD
A["Java 侧的事实来源<br/>Class / Method / Constructor / Parameter / PropertyDescriptor / BeanInfo"] --> B["静态工厂<br/>mbeanInfo :156, :162 · attribute :81-90 · operation :66-79 · constructor :43-49 · notification :62-72 · parameter :56-64"]
B --> C["构建器状态字段<br/>name、description、descriptor、type、read/write/is、parameters、returnType、impact、types"]
C --> D["MBeanDescribableBuilder.build() :61 —— 抽象,模板方法"]
D --> E1["MBeanAttributeInfo :76-79"]
D --> E2["MBeanFeatureInfo :50-52"]
D --> E3["MBeanParameterInfo :52-54"]
D --> E4["MBeanConstructorInfo :39-41"]
D --> E5["MBeanOperationInfo :62-64"]
D --> E6["MBeanNotificationInfo :58-60"]
D --> E7["MBeanInfo :127-130"]
C -.-> F["descriptorForElement / descriptorForAnnotations<br/>management/JmxUtils.java:588-591, :597-619"]
F -.-> G["由 @DescriptorKey 产出 Descriptor 字段<br/>ImmutableDescriptor :614-616, EMPTY_DESCRIPTOR :618"]
MBeanDescribableBuilder.build() 的声明是 public abstract <I extends DescriptorRead> I build();
(management/builder/MBeanDescribableBuilder.java:61)—— 它是模板方法模式中的抽象步骤,
其共享步骤(description(String) :51-54、descriptor(Descriptor) :56-59)被全部九个具体/抽象构建器继承。
泛型的自返回类型 B 正是使得在子类上调用 name(...) 之后仍能继续链式返回的原因。
MBeanInfoBuilder.build()(management/builder/MBeanInfoBuilder.java:127-130)展开为四个私有步骤 ——
buildAttributes() :132-136、buildConstructors() :138-142、buildOperations() :144-148、
buildNotifications() :150-154 —— 每一步都是 builders.stream().map(X::build).toArray(X[]::new)。
Note
值得记录的一处修正:传给 MBeanInfo 构造器的数组是由 toArray(...) 产出的,不是由 JmxUtils 中的
EMPTY_MBEAN_*_ARRAY 常量(management/JmxUtils.java:91-99)。那五个常量中只有
EMPTY_MBEAN_ATTRIBUTE_ARRAY(:91)被主干代码使用 —— 在 :417。其余四个(:93、:95、:97、:99)
只被 .../test/java/io/microsphere/management/JmxUtilsTest.java:117-121 引用。
从 java.lang.reflect 到 javax.management 的桥梁是 MBeanExecutableInfoBuilder.from(Executable)
(management/builder/MBeanExecutableInfoBuilder.java:76-84),它记录该 executable 并推导出它的参数列表;
随后 signature(...) :62-66 与 toSignature() :86-88 把 MBeanParameterInfo[] 物化出来。
同样的推导也以静态形式存在于 JmxUtils.methodSignature(Method) → signature(Parameter[])
(management/JmxUtils.java:573-582)中,它先使用 MBeanParameterInfoBuilder::parameter 再 ::build ——
这就是 §1 中那个包级循环的 management 半边。
描述文本从不被凭空编造。MBeanExecutableInfoBuilder.from(Executable) 以及 attribute(PropertyDescriptor) 路径
让 description(...) 取自 JDK 对象自身的 toString();mbeanInfo(BeanInfo) 设置
builder.description = beanInfo.toString()(management/builder/MBeanInfoBuilder.java:167),
className 取自 ClassUtils.getTypeName(beanClass)(:166);而 mbeanInfo(String className)(:156-160)
把 description 与 descriptor 都留为 null —— 该行为被
.../test/java/io/microsphere/management/builder/MBeanInfoBuilderTest.java:154-159 钉死。
从 JavaBean 推导属性/操作(:162-185)是三个循环:PropertyDescriptor[] → attribute(...)(:170-172)、
MethodDescriptor[] → operation(...)(:174-177)、beanClass.getDeclaredConstructors() → constructor(...)
(:179-182)。attribute(PropertyDescriptor) 自身(:75-85)把 is 映射自
MethodUtils.isIsMethod(readMethod),read 映射自 nonNull(readMethod),write 映射自
nonNull(propertyDescriptor.getWriteMethod()),并用 ListUtils.newLinkedList() 承载那四个构建器列表
(:57-66,import 在 :36)。
management.builder 中没有任何东西注册 MBean。整个切片里唯一的 registerMBean(...) 调用都在测试中
(.../test/java/io/microsphere/management/JmxUtilsTest.java:267、:291),使用调用方自选的名称
(:234、:284)。元数据产出与注册被刻意分离。
| 持有者 | 声明 | 判定 |
|---|---|---|
ProcessManager.unfinishedProcessesCache |
process/ProcessManager.java:47 —— ConcurrentHashMap,包级私有字段 |
可以安全地并发 putIfAbsent(:53)与双参数 remove(process, arguments)(:58)。注意这里的条件式移除:arguments 值不匹配时会静默保留该条目。 |
ProcessManager.unfinishedProcessesMap() |
process/ProcessManager.java:67-71 |
返回 unmodifiableMap(cache) —— 这是一个活动的视图,不是快照。弱一致迭代(没问题),但 :68 处的 @Immutable 言过其实:并发注册时内容会变化。 |
ManagementUtils.currentProcessId |
management/ManagementUtils.java:22 —— static final long |
实质不可变;安全发布由类初始化锁保证。唯一的刷新方式是换一个 JVM。 |
JmxUtils 的 MXBean 缓存 |
management/JmxUtils.java:101-117 —— 九个非 volatile 私有静态字段,每个都在 if (x == null) x = ManagementFactory.getX() 形态的 getter 中惰性填充(:132-138、:153-159、:174-180、:195-201、:223-229、:248-254、:275-282、:306-313、:337-344) |
结构上属于良性竞争(幂等重复赋值),但被发布的引用没有 happens-before 边。对 ManagementFactory 单例而言对象本已构造完成,所以这种暴露只是理论上的;对那三个 List 缓存而言,它们包装的是在 getter 内部构建的列表,不安全发布才是评审者应当思考的情形。另外:"只缓存一次"这一性质并未被强制,所以在竞争下 ManagementFactory.getMemoryPoolMXBeans() 可能被调用多次。 |
ServiceLoaderUtils.servicesCache |
util/ServiceLoaderUtils.java:140(ConcurrentMap,:846 处 computeIfAbsent) |
线程安全,但只有当 SERVICE_LOADER_CACHED 为 true 时才会被查询(:138);默认是 false,所以 ManagementUtils(:25)每次调用都重新发现。在本组中,摊销来自 currentProcessId 静态字段。 |
ShutdownHookUtils.shutdownHookCallbacks |
util/ShutdownHookUtils.java:114 —— static final PriorityBlockingQueue |
线程安全队列;ExecutorUtils.shutdownOnExit(concurrent/ExecutorUtils.java:63-68)无上限地追加且从不移除 —— 见用户指南的陷阱 11。 |
DelegatingScheduledExecutorService.delegate |
concurrent/DelegatingScheduledExecutorService.java:58 —— volatile |
引用替换是安全发布的,仅此而已。:49-50 的 Javadoc("线程安全,并允许动态更换被委托对象")说的是字段,而不是一次进行中的 schedule(...)/Future 序列。 |
DelegatingBlockingQueue.delegate |
concurrent/DelegatingBlockingQueue.java:57 —— final |
通过 final 字段安全发布;线程安全继承自被包装的队列,装饰器既不增加也不减少。 |
CustomizedThreadFactory |
concurrent/CustomizedThreadFactory.java:57-67,计数器在 :71 |
除 AtomicInteger 线程计数器外所有字段实质不可变 → 可安全并发 newThread。 |
ProcessExecutor |
process/ProcessExecutor.java:90、:104-115、:149-192 |
每实例一个单线程执行器;实例不适合被共享用于并发 execute(...) 调用(每次调用都提交到同一个线程并修改共享状态)。 |
MBeanAttribute |
management/MBeanAttribute.java:59,构造器 :67-75 |
不可变值对象;构造器对两个元数据参数的 requireNonNull(:70-71)是这个类唯一的抛出点。 |
| 全部九个构建器 | 全程字段变更 | 按设计不是线程安全的;一个构建器是为单次 build() 暂存数据的单线程场所。 |
针对仓库的 NPE/异常加固审计,以下是本组否则一贯的静默降级策略被打破的位置,逐行核对过:
management/JmxUtils.java:627-628——doGetAttribute内部的catch (Throwable e) { throw wrap(e, RuntimeException.class); };只有ReflectionException、InstanceNotFoundException与AttributeNotFoundException被优雅处理(:625-626)。process/ProcessExecutor.java:172(非零退出码的IOException)、:188(在future.cancel(true)之后重新抛出的TimeoutException)、:190(wrap(e, IOException.class))。management/MBeanAttribute.java:70-71——requireNonNull。management/builder/MBeanAttributeInfoBuilder.java:86——assertNotNull(attributeType, ...)→IllegalArgumentException(util/Assert.java:149-153)。process/ModernProcessIdResolver.java:65—— 在不被支持的 JVM 上抛异常而不是返回null。util/ServiceLoaderUtils.java:882-886—— 从management/ManagementUtils.java:25抵达,位于静态初始化器内。- 潜伏(当前不可达):
process/VirtualMachineProcessIdResolver.java:91-96。
其余一切都是降级:ExecutorUtils.shutdown 返回 false(concurrent/ExecutorUtils.java:96-101、:132-134),
JmxUtils 返回空数组(management/JmxUtils.java:416-418)、空 map(:375-377)、记录日志后返回 null
(:560-568、:513-534),或 EMPTY_DESCRIPTOR(:618)。
| 模式 | 佐证类 | 在源码中的表现 |
|---|---|---|
| 策略 + 有序选择 | ProcessIdResolver(process/ProcessIdResolver.java:54)及其三个实现 |
supports()/current() 契约;按 Prioritized 排序(:54、lang/Prioritized.java:102-105);挑选循环在 management/ManagementUtils.java:25-31 |
| 服务提供者接口(SPI) | META-INF/services/io.microsphere.process.ProcessIdResolver + ServiceLoaderUtils |
唯一一个由数据而非类型驱动的扩展点 |
| 门面 | JmxUtils 覆盖 ManagementFactory/MBeanServer;ManagementUtils 覆盖解析器链条 |
九个 MXBean 访问器的单一扁平静态面(management/JmxUtils.java:132-344);隐藏三个策略的单个 getCurrentProcessId()(management/ManagementUtils.java:56-58) |
| 构建器 | 整个 management.builder 家族 |
返回 this/(B) this 的链式 setter,以及对应 JDK 构造器的终结方法 build()(management/builder/MBeanInfoBuilder.java:127-130) |
| 模板方法 | MBeanDescribableBuilder(management/builder/MBeanDescribableBuilder.java:34-61) |
description()/descriptor() 是共享步骤,build() 是抽象步骤;MBeanInfoBuilder.build() 同样固定了四个 map(...).toArray(...) 子步骤的骨架(:132-154) |
| 装饰器 / 委托包装 | DelegatingBlockingQueue(concurrent/DelegatingBlockingQueue.java:55)、DelegatingScheduledExecutorService(:56) |
每个方法都是一行转发;tryUnwrap 压平嵌套(:59-62、lang/Wrapper.java:113-121) |
| 单例(急切、私有构造器) | ProcessManager.INSTANCE(process/ProcessManager.java:45,构造器 :49-50) |
每个类加载器一个注册表;protected 的变更方法使子类化成为扩展路径(:52、:57) |
| 工厂方法 | CustomizedThreadFactory.newThreadFactory 各重载(concurrent/CustomizedThreadFactory.java:78-90);六个静态 mbeanInfo/attribute/operation/constructor/notification/parameter 工厂 |
CustomizedThreadFactory 的构造器是 protected(:69),因此工厂方法就是公开入口 |
| 空对象(Null Object) | 缺失 —— 刻意标注 | 这里没有 Null Object。UNKNOWN_PROCESS_ID = -1L(process/ProcessIdResolver.java:59)是一个哨兵基量,Long current() 在失败时返回真正的 null,而唯一在场的小空对象(ImmutableDescriptor.EMPTY_DESCRIPTOR,用于 management/JmxUtils.java:618)来自 JDK,而非本组 |
| 值对象 | MBeanAttribute(management/MBeanAttribute.java:59) |
final 字段,equals/hashCode 派生自被包装的 MBeanAttributeInfo,除委托外没有行为(:86-138) |
| 命令(轻量) | ProcessExecutor(process/ProcessExecutor.java:58、:104-115) |
构造时捕获命令行 + 选项,之后针对某个 OutputStream 执行(:149-192) |
- 新增一种 PID 策略。 实现
ProcessIdResolver,选一个能插入现有 1/5/9 之间的getPriority(), 并把类名追加到META-INF/services/io.microsphere.process.ProcessIdResolver。其他一切都不必改动 ——ManagementUtils的链条会自动接收它。请给current()加护,使它在supports()为false时永不抛异常。 - 定制线程创建。
CustomizedThreadFactory的构造器之所以是protected(concurrent/CustomizedThreadFactory.java:69-76),正是为了让子类可以覆写newThread(:94-99)—— 例如安装一个UncaughtExceptionHandler,基类刻意没有做这件事。 - 拦截调度或队列操作。 继承
DelegatingScheduledExecutorService(volatile可替换的 delegate,concurrent/DelegatingScheduledExecutorService.java:58-70),或用DelegatingBlockingQueue进行包装(concurrent/DelegatingBlockingQueue.java:55)。 - 以不同方式跟踪子进程。
ProcessManager的protectedadd/remove(process/ProcessManager.java:52-60) 就是为子类调用而设计的;私有构造器(:49-50)使INSTANCE(:45)成为 JDK 可见的唯一单例。 - 产出自定义 JMX 元数据。 继承任意构建器并设置 JDK 要求的字段;
description(...)与descriptor(...)继承自MBeanDescribableBuilder(management/builder/MBeanDescribableBuilder.java:51-59)。
| 测试 | 钉死了什么 |
|---|---|
.../process/ModernProcessIdResolverTest.java:26-50 |
与版本相关的 supports(),经由 testCurrentJavaVersion(GE, JAVA_VERSION_9)(:26)、JDK 8 上 current() 抛出的 IllegalArgumentException(:43)、优先级 1 |
.../process/VirtualMachineProcessIdResolverTest.java:28-41 |
supports() 为 true、current() 非 null、优先级 5 |
.../process/ClassicProcessIdResolverTest.java:28-41 |
supports() 为 true、current() 非 null、优先级 9 |
.../management/ManagementUtilsTest.java:19-24 |
getCurrentProcessId() > 0 以及与缓存的 currentProcessId 字段相等 |
.../process/ProcessManagerTest.java:28-60 |
在一个 java -version 子进程运行期间实时观察注册表;断言记录的 options 字符串是 " -version"(:52) |
.../process/ProcessExecutorTest.java:38-64 |
正常路径以及全部三种失败模式:TimeoutException(1 毫秒超时)、错误参数的 IOException、命令不存在的 IOException |
.../management/JmxUtilsTest.java:107-294 |
MXBean 同一性、五个 ClassLoading 属性(:190-199)、未知名称的静默降级(:201-205)、一个以 io.microsphere.management:type=Test 注册的手写 DynamicMBean(:231-234、:267),以及经由 CacheControl 逃逸的 RuntimeMBeanException(:284-293) |
.../management/JmxUtilsTest.java:319-343 |
@DescriptorKey → Descriptor 字段,以及 EMPTY_DESCRIPTOR 分支 |
.../management/MBeanAttributeTest.java:48-96 |
值对象的委托面 |
.../management/builder/*BuilderTest.java |
每个构建器的 build() 映射;MBeanInfoBuilderTest.java:154-159 钉死 description 为 null 的情形,:162-181 是 BeanInfo 路径,:184-204 是聚合 |
.../management/builder/AbstractMBeanDescribableBuilderTest.java:38-39、AbstractMBeanFeatureInfoBuilderTest.java:37-38 |
两个共享基类只在抽象基类上标了 @Disabled(:38、:37),并通过类型参数反射构造具体构建器,因此七个具体测试仍然执行 |
.../concurrent/*Test.java |
线程名/守护/优先级断言(CustomizedThreadFactoryTest.java:40-61)、关闭语义(ExecutorUtilsTest.java:36-89,hook 队列通过 getStaticFieldValue 排空于 :47-67)、委托转发与替换(DelegatingScheduledExecutorServiceTest.java:28-110、DelegatingBlockingQueueTest.java:36-177) |
.../security/SecurityUtilsTest.java:44-71 |
get/set 往返以及 clearProperty 收尾清理 |
值得注意的覆盖率缺口:没有测试把 ManagementUtils 逼到 -1 状态(SPI 文件始终存在),
没有测试在 supports() 为 true 但字段不可访问的 JDK 上执行 VirtualMachineProcessIdResolver.current(),
也没有任何测试断言 Microsphere 的 SERVICE_LOADER_CACHED 被翻成 true 时的行为。
ProcessManager宣称了自己并不拥有的 API。 Javadoc 条目"Safely destroy a process" (process/ProcessManager.java:19)与示例ProcessManager.INSTANCE.destroy(process)(:30) 描述的是类体(:40-72)从未声明的方法。:25-27的示例还从类外部调用了protected的addUnfinishedProcess(...)。ManagementUtils的降级承诺过强。management/ManagementUtils.java:48-51说当没有解析器能确定 PID 时结果 "默认为UNKNOWN_PROCESS_ID";而:25-31只在null时降级,从不处理抛异常者,且缺失的 SPI 文件会从 静态初始化器抛出(:22、util/ServiceLoaderUtils.java:882-886)。JmxUtils.getAttribute没有可读性卫兵。management/JmxUtils.java:453与:464-465的 Javadoc 承诺当"属性不可读"时返回null,但:456-458直接转发给doGetAttribute;不可读这一情形实际表现为 在:625被捕获并记录日志(:633-640)的AttributeNotFoundException。getMBeanAttributes(:422-427) 会读取每一个声明的属性,因此一个只写属性会让每次快照付出一次带 warn 日志的异常。- 在活动视图上标注
@Immutable。process/ProcessManager.java:67-71返回ConcurrentMap的unmodifiableMap并给它加上@Immutable;调用方迭代期间内容就在变化。 JmxUtils中的非 volatile 静态缓存。 九个字段(management/JmxUtils.java:101-117) 在没有 volatile/锁的情况下发布 —— 关于 MXBean 单例与List缓存之间的精确风险切分见 §6。- 公开面上的拼写错误。
DEFAULT_PROCESS_EXECUTION_TIMEOUT_PROPERTY_VAUE(process/ProcessExecutor.java:65) 是一个public static final字段;重命名它在已发布版本线上属于破坏性变更。 - 线程安全 Javadoc 越界。
concurrent/DelegatingScheduledExecutorService.java:49-50宣称类级线程安全; 只有volatile引用替换是安全的(§6)。 - 两个只存在于文档中的产物。
TestSecurityManager(.../test/java/io/microsphere/security/TestSecurityManager.java:27、:38、:52)没有任何地方引用,而security/SecurityUtils.java:35链接了@see SecurityManager,一个自 JDK 17 起废弃并计划移除、 在 JDK 24 中被禁用的 API —— 对 Javadoc 读者是个陷阱。 - 四个从未使用的
public static final空数组(management/JmxUtils.java:93、:95、:97、:99), 只被JmxUtilsTest(:117-121)使用。 - 契约本身的 Javadoc 小笔误:
process/ProcessIdResolver.java:71渲染成了<code>>null</code>。 - 没有未捕获异常处理。
CustomizedThreadFactory.newThread(concurrent/CustomizedThreadFactory.java:94-99)只设置分组/名称/守护/优先级; 对主干源码做全仓搜索找不到任何UncaughtExceptionHandler用法,这是调用方必须自己填补的一个刻意空缺。
- 面向使用者的对应页面:并发、进程、管理与安全 —— 用户指南
- 本页英文原文:Concurrency, process, management and security — developer guide
- 相关 zh 页面:语言抽象 · 工具类、常量与文本 · 反射、类型与 Bean · 集合与过滤器 · IO 与扫描 · 日志 · 类加载与构件
- JDK 工具 —— 开发者指南 ——
management运行期层的编译期对应物 - 开发者指南索引
- 子片索引:./README.zh.md