| 日期 | 更新说明 |
|---|---|
| 2025年12月3日 | 初版完成 |
| 2025年12月6日 | 案例编写 |
前言
如果编程语言最容易出现的错误,那么 NPE(NullPointerException)绝对算一个了;让我们时光追溯到1965年,霍尔在设计ALGOL W语言时首次引入空引用(null),初衷是简化编译器实现,空引用性就成为一种零成本的抽象,用于表达值的潜在缺失,并且与现有 API 向后兼容;但在他在2009年提出是一个十亿美元的错误’”(Billion-Dollar Mistake)。rust甚至放弃了 null 的 设计,对于 javaer从来没有放弃过努力。
由来
规避 NPE 最好的方式就是在编译阶段明确,“空注解”应运而生加上静态扫描的工具,基本可以解决。但在此之前,各种“可空性注解”群魔乱舞:
| 注解来源 | 示例 | 状态 |
|---|---|---|
| JSR-305 | @Nonnull, @Nullable | 废案(JSR 305 没有最终版) |
| JetBrains | @NotNull, @Nullable | IntelliJ 私有实现 |
| Checker Framework | @NonNull, @Nullable | 学术/工业项目使用 |
| Android Support / AndroidX | @NonNull, @Nullable | Android 生态使用 |
| Lombok | @NonNull | Lombok 专用 |
| Eclipse JDT | @NonNull, @Nullable | Eclipse 专用 |
大概在 2019–2022 期间,Google 主导并联合:
- JetBrains(IntelliJ)
- Checker Framework 团队(学术界)
- Kotlin 核心成员(提供 null-safety 经验)
- ErrorProne / NullAway 团队
→ NullAway 基于 ErrorProne,是 Google 内部广泛使用
Java 的官方 Nullability 标准(事实上的标准);为什么说实施标准呢?第一次形成了跨工具的一致性(所谓的“生态”)
- ErrorProne / NullAway
- IntelliJ
- Eclipse
- Checker Framework
- AndroidX
都可以用同一个注解解释 nullability。
| 时间 | 事件 |
|---|---|
| 2019 | Google 与学界开始制定初版规范 |
| 2021 | 首批 draft 发布 |
| 2022 | NullAway / Checker Framework 开始部分支持 |
| 2023 | JSpecify 1.0 beta 版本发布 |
| 2024 | JSpecify 1.0.0 正式版发布,进入生产可用阶段 |
| 2024–2025 | Spring Boot 4 等现代框架开始推荐 JSpecify |
本质
JSpecify 为 Java 生态提供一个跨工具统一的 Nullability 类型语义标准,使静态分析器能够在编译期进行一致的 null 审查,并为未来 Java 官方的 null-safety 语言特性铺路。
JSpecify 的设计哲学是 “默认非空(Null-Marked),只标注例外,而不是标注正常情况。 ” ;这个独到的设计哲学符合我们的编程方式,仔细想想,空值不是常态才是。JSpecify 非常克制,只包含 4 个核心注解:
| 注解 | 作用 | 属于哪一类 |
|---|---|---|
@Nullable | 标注某个类型可以接受 null | 成员注解 |
@NonNull | 标注某个类型不允许为 null | 成员注解 |
@NullMarked | 将整个包或类默认为 NonNull(默认非空) | 默认注解 |
@NullUnmarked | 将子包/类重新设回“无默认规则” | 默认注解 |
使用
SpringBoot4 JSpecify 官方使用案例
JacksonJsonParser 为例:
JacksonJsonParser提前剧透下一篇更新关于 Jackson3的内容哈。
案例程序
注意切换到jspecify分支
其实开发过程中 IDE 工具基本可以提示你的错误;注意注释部分内容:
到这你以为结束了,国内其他博客可能会,而我这不会。
编译构建
mvn -DskipTests=true compile
关键部分内容:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<release>17</release>
<encoding>UTF-8</encoding>
<fork>true</fork>
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>--should-stop=ifError=FLOW</arg>
<arg>-Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked -XepOpt:NullAway:JSpecifyMode=true</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.42.0</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<version>0.12.12</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
- Error Prone(Google 的 Java 静态分析器)最核心、最重要的模块。如果你要启用 ErrorProne 或 NullAway,它几乎一定会出现在你的依赖里
- NullAway = Java 的“快速、实用、接近 Kotlin 级别”的空指针静态检查器;
-
- 几乎零误报
- 超快(对大型项目几乎无编译损耗)
- 与 JSpecify 注解兼容
- Google 和 Uber 大规模生产实践
超低的误报率:
| 工具 | 特点 | 误报率 |
|---|---|---|
| Checker Framework | 非常强但非常重 | ❗高 |
| FindBugs / SpotBugs | 老工具,误报多 | ❗高 |
| IntelliJ Nullability | 轻量,分析不完全 | 中 |
| NullAway | 平衡度极好,生产环境友好 | 最低 |
缺点:
需要 JDK22+;另外对于使用 lombok不太友好。
注意 NullAway JSpecify 模式需要较新的 javac 版本,因此我们建议如果可以的话使用 Java 25,否则大多数 JDK 21.0.8+发行版(除 Oracle JDK 外)应该支持 -XDaddTypeAnnotationsToSymbol=true 标志,这将允许 NullAway 按预期工作。未来可能会提供这个标志的 Java 17 后端。如果像 Spring 一样需要保留 Java 17 基线,你可以使用 Java 25 工具链,并像jspecify-nullaway-demo.中所示那样配置 Maven 或 Gradle 构建的 javac 选项 --release 17
总结
当 Spring Boot 4 发布并在您的应用程序中使用时,特别是如果您在应用级别也启用了这些空值检查,那么在生产环境中 NullPointerException 的风险将显著降低甚至消除,因为只有来自第三方库的类型才可能导致这种情况。通过明确指定可能发生空引用的位置、处理这些代码路径并引入相关的自动检查,我们将“价值十亿美元的错误”转化为零成本的抽象,允许表达值的潜在缺失,从而显著提高 Spring 应用程序的安全性。
补充
截至发文(2025年12月6日)spring官方完成如下内容:
以下项目现在提供 null-safe API:
- Spring Boot 4.0
- Spring Framework 7.0
- Spring Data 4.0
- Spring Security 7.0
- Spring Batch 6.0
- Spring Kafka 4.0
- Spring Integration 7.0
- Spring GraphQL 2.0
- Spring Web Services 5.0
- Spring AMQP 4.0
- Spring Shell 4.0
- Spring Kafka 4.0
- Spring Plugin 4.0
- Spring HATEOAS 3.0
- Spring Modulith 2.0
- Spring Vault 4.0
- Spring Cloud Commons 5.0
- Spring Cloud Gateway 5.0
- Micrometer 1.16
- Micrometer Tracing 1.6
- Context Propagation 1.2
- Reactor 2025.0
有些 Spring 项目尚未提供空安全 API,但计划在不久的将来提供:
- Spring AI (planned in 2.0)
Spring AI(计划在 2.0 版本) - Spring Session
- Spring LDAP
- Spring gRPC (tentatively planned in 1.0)
Spring gRPC (计划在 1.0 版本中推出) - The rest of Spring Cloud (tentatively planned in 2026.0)
Spring Cloud 的其余部分 (计划在 2026.0 版本中推出)
扩展阅读: