从零开始 Spring Boot 33:Null-safety

news2024/12/29 13:06:44

从零开始 Spring Boot 33:Null-safety

spring boot

图源:简书 (jianshu.com)

Null-safety(null安全)实际上是Java这个“古老”语言的历史包袱,很多新的语言(比如gokotlin)在诞生起就在语言层面提供对null安全的解决方案。

实际工作中有相当一部分bug都是“空指针异常”。

Spring框架提供一些注解作为null安全这一问题的解决方案,可以通过在Spring框架中使用这些注解来在编码阶段尽早发现一部分“空指针异常”引起的bug。

Spring框架提供以下注解:

  • @Nullable: 注解,表明一个特定的参数、返回值或字段可以是 null 的。
  • @NonNull: 注解表明特定的参数、返回值或字段不能为 null(在参数/返回值和字段上不需要,因为 @NonNullApi@NonNullFields 分别适用)。
  • @NonNullApi: 包级的注解,声明非null为参数和返回值的默认语义。
  • @NonNullFields: 在包一级的注解,声明非null为字段的默认语义。

这些注解都属于org.springframework.lang包。

@NonNull

看下面这个示例:

@Service
public class HelloService {
    public Integer plusWithNoAnnotation(Integer a, Integer b) {
        return a + b;
    }
}

@RestController
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    private HelloService helloService;

    @GetMapping("")
    public Object hello() {
        helloService.plusWithNoAnnotation(1, null);
        return null;
    }
}

实际上HelloService.plusWithNoAnnotation()方法的两个参数我们都不希望是null,但这在编码中是很难发现的(如果你不仔细阅读将要调用的方法源码的话),大多数情况是要真正运行这段代码才会发现这里会产生一个空指针异常。

这种问题可以用@Nonull注解来改善:

@Service
public class HelloService {
	// ...
	@NonNull
    public Integer plus(@NonNull Integer a, @NonNull Integer b) {
        return a + b;
    }
}

示例中的@NonNull注解表明plus()方法的两个参数ab,以及返回值都应当是非null的。

当然,这种约束不是强制性的,目前并没有被Java社区采纳并实施,但依然可以利用IDE等相关工具来尽早发现此类问题,比如在 IntelliJ IDEA 中,如果调用示例中的方法且传入null,就会出现提示:

image-20230522171906874

这是一个warning:

image-20230522172103507

具体IDEA怎么处理这个注解,是否要显示相应的提示,提示是显示为warning还是error,这些都是可以在IDEA中设置的:

image-20230522172354134

@NonNull同样可以用于标记属性:

public class Person {
    @NonNull
    private String name;
}

image-20230522172740398

提示说的很清楚:@NonNull标记的属性必须被初始化。

@Nullable

@Nullable的意思是可以是null(也可以不是)。同样的,这个注解也可以用于标记属性、方法参数、返回值。

看起来似乎@Nullable没有什么意义,平时不使用这类注解的时候大多数参数都是这个隐含意思。但有没有用@Nullable明确表明是有意义的,比如下面这个示例:

@Service
public class HelloService {
	// ...
	@NonNull
    public Integer plusAllowNull(@Nullable Integer a, @Nullable Integer b) {
        return a + b;
    }
}

在IDEA中会有下面的提示:

image-20230522173535938

很显然,如果ab可能是null,你就需要进行处理,而不是直接利用包装类进行运算:

@Service
public class HelloService {
	// ...
	@NonNull
    public Integer plusAllowNull(@Nullable Integer a, @Nullable Integer b) {
        if (a == null) {
            a = 0;
        }
        if (b == null) {
            b = 0;
        }
        return a + b;
    }
}

@NonNullApi 和 @NonNullFields

虽然前面说的@NonNull@Nullable对我们发现“空指针异常”并减少bug很有用,但对于每个属性和方法都加上一堆@NonNull@Nullable注解无疑相当繁琐。对此,我们可以利用@NonNullApi@NonNullFields这两个注解进行一定程度上的简化。

