当前位置: 首页 > news >正文

SpringBoot4.X: 彻底消灭 NullPointerException

一、背景与知识点

1. 背景:

​空指针异常(NullPointerException,简称NPE)曾被称作 “十亿美元的错误”。但 Spring 团队认为,真正的错误并非 “null” 的存在,而是Java 语言没有显式表达可空性(nullability

​Spring 团队正式宣布:Spring Boot 4 与整个 Spring 全家桶已全面实现 Null 安全(Null-Safety),通过引入JSpecify标准注解,让 Java 开发者终于有能力从框架层面杜绝NullPointerException

它的核心理念是:显式表达每一个可能为空的类型

2. 四个org.jspecify.annotations下的注解:
NullMarked NullUnmarked Nullable NonNull
3.安装插件:
io.spring.nullability
4. 最佳实践
  1. 始终使用@NullMarked注解:这可以帮助你在整个项目中强制实施可空性检查。在类级别启用 Null 安全模式,默认所有类型不可为 null;
  2. 合理使用@Nullable@NonNull注解:明确标记哪些变量和方法返回值是可空的,哪些是不可空的。
  3. 用插件直接在编译期完成检查:确保你的代码始终符合JSpecify的规范。

二、实操(采用gradle项目 ):

因为gradle配置比maven简洁的多,另外配置语法采用了kotlin,语法结构上更加规范,并有相应错误检查。

  • 步骤1:创建SpringBoot4.X项目(略)

  • 步骤2:安装检验nullability的插件,spring 团队开发的插件

    id("io.spring.nullability") version "0.0.12"

    完整的build.gradle.kts配置 :

    plugins { java id("org.springframework.boot") version "4.0.5" id("io.spring.dependency-management") version "1.1.7" id("io.spring.nullability") version "0.0.12" // 检验 `nullability` 的插件 } group = "com.yiyi" version = "0.0.1-SNAPSHOT" description = "web" java { toolchain { languageVersion = JavaLanguageVersion.of(25) } } repositories { mavenCentral() maven { name = "Central Portal Snapshots" url = uri("https://central.sonatype.com/repository/maven-snapshots/") } } dependencies { implementation(platform("org.springframework.ai:spring-ai-bom:2.0.0-M4")) implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-data-r2dbc") implementation("io.asyncer:r2dbc-mysql:1.4.1") // AI implementation("org.springframework.ai:spring-ai-starter-model-zhipuai") implementation("org.springframework.ai:spring-ai-starter-model-deepseek") annotationProcessor("org.projectlombok:lombok") compileOnly("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-webflux-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } tasks.withType<Test> { useJUnitPlatform() }
  • 步骤3:在 JAVA 代码的举例

    我们以 web 项目中通用相应体R<T>作为例子讲解。要求:

    code,message不能为空 ,data允许为空(必须声明)。因为类上配置了NullMarked,则默认的值都为非空。

    完整的R.java代码

    package com.yiyi.web.model; import lombok.Data; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; /** * 通用响应体(适配前后端统一的 {code, msg, data} 格式) * @param <T> 数据泛型 */ @NullMarked @Data public class R<T> { /** * 状态码:200成功,其他为失败 */ private int code; /** * 提示信息 */ private String msg = ""; /** * 响应数据 */ @Nullable private T data; // 成功响应(带数据) public static <T> R<T> success(@Nullable T data) { R<T> response = new R<>(); response.setCode(200); response.setMsg("操作成功"); response.setData(data); return response; } // 成功响应(无数据) public static <T> R<T> success() { return success(null); } // 失败响应 public static <T> R<T> error(int code, String msg) { R<T> response = new R<>(); response.setCode(code); response.setMsg(msg); response.setData(null); return response; } // 通用失败响应(默认500) public static <T> R<T> error(String msg) { return error(500, msg); } }
  • 步骤4: 重要说明

    • 安装了io.spring.nullability插件后,你项目中的类必须加上注解@NullMarked@NullUnmarked
    • 为了更好的避免 null 值,先在类上注解@NullMarked, 若某个属性类允许很多的null值,则可在此类上注解@NullUnmarked
    • R.java类中的message 必须赋予初始值(要求非空)
    • 在方法里面,允许为空的要注解上@Nullable,默认都是非空的
    • 集合元素使用List<@Nullable T>标记元素可能为空
三、结语

​编译器变成安检员,在代码运行前就拦截所有"携带违禁品(null)"的行为。让你再也不用为了在运行时或上线时抛出NullException而烦恼。而对哪些允许为null值的变量也标记了明确的注解(至少你应该明确了为什么允许null)。

​项目中为了避免编译器报错,而大量的使用非检查null注解@NullUnmarked,则其提供的好处就不再存在,那甚至去掉此功能更好(至少代码量少一点,编译的开销也少一些)

​注解体系不是要彻底消灭null值——null本身是有用的,表示"值不存在"这一合理业务场景。它真正解决的是"意外的null",将"可能为空"从隐性知识变为显性契约,从依赖开发者的细心变为依赖工具的保障。

http://www.jsqmd.com/news/598900/

相关文章:

  • 9篇8章2节:MIMIC 数据库的 CITI 注册与课程选择(2026年版)
  • 实战指南:基于快马平台构建带第三方登录的tk网站登录页
  • 前端 SSE(Server-Sent Events)实现详解:从原理到前端 AI 对话应用
  • WaveTools重构鸣潮游戏体验:突破性能瓶颈的开源解决方案
  • AI率80%和40%降到20%,难度差了多少?
  • 【React】setState 触发渲染的流程
  • 基于STM32的‘水位检测自动控制系统‘:支持超声波模块、DS18B20传感器,包含原理图、P...
  • 基于液压控制的冲床自动送料机的设计【说明书+CAD+外文翻译】
  • Math.js 使用教程
  • 五相电机双闭环矢量控制模型:原理说明、仿真波形及完整版Simulink模型
  • Windows下5种端口连通性测试方法实测对比(附详细命令)
  • Ostrakon-VL-8B多模态运维监控实战:智能日志分析与故障预警
  • e1547:重新定义e621浏览体验的现代化客户端解决方案
  • Golang笔记1-变量与类型
  • 3步优化鸣潮游戏体验:面向全层级玩家的WaveTools工具箱使用指南
  • VI、 UI 和 UX 设计区别,详细介绍
  • # 发散创新:基于Python与OpenCV的手势识别系统实战详解在人工智能快速发
  • 终极指南:使用payload-dumper-go快速提取Android OTA更新包
  • 【VBA】【EXCEL】分类汇总
  • 篡改猴Tampermonkey失效解决方案
  • 单目相机实战:用OpenCV的solvePnP实现物体位姿估计(附Python代码)
  • C++ STL 核心:string 从入门到精通(面试+源码+OJ实战)
  • 100个服装款的PPT商品详情页,我用这三步1分钟搞定
  • 常见网络连接问题分类
  • 基于非对称纳什谈判理论的微网电能共享运行优化策略:合作博弈与P2P交易完美复现的完美电网技术文献实践
  • 2026年二手化工设备二手制药设备厂家最新推荐:二手蒸发器回收、二手离心机回收、二手干燥机回收、二手混合机回收、二手反应釜回收厂家选择指南 - 海棠依旧大
  • 游戏开发中的“场”魔法:用梯度、散度模拟水流、烟雾与热量扩散
  • ParaView实战:5分钟搞定热流图单元格体积计算(附Python脚本)
  • 4月3日
  • C++ 硬件特征自适应分发:利用 C++ 特性实现对不同 CPU 指令集(AVX2/AVX-512)的运行时代码路径最优选择