开心一刻

今天心情不好,给哥们发语音

我:哥们,晚上出来喝酒聊天吧

哥们:咋啦,心情不好?

我:嗯,刚刚在公交车上看见前女友了

哥们:然后呢?

我:给她让座时,发现她怀孕了...

哥们:所以难受了?

我:不是她怀孕让我难受,是她怀孕还坐公交车让我难受

哥们:不是,她跟着你就不用坐公交车了?不还是也要坐,有区别吗?

我默默的挂断了语音,心情更难受了

Java开发手册

作为一个 javaer,我们肯定看过 AlibabaJava开发手册,作为国内Java开发领域的标杆性编码规范,我们或多或少借鉴了其中的一些规范,其中有一点

我印象特别深,也一直在奉行,自己还从未试过用 is 作为布尔类型变量的前缀,不知道会有什么坑;正好前段时间同事这么用了,很不幸,他挖坑,我踩坑,阿西吧!

is前缀的布尔变量有坑

为了复现问题,我先简单搞个 demo;调用很简单,服务 workflow 通过 openfeign 调用 offline-sync,代码结构如下

qsl-data-govern-common:整个项目的公共模块

qsl-offline-sync:离线同步

  • qsl-offline-sync-api:向外提供 openfeign 接口
  • qsl-offline-sync-common:离线同步公共模块
  • qsl-offline-sync-server:离线同步服务

qsl-workflow:工作流

  • qsl-workflow-api:向外提供 openfeign 接口,暂时空实现
  • qsl-workflow-common:工作流公共模块
  • qsl-workflow-server:工作流服务

完整代码:qsl-data-govern

qsl-offline-sync-server 提供删除接口

/**
* @author 青石路
*/
@RestController
@RequestMapping("/task")
public class SyncTaskController { private static final Logger LOG = LoggerFactory.getLogger(SyncTaskController.class); @PostMapping("/delete")
public ResultEntity<String> delete(@RequestBody SyncTaskDTO syncTask) {
// TODO 删除处理
LOG.info("删除任务[taskId={}]", syncTask.getTaskId());
return ResultEntity.success("删除成功");
}
}

qsl-offline-sync-api 对外提供 openfeign 接口

/**
* @author 青石路
*/
@FeignClient(name = "data-govern-offline-sync", contextId = "dataGovernOfflineSync", url = "${offline.sync.server.url}")
public interface OfflineSyncApi { @PostMapping(value = "/task/delete")
ResultEntity<String> deleteTask(@RequestBody SyncTaskDTO syncTaskDTO);
}

qsl-workflow-server 调用 openfeign 接口

/**
* @author 青石路
*/
@RestController
@RequestMapping("/definition")
public class WorkflowController { private static final Logger LOG = LoggerFactory.getLogger(WorkflowController.class); @Resource
private OfflineSyncApi offlineSyncApi; @PostMapping("/delete")
public ResultEntity<String> delete(@RequestBody WorkflowDTO workflow) {
LOG.info("删除工作流[workflowId={}]", workflow.getWorkflowId());
// 1.查询工作流节点,查到离线同步节点(taskId = 1)
// 2.删除工作流节点,删除离线同步节点
ResultEntity<String> syncDeleteResult = offlineSyncApi.deleteTask(new SyncTaskDTO(1L));
if (syncDeleteResult.getCode() != 200) {
LOG.error("删除离线同步任务[taskId={}]失败:{}", 1, syncDeleteResult.getMessage());
ResultEntity.fail(syncDeleteResult.getMessage());
}
return ResultEntity.success("删除成功");
}
}

逻辑是不是很简单?我们启动两个服务,然后发起 http 请求

POST http://localhost:8081/data-govern/workflow/definition/delete

Content-Type: application/json

{

"workflowId": 99

}

此时 qsl-offline-sync-server 日志输出如下

2025-06-30 14:53:06.165|INFO|http-nio-8080-exec-4|25|c.q.s.s.controller.SyncTaskController :删除任务[taskId=1]

至此,一切都很正常,第一版也是这么对接的;后面 offline-sync 进行调整,删除接口增加了一个参数:isClearData

public class SyncTaskDTO {

