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 配置 :

    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 代码

    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",将"可能为空"从隐性知识变为显性契约,从依赖开发者的细心变为依赖工具的保障。

相关推荐
腾渊信息科技公司27 分钟前
Spring Boot集成TDengine实战:工业时序数据存储选型与迁移方案
spring boot·后端·tdengine
xiaoqiMikko15 小时前
Dependabot 面板全绿,不代表你的 Tomcat 没洞
java·spring boot
摇滚侠16 小时前
SpringBoot 官网 阅读笔记 启用生产就绪功能 端点
spring boot·笔记·后端
evans在进步16 小时前
HashMap 为什么线程不安全?ConcurrentHashMap 如何解决?
java·spring boot·spring
小林敲代码778821 小时前
Spring Boot 3 升级踩坑:aj-captcha 行为验证码底图加载失效
java·spring boot·后端
程序员阿明1 天前
spring boot3访问resources下的文件,使得在浏览器url中可以直接访问
java·spring boot·后端
霸道流氓气质1 天前
SpringBoot中使用OAuth2 认证与 JWT Token — 概念、原理与实践
java·spring boot·后端
snow@li1 天前
Vue Axios封装与SpringBoot Payload封装全景关联分析(前后端数据交互底层闭环)
前端·vue.js·spring boot
用户3126874877201 天前
Spring Boot 日志体系到底怎么加载的?从 Logback 到 MDC 的全链路拆解
spring boot
paopaokaka_luck1 天前
基于springboot3+vue3的云南本土影视文旅推荐平台(协同过滤算法、Echarts图形化分析)
前端·spring boot·学习·算法·echarts·mybatis