@NonNullApi@NonNullFields是定义在包上的注解(其定义是@Target({ElementType.PACKAGE})),前者的意思是指定包下的方法默认返回的是非Null的值并且参数也是非null的,后者的意思是指定包下的类属性默认是非Null的。

看示例,假如我们的项目结构是这样的:

image-20230522181135895

要使用包注解,我们需要在相应的包(这里是com.example.nullsafe.util)下创建一个package-info.java文件,其内容大概像这样:

@NonNullApi
@NonNullFields
package com.example.nullsafe.util;

import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

这个文件只包含当前包名的信息,只不过我们可以选择使用@NonNullApi@NonNullFields进行标注。

现在,com.example.nullsafe.util这个包下的代码,默认都应该遵循下面的规则:

  • 类属性应当是非null的。
  • 方法的返回值都应当是非null的。
  • 方法参数应该是非null的。

如果你没有这么做,IDEA就会以warning的方式报告错误,比如:

package com.example.nullsafe.util;
// ...
@Component
public class MyUtil {
    private Integer test;

    public Integer plus(Integer a, Integer b) {
        Integer c = a + b;
        return null;
    }
}

image-20230522182238971

这相当于我们为该包下所有的属性和方法都添加了@NonNull注解,因此现在我们只要在必要的时候添加上@Nullable注解就可以了,比如:

@Component
public class MyUtil {
    @Nullable
    private Integer test;

    public Integer plus(@Nullable Integer a, @Nullable Integer b) {
        if (a == null) {
            a = 0;
        }
        if (b == null) {
            b = 0;
        }
        Integer c = a + b;
        return c;
    }
}

继承的影响

OOP中,继承可能会引发一些复杂的影响,比如协变反协变,Null-sfety也存在类似的问题,比如:

public class Parent {
    @Nullable
    public Integer plus(Integer a, Integer b) {
        Integer c = a + b;
        if (c <= 0) {
            return null;
        }
        return c;
    }
}

public class Child extends Parent {
    @NonNull
    @Override
    public Integer plus(Integer a, Integer b) {
        return a + b;
    }
}

Child覆盖了父类Parentplus方法,父类返回值用@Nullable标记,子类返回值用@NonNull标记,这样是没有问题的。但如果反过来:

public class GrandSon extends Child{
    @Nullable
    @Override
    public Integer plus(Integer a, Integer b) {
        return super.plus(a, b);
    }
}

image-20230522184314861

提示说的很清楚:不能用@Nullable方法重写@NonNull方法。

对于参数,同样存在类似反协变的现象,比如:

public class Parent {
    @Nullable
    public Integer plus(@NonNull Integer a, @NonNull Integer b) {
        Integer c = a + b;
        if (c <= 0) {
            return null;
        }
        return c;
    }
}

public class Child extends Parent {
    @NonNull
    @Override
    public Integer plus(@Nullable Integer a, @Nullable Integer b) {
        return a + b;
    }
}

父类Parentplus方法的参数是@NonNull标记的,子类的plus方法是@Nullable标记的,这样是可行的。但如果反过来:

public class Child extends Parent {
    @NonNull
    @Override
    public Integer plus(@Nullable Integer a, @Nullable Integer b) {
        return a + b;
    }
}

public class GrandSon extends Child{
    @NonNull
    @Override
    public Integer plus(@NonNull Integer a, @NonNull Integer b) {
        return super.plus(a, b);
    }
}

image-20230522185058546

错误提示是:@NonNull注解不能用于覆盖@Nullable标记的参数。

这些规定都类似于里氏替换原则

关于LSP的详细说明可以阅读里氏替换原则——面向对象设计原则 (biancheng.net)。

需要说明的是,这种限制是非强制的,不是语言层面的,可以看做是一种“符合李氏替换原则的编程建议”,你完全可以无视或者关闭IDEA的相关注解提示。