    public SyncTaskDTO(){}

    public SyncTaskDTO(Long taskId, Boolean isClearCache) {
this.taskId = taskId;
this.isClearData = isClearCache;
} private Long taskId;
private Boolean isClearData = false; public Long getTaskId() {
return taskId;
} public void setTaskId(Long taskId) {
this.taskId = taskId;
} public Boolean getClearData() {
return isClearData;
} public void setClearData(Boolean clearData) {
isClearData = clearData;
}
}

然后实现对应的逻辑

/**
* @author 青石路
*/
@RestController
@RequestMapping("/task")
public class SyncTaskController { private static final Logger LOG = LoggerFactory.getLogger(SyncTaskController.class); @PostMapping("/delete")
public ResultEntity<String> delete(@RequestBody SyncTaskDTO syncTask) {
// TODO 删除处理
LOG.info("删除任务[taskId={}]", syncTask.getTaskId());
if (syncTask.getClearData()) {
LOG.info("清空任务[taskId={}]历史数据", syncTask.getTaskId());
// TODO 清空历史数据
}
return ResultEntity.success("删除成功");
}
}

调整完之后,同事通知我,让我做对 qsl-workflow 做对应的调整。调整很简单,qsl-workflow 删除时直接传 true 即可

/**
* @author 青石路
*/
@RestController
@RequestMapping("/definition")
public class WorkflowController { private static final Logger LOG = LoggerFactory.getLogger(WorkflowController.class); @Resource
private OfflineSyncApi offlineSyncApi; @PostMapping("/delete")
public ResultEntity<String> delete(@RequestBody WorkflowDTO workflow) {
LOG.info("删除工作流[workflowId={}]", workflow.getWorkflowId());
// 1.查询工作流节点,查到离线同步节点(taskId = 1)
// 2.删除工作流节点,删除离线同步节点
// 删除离线同步任务,isClearData直接传true
ResultEntity<String> syncDeleteResult = offlineSyncApi.deleteTask(new SyncTaskDTO(1L, true));
if (syncDeleteResult.getCode() != 200) {
LOG.error("删除离线同步任务[taskId={}]失败:{}", 1, syncDeleteResult.getMessage());
ResultEntity.fail(syncDeleteResult.getMessage());
}
return ResultEntity.success("删除成功");
}
}

调整完成之后,发起 http 请求,发现历史数据没有被清除,看日志发现

LOG.info("清空任务[taskId={}]历史数据", syncTask.getTaskId());

没有打印,参数明明传的是 true 吖!!!

offlineSyncApi.deleteTask(new SyncTaskDTO(1L, true));

这是哪里出了问题?

问题排查

因为 qsl-offline-sync-api 是直接引入的,并非我实现的,所以我第一时间找到了其实现者,反馈了问题后让其自测下;一开始他还很自信,说这么简单怎么会有问题

当他启动 qsl-offline-sync-server 后,发起 http 请求

POST http://localhost:8080/data-govern/sync/task/delete

Content-Type: application/json

{

"taskId": 123,

"isClearData": true

}

发现 isClearData 的值是 false

此刻,疑问从我的额头转移到了他的额头上,他蒙蔽了,我轻松了。为了功能能够正常交付,我还是决定看下这个问题,没有了心理压力,也许更容易发现问题所在。第一眼看到 isClearData,我就隐约觉得有问题,所以我决定仔细看下 SyncTaskDTO 这个类,发现 isClearDatasettergetter 方法有点不一样

private Boolean isClearData = false;

public Boolean getClearData() {
return isClearData;
} public void setClearData(Boolean clearData) {
isClearData = clearData;
}

方法名是不是少了 Is?带着这个疑问我找到了同事,问他 settergetter 为什么要这么命名?他说是 idea 工具自动生成的(也就是我们平时用到的idea自动生成setter、getter方法的功能)

我让他把 Is 补上试试

private Boolean isClearData = false;

public Boolean getIsClearData() {
return isClearData;
} public void setIsClearData(Boolean isClearData) {
this.isClearData = isClearData;
}

发现传值正常了,他回过头看着我,我看着他,两人同时提问

他:为什么加了 Is 就可以了?

