惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 青石路

Claude Code安装,接入阿里云百炼模型,蹭蹭免费额度 异源数据同步 → 记一次 DataX 已同步数据量优化 明明连接的是Redis的DB0,为什么能查到DB3的数据? TINYINT(1) 类型的字段,明明数据存的是 2,为什么查出来是 true 用了MySQL的INSERT ON DUPLICATE KEY UPDATE,怎么还报唯一索引冲突错误 那些年不该放到事务中的操作,你实现过哪些 关于布尔类型的变量不要加 is 前缀,被网友们吐槽了,特来完善下 安全漏洞修复导致SpringBoot2.7与Springfox不兼容,问题排查与处理 记一次SQL隐式转换导致精度丢失问题的排查 → 不规范就踩坑 经由同个文件多次压缩的文件MD5都不一样问题排查,感慨AI的强大! 记一次cannot access its superinterface问题的的排查 → 强如Spring也一样写Bug SpringBoot支持Kafka多源配置的同时还要支持启停配置化,是真的会玩 如果XXL-JOB执行器在执行某任务中被重启了,重启后该任务能够被自动弥补调度吗 Spring Boot读取外部配置文件失败,原因绝对出乎你意料 不依赖 Spring,你会如何自实现 RabbitMQ 消息的消费(一) 异源数据同步 → DataX 同步启动后如何手动终止? 异源数据同步 → 如何获取 DataX 已同步数据量? 记一次 RabbitMQ 消费者莫名消失问题的排查 不升级 POI 版本,如何生成符合新版标准的Excel 2007文件 以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题? maven 插件之 maven-shade-plugin,解决同包同名 class 共存问题的神器
都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑我了嘛
青石路 · 2025-07-01 · via 博客园 - 青石路

开心一刻

今天心情不好,给哥们发语音
我:哥们,晚上出来喝酒聊天吧
哥们:咋啦,心情不好?
我:嗯,刚刚在公交车上看见前女友了
哥们:然后呢?
我:给她让座时,发现她怀孕了...
哥们:所以难受了?
我:不是她怀孕让我难受,是她怀孕还坐公交车让我难受
哥们:不是,她跟着你就不用坐公交车了?不还是也要坐,有区别吗?
我默默的挂断了语音,心情更难受了

痛苦面具

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 isClearData) {
        this.taskId = taskId;
        this.isClearData = isClearData;
    }

    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));

这是哪里出了问题?

20240115000802

问题排查

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

640 (15)

当他启动 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是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方法的功能)

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的

能够查到如下重点信息

springmvc参数转换

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绑定参数

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

set反射设值

通过前端传过来的参数 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 前缀

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

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