The End,谢谢阅读。

本文的所有示例可以从ch33/null-safe · 魔芋红茶/learn_spring_boot - 码云 - 开源中国 (gitee.com)获取。

参考资料

  • @Nullable和@NotNull注释的使用_w3cschool
  • Null安全(Null-safety) (springdoc.cn)
  • Spring 中的Null-Safety - 程序员cxuan - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/555398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试需要学习什么?好学吗?需要学多久?到底是报班好还是自学好?

目录 前言&#xff1a; 【文章的末尾给大家留下了大量的福利哦。】 一&#xff1a;软件测试好学吗&#xff1f;需要学习多久&#xff1f; 二&#xff1a;那么选择软件测试行业有什么优势呢&#xff1f; 三&#xff1a;再来说说大家最关心的——软件测试人员的薪资怎么样? …

Spring : XML配置 JavaBean

文章目录 前言一、xml 加载 Bean 对象总结XML加载Bean对象 前言 跟着大佬走&#xff01;&#xff01;&#xff01;&#xff01; https://github.com/DerekYRC/mini-spring 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、xml 加载 Bean 对象 大家先…

【C语言】数组名作函数参数

数组名作函数参数 引例思考例2通用性指针形参和数组形参几点说明 引例 在主函数中输入10个整数&#xff0c;并存入一个一维数组中&#xff1b;然后在被调函数中&#xff0c;将0号元素的值改为原值的10倍&#xff1b;最后在主函数中输出结果。 思路&#xff1a; 若想在被调函数…

10:00进去,10:05就出来了,这问的也太变态了···

从外包出来&#xff0c;没想到死在另一家厂子了。 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到5月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内推…

SSM框架-SpringMVC

1. SpringMVC 1.1 Spring与Web环境集成 ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的&#xff0c;但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) &…

ActiveMq消息队列

ActiveMq是一种开源的java程序&#xff0c;支持Java消息服务(JMS) 1.1 版本 一、持久化机制 1、KahaDB&#xff1a;5.4及之后版本&#xff0c;默认使用日志文件 activemq.xml默认使用KahaDB持久化存储&#xff0c;默认配置安装路径data目录下 <persistenceAdapter> …

Django框架之模板其他补充

本篇文章是对django框架模板内容的一些补充。包含注释、html转义和csrf内容。 目录 注释 单行注释 多行注释 HTML转义 Escape Safe Autoescape CSRF 防止csrf方式 表单中使用 ajax请求添加 注释 单行注释 语法&#xff1a;{# 注释内容 #} 示例&#xff1a; {# 注…

09 FPGA—利用状态机实现可乐售卖机(附代码)

1. 理论 FPGA 是并行执行的&#xff0c;如果我们想要处理具有前后顺序的事件&#xff0c;就需要引入状态机。举个例子&#xff0c;将人看成 FPGA ,我们可以在散步的时候听歌和聊天这是并行执行的&#xff0c;但一天的行程安排却是以时间段前后执行的。 状态机简写为 FSM&#…

java前后端分离有详细内容吗?

微服务架构java前后端分离都有哪些具体内容&#xff1f;目前&#xff0c;有不少客户朋友经常询问我们类似的问题。其实&#xff0c;在新的经济发展形势下&#xff0c;提质增效的低代码开发平台微服务架构早已成为不少新老客户的选择&#xff0c;它们不仅能提高办公协作效率&…

成为更优秀的项目经理:快速提升影响力的六大原则与独门秘笈

在很多公司的组织架构中&#xff0c;项目经理并不是一个常规的职能岗位&#xff0c;项目组是为了某个项目目标临时组建的团队&#xff01; 这就造成了PM一个很尴尬的处境&#xff0c;权、责、利不匹配&#xff0c;也就是有责无权&#xff1a;PM既要对项目目标的实现负责&#…

K8S内容分发网络之集群,nginx,负载均衡,防火墙