我:布尔类型的变量,你为什么要加 is 前缀?

问题延申

作为一个严谨的开发,不只是要知其然,更要知其所以然;关于

为什么加了 Is 就可以了

这个问题,我们肯定是要会上一会的;会这个问题之前,我们先来捋一下参数的流转,因为是基于 Spring MVC 实现的 Web 应用,所以我们可以这么问 deepseek

Spring MVC 是如何将前端参数转换成POJO的

能够查到如下重点信息

RequestResponseBodyMethodProcessorresolveArgument

/**
* Throws MethodArgumentNotValidException if validation fails.
* @throws HttpMessageNotReadableException if {@link RequestBody#required()}
* is {@code true} and there is no body content or if there is no suitable
* converter to read the content with.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
} return adaptArgumentIfNecessary(arg, parameter);
}

正是解析参数的地方,我们打个断点,再发起一次 http 请求

很明显,readWithMessageConverters 是处理并转换参数的地方,继续跟进去会来到 MappingJackson2HttpMessageConverterreadJavaType 方法

此刻我们可以得到,是通过 jackson 完成数据绑定与数据转换的。继续跟进,会看到 isClearData 的赋值过程

通过前端传过来的参数 isClearData 找对应的 setter方法是 setIsClearData,而非 setClearData,所以问题

为什么加了 Is 就可以了

是不是就清楚了?

问题解决

  1. 按上述方式调整 isClearDatasettergetter 方法

    带上 is

    public Boolean getIsClearData() {
    return isClearData;
    } public void setIsClearData(Boolean isClearData) {
    this.isClearData = isClearData;
    }
  2. 布尔类型的变量,不用 is 前缀

    可以用 if 前缀

    private Boolean ifClearData = false;
    
    public Boolean getIfClearData() {
    return ifClearData;
    } public void setIfClearData(Boolean ifClearData) {
    this.ifClearData = ifClearData;
    }
  3. 可以结合 @JsonProperty 来处理

    @JsonProperty("isClearData")
    private Boolean isClearData = false;

总结

  1. Spring MVC 对参数的绑定与转换,内容不同,采用的处理器也不同

    1. form表单数据(application/x-www-form-urlencoded)

      处理器:ServletModelAttributeMethodProcessor

    2. JSON 数据 (application/json)

      处理器:RequestResponseBodyMethodProcessor

      转换器:MappingJackson2HttpMessageConverter

    3. 多部分文件 (multipart/form-data)

      处理器:MultipartResolver

  2. POJO 的布尔类型变量,不要加 is 前缀

    命名不符合规范,集成第三方框架的时候就很容易出不好排查的问题

    成不了规范的制定者,那就老老实实遵循规范!

都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑我了嘛的更多相关文章

  1. POJO类中的任何布尔类型的变量,都不要加is

    POJO类中的任何布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误. 定义为基本数据类型boolean isSuccess:的属性,它的方法也是isSuccess(),HSF框架在反向解 ...

  2. C++中对一个布尔类型的变量按位取反结果不变

    C++中对一个bool类型的变量按位取反是无效的.例如: bool a = true; bool b = ~a; // b的值还是true

  3. 016 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 10 布尔类型和字符串的字面值

    016 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 10 布尔类型和字符串的字面值 本文知识点:字面值 关于字面值的概念,需要注意:很多地方,我们可能就把字面值 ...

  4. Python 学习 第一篇:数据类型(数字,集合,布尔类型,操作符)

    Python语言最常用的对象是变量和常量,常量的值是字面意思,其值是不可变的,变量的值是可变的,例如,123,"上海"是常量,而a=1,a=2,其中a是变量名.内置的核心数据类型有 ...

  5. POJO类中布尔类型为啥不让用isXxx命名

    源码面前,了无秘密 <阿里开发规范泰山版>(2020.04.22)-->编程规约-->(一) 命名风格-->第8条规定: [强制]POJO 类中的任何布尔类型的变量,都不 ...

  6. BOOL布尔类型

    1.BOOL数据类型,是一种表示非真即假的数据类型,布尔类型的变量只有YES和NO两个值.YES表⽰示表达式结果为真,NO表示表达式结果为假. 2.在C语言中,认为非0即为真. 3.分⽀支语句中,经常 ...

  7. python元组类型的变量以及字符串类型的变量作为参数进行传值

    今天做selenium元素对象剥离时(我把元素对象都放到了元组类型的变量中,格式:user = (“id”,“X-Auto-2”)),遇到一个元组变量,以及str字符串变量一起作为参数传值的问题,发现 ...

  8. Python:Base1(数据类型,print语句,变量,定义字符串,raw字符串与多行字符串,Unicode字符串,整数和浮点数运算,布尔类型运算)

    1.Python中数据类型: 计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值.但是,计算机能处理的远不止数值,还可以处理文本.图形.音频.视频.网页等各种各样的数据 ...

  9. ECMAScript1.1 js书写位置 | 声明变量 | 基本数据类型 | 数据类型转换 | 操作符 | 布尔类型的隐式转换

    js书写位置 由于在写css样式时使用的时双引号,所以我们在写js代码时建议使用单引号(‘’)! 行内式 <input type="button" value="点 ...

  10. Python 为什么要在 18 年前引入布尔类型?且与 C、C++ 和 Java 都不同?

    花下猫语:在上一篇<Python 为什么能支持任意的真值判断? >文章中,我们分析了 Python 在真值判断时的底层实现,可以看出 Python 在对待布尔值时,采用了比较宽泛的态度.官 ...

随机推荐

  1. .NET Cas 认证(基于Cookie)

    项目需求:开发系统A 对接客户公司的cas 认证系统 B,实现单点登录 业务场景描述:打开A 系统地址,判断Cookie 是否登录状态,如果未登录,跳转B登录界面:如果已登录,直接获取到cookie ...

  2. DeepSeek引发的AI发展路径思考

    DeepSeek引发的AI发展路径思考 参考文章来源于科技导报 ,作者李国杰院士 | 哈工大 DeepSeek 技术前沿与应用讲座 1. DeepSeek 的科技突破 7 天之内 DeepSeek 的 ...

  3. 学习Django【1】模型

    编辑 models.py 文件,改变模型. 运行 python manage.py makemigrations 为模型的改变生成迁移文件. 运行 python manage.py migrate 来 ...

  4. datasnap的监督功能【2】-管理Session

    1.服务端的Session是有TDSSession定义的.TDSSession提供了许多有用的方法和特性,再开发室取得服务or重要信息. 如Session状态.安排Session独享定时or自动执行工 ...

  5. C中输入输出

    引入一个概念,对于计算机来说,外来数据都是输入,经过计算机处理的结果并进行显示的就是输出.在linux里面,一切都是文件,就连输入输出,都可以划归到"文件"一类,而为了管理这些文件 ...

  6. RocketMQ的Producer是如何发送消息的

    RocketMQ 的 Producer 发送消息过程涉及多个步骤,包括初始化.消息创建.发送方式选择 1.Producer初始化 首先,我们需要创建并初始化一个Producer示例 这段代码完成了以下 ...

  7. thinkphp里__PUBLIC__的使用

    1.默认值 __PUBLIC__常量默认指向当前项目根目录下的pulic目录, 例如:www下有一个blog项目目录,blog下一般有application.Home.public.Thinkphp ...

  8. Windows系统优化 3-清理预安装软件

    事件起因: 经过我们上次 Windows系统优化 2-系统设置优化 之后,现在电脑已经基本上可以使用,不过对于有强迫症的我来说还差了一步,那就是系统预安装的软件: 对于我们刚入手的电脑你是否有 这些 ...

  9. 适用于LixtBox的,开启UI虚拟化时,某些时候需要定位到还没加载的项,比如自动选中某项,视图自动移过去等等

    1 /// <summary> 2 /// 将指定父级的下级索引元素,显示在视野下,使其可见 3 /// </summary> 4 /// <param name=&qu ...

  10. Python—Pytorch学习-RNN(一)

    前言 有好几个月没搞神经网络代码了,期间也就是回顾了两边之前的文字. 不料,对nn,cnn的理解反而更深入了-_-!. 修改 <零基础学习人工智能-Python-Pytorch学习(四)> ...