目录 第一章.实验架构需求 第二章.实验环境准备 2.1.节点准备 2.2.环境准备 2.3.在master&#xff0c;node01&#xff0c;node02上操作安装docker 2.4.所有节点安装kubeadm&#xff0c;kubelet和kubectl 2.5.部署K8S集群 2.6.在master节点操作 2.7.所有节点部署网络插件…

小白windows安装python(图文详解)

以下是在 Windows 操作系统上安装 Python 的详细步骤&#xff1a; 打开浏览器&#xff0c;进入 Python 官网&#xff08;https://www.python.org/&#xff09;。 点击“Downloads”&#xff0c;然后选择适合您的操作系统的 Python 版本。例如&#xff0c;如果您的操作系统是…

“Shell“firewall防火墙

文章目录 一.Firewalld防火墙1.1firewalld概述1.2Firewalld和iptables的关系1.3Firewalld和iptables的区别 二.Firewalld网络区域2.1区域介绍&#xff1a;2.2firewalld 区域的概念:2.3Firewalld数据处理流程2.4Firewalld检查数据包的源地址的规则&#xff1a; 三.Firewalld防火…

05 Android开机启动之SystemServer

Android开机启动之SystemServer(SS) 一、梳理SystemServer启动流程 从上面整个Android开机启动思维导图(android 5.0的启动组成图)中可以看到: SystemServer是从Zygote中启动的。 开机->bootloader->kernel->init->zygote->SystemServer 二、SystemServe…

【嵌入式烧录/刷写文件】-1.5-Fill填充Motorola S-record(S19/SREC/mot/SX)文件

案例背景(共8页精讲)&#xff1a;该篇将告诉你&#xff0c;如何对一个S19文件进行填充&#xff1a; 对“起始地址”和“结束地址”内的非连续的Block块&#xff0c;进行填充&#xff1b;自定义填充范围。 目录 1 为什么要“Fill填充” 2 使用Vector HexView工具“填充”S19…

阿里,变“小”了,也变强了

文 | 螳螂观察 作者 | 青月 小公司总想做大&#xff0c;但在如今快速变换的科技浪潮下&#xff0c;一些大企业却想“变小”。 3月28日&#xff0c;阿里巴巴宣布启动“16N”组织变革&#xff0c;这意味着未来具备条件的业务集团和业务公司&#xff0c;都可以独立融资和独立上…

一文带你了解MySQL之InnoDB 统计数据是如何收集的

前言 本文章收录在MySQL性能优化原理实战专栏&#xff0c;点击此处查看更多优质内容。 我们前边唠叨查询成本的时候经常用到一些统计数据&#xff0c;比如通过show table status可以看到关于表的统计数据&#xff0c;通过show index可以看到关于索引的统计数据&#xff0c;那…

分享国内可用的免费ChatGPT网站_测评by杂草小生

参考的文章1&#xff1a;ChatGPT套壳网站汇总-5月21日更新_QQVQQ...的博客-CSDN博客 参考文章2&#xff1a;分享一个国内可用的免费ChatGPT网站_Aaron_Plus的博客-CSDN博客 ChatGPT是基于自然语言处理技术的聊天机器人&#xff0c;可以进行对话和提供相关信息。由于chatGPT不…

导入/导出 Postcat 格式文件,打通数据不再难

导入 Postcat 插件。 使用 导入功能有多个入口&#xff0c;你可以在 API 分组处点击加号导入 API&#xff1a; 也可以在点击设置&#xff0c;然后选择导入选项 导出 Postcat 插件 支持导出 Postcat JSON 文件。 使用 进入空间页面&#xff0c;可以看到导出功能&#xff0c;点…

XXL-SSO简要说明

一、介绍 XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 拥有”轻量级、分布式、跨域、CookieToken均支持、WebAPP均支持”等特性。现已开放源代码&#xff0c;开箱即用。 官方文档 二、集成 2.1、源码下载 下载地址 2.2、代码结构…