翻车了,lombok这玩意真坑

news2024/12/23 4:14:44

目录

    • 背景
    • 排查
        • Setter-Getter方法的坑
        • 解决
        • 原因
        • 解决方案
        • 原因
        • 解决方案
    • 总结

背景

青柠最近在写自己的项目,刚开始就写不下去了,心态崩了,这啥玩意啊,就是找不到问题在哪?

早前,在项目当中引入了Lombok插件,着实解放了双手,代替了一些重复的简单工作(Getter,Setter,toString等方法的编写)。

但是,在使用的过程当中,也发现了一些问题,开始的时候并没有察觉到是 Lombok 的问题,后来跟踪了对应的其他组件的源码,才发现是Lombok的问题!
在这里插入图片描述

排查

Setter-Getter方法的坑

问题发现
我们在项目当中主要使用Lombok的Setter-Getter方法的注解,也就是组合注解@Data,但是在一次使用Mybatis插入数据的过程当中,出现了一个问题,问题描述如下:

我们有个实体类:

@Data
public class NMetaVerify{
    private NMetaType nMetaType;
    private Long id;
    ....其他属性
}

当我们使用Mybatis插入数据的时候,发现,其他属性都能正常的插入,但是就是nMetaType属性在数据库一直是null。

解决

当我debug项目代码到调用Mybatis的插入SQL对应的方法的时候,我看到NMetaVerify对象的nMetaType属性还是有数据的,但是执行插入之后,数据库的nMetaType字段就是一直是null,原先我以为是我的枚举类型写法不正确,看了下别的同样具有枚举类型的字段,也是正常能插入到数据库当中的,这更让我感觉到疑惑了。

于是,我就跟踪Mybatis的源码,发现Mybatis在获取这个nMetaType属性的时候使用了反射,使用的是getxxxx方法来获取的,但是我发现nMetaType的get方法好像有点和Mybatis需要的getxxxx方法长的好像不一样,问题找到了!

原因

Lombok对于第一个字母小写,第二个字母大写的属性生成的get-set方法和Mybatis以及idea或者说是Java官方认可的get-set方法生成的不一样:

Lombok生成的Get-Set方法

@Data
public class NMetaVerify {
    private Long id;
    private NMetaType nMetaType;
    private Date createTime;
    
    public void lombokFound(){
        NMetaVerify nMetaVerify = new NMetaVerify();
        nMetaVerify.setNMetaType(NMetaType.TWO); //注意:nMetaType的set方法为setNMetaType,第一个n字母大写了,
        nMetaVerify.getNMetaType();                                  //getxxxx方法也是大写
    }
}

idea,Mybatis,Java官方默认的行为为:

public class NMetaVerify {
    private Long id;
    private NMetaType nMetaType;
    private Date createTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public NMetaType getnMetaType() {//注意:nMetaType属性的第一个字母小写
        return nMetaType;
    }

    public void setnMetaType(NMetaType nMetaType) {//注意:nMetaType属性的第一个字母小写
        this.nMetaType = nMetaType;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

Mybatis(3.4.6版本)解析get-set方法获取属性名字的源码:

package org.apache.ibatis.reflection.property;

import java.util.Locale;

import org.apache.ibatis.reflection.ReflectionException;


public final class PropertyNamer {

     private PropertyNamer() {
         // Prevent Instantiation of Static Class
       }

    public static String methodToProperty(String name) {
      if (name.startsWith("is")) {//is开头的一般是bool类型,直接从第二个(索引)开始截取(简单粗暴)
          name = name.substring(2);
      } else if (name.startsWith("get") || name.startsWith("set")) {//set-get的就从第三个(索引)开始截取
          name = name.substring(3);
      } else {
          throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
      }
           //下面这个判断很重要,可以分成两句话开始解释,解释如下
            //第一句话:name.length()==1
            //       对于属性只有一个字母的,例如private int x;
            //          对应的get-set方法是getX();setX(int x);
            //第二句话:name.length() > 1 && !Character.isUpperCase(name.charAt(1)))
            //      属性名字长度大于1,并且第二个(代码中的charAt(1),这个1是数组下标)字母是小写的
            //      如果第二个char是大写的,那就直接返回name
      if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
          name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);//让属性名第一个字母小写,然后加上后面的内容
      }

      return name;
    }

    public static boolean isProperty(String name) {
       return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
    }

    public static boolean isGetter(String name) {
       return name.startsWith("get") || name.startsWith("is");
    }

    public static boolean isSetter(String name) {
       return name.startsWith("set");
    }

}

Mybatis解析get-set方法为属性名字测试

 @Test
 public void foundPropertyNamer() {
        String isName = "isName";
        String getName = "getName";
        String getnMetaType = "getnMetaType";
        String getNMetaType = "getNMetaType";

        Stream.of(isName,getName,getnMetaType,getNMetaType)
                .forEach(methodName->System.out.println("方法名字是:"+methodName+" 属性名字:"+ PropertyNamer.methodToProperty(methodName)));
  }

输出结果如下:

方法名字是:isName 属性名字:name 
方法名字是:getName 属性名字:name 
方法名字是:getnMetaType 属性名字:nMetaType //这个以及下面的属性第二个字母都是				大写,所以直接返回name
方法名字是:getNMetaType 属性名字:NMetaType

解决方案

修改属性名字,让第二个字母小写,或者说是规定所有的属性的前两个字母必须小写。如果数据库已经设计好,并且前后端接口对接好了,不想修改,那就专门为这种特殊的属性使用idea生成get-set方法复制代码
@Accessor(chain = true)注解的问题
问题发现
在使用 easyexcel 导出的时候,发现以前的实体类导出都很正常,但是现在新加的实体类不正常了,比对了发现,新加的实体类增加了@Accessor(chain = true)注解,我们的目的主要是方便我们链式调用set方法:

new UserDto()
.setUserName("")
.setAge(10)
........
.setBirthday(new Date());

原因

easyexcel底层使用的是cglib来做反射工具包的:

com.alibaba.excel.read.listener.ModelBuildEventListener 类的第130行
BeanMap.create(resultModel).putAll(map);

最底层的是cglib的BeanMap的这个方法调用

abstract public Object put(Object bean, Object key, Object value);
但是cglib使用的是Java的rt.jar里面的一个Introspector这个类的方法:

Introspector.java 第520行

if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
   pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
   //下面这行判断,只获取返回值是void类型的setxxxx方法
 } else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
    // Simple setter
    pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
    if (throwsException(method, PropertyVetoException.class)) {
       pd.setConstrained(true);
    }
}

解决方案

去掉Accessor注解。

要么就等待easyexcel的作者替换掉底层的cglib或者是其他,反正是支持获取返回值不是void的setxxx方法就行复制代码。

总结

踩坑是避免不了的,一时踩坑一时爽,一直踩坑就完犊子了,遇到问题不要慌,记录一下,慢慢找解决方法。 如果对你有帮助,留下三联

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

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

相关文章

zabbix配置监控日志

目录 一、zabbix日志监控的功能 二、zabbix监控日志的条件 三、日志监控的监控指标 四、日志监控Item的配置案例 一、zabbix日志监控的功能 Zabbix-agent支持对日志文件的监控,可以对日志对的关键字进行监控,然后告警。日志监控支持普通的日志文件&am…

ChatGPT与软件架构(5) - 网络安全

安全是任何系统都无法回避的关键要素,本文介绍了若干利用ChatGPT增强的网络安全攻击,并提出了应对策略。原文: ChatGPT and Cyber Security Benjamin Elliott Unsplash 如果你还不熟悉ChatGPT,那么考虑到这种生成式人工智能服务的快速崛起&am…

Hive SQL 语法大全~

基于语法描述说明 CREATE DATABASE [IF NOT EXISTS] db_name [LOCATION] path; SELECT expr, ... FROM tbl ORDER BY col_name [ASC | DESC] (A | B | C) 如上语法,在语法描述中出现: [],表示可选,如上[LOCATION]表示可写、可不…

神州数码DCRS试题题目合集

2017.11.3 DCRS 在公司总部的DCRS上配置,配置设备enable密码,并且在登录设备时必须正确输入enable密码才能进入交换机的配置模式。 在公司总部的DCRS上配置,在交换设备上开启SSH管理功能,用户名和密码都是DCN,并关闭…

flutter系列之:做一个会飞的菜单

文章目录 简介定义一个菜单项目让menu动起来添加菜单内部的动画总结 简介 flutter中自带了drawer组件,可以实现通用的菜单功能,那么有没有一种可能,我们可以通过自定义动画来实现一个别样的菜单呢? 答案是肯定的,一起…

(栈和队列) 150. 逆波兰表达式求值 ——【Leetcode每日一题】

❓150. 逆波兰表达式求值 难度:中等 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意: 有效的算符为 ‘’、‘-’、‘*’ 和 ‘/’ 。每个操作数&#…

vue制作自己的组件库(仿ElementUI)

1.首先自己创建个新的vue项目,之后更改下目录形式,将src文件更改为examples,这里是专门放组件展示的md文件,packages文件里是放自己写的组件代码 2.然后是开始配置vue.config.js文件 ,其中md-loader是读取md文件的相关…

Linux下安装docker教程

目录 一、安装CentOS系统 二、安装Docker 1.卸载之前安装过的旧版本: 2.安装docker 3.启动docker 4.配置docker镜像仓库 三、Docker的使用 1.docker容器常用指令: 2.运行案例:docker拉取并运行nignx 3.查看容器日志 一、安装CentOS系…

Prompt工程-高级提示

高阶Prompting 到这一步,应该很明显,改进提示有助于在不同任务上获得更好的结果。这就是Prompt工程背后的整个理念。 虽然之前的例子很有趣,但在我们深入了解更高级的概念之前,让我们先正式地介绍一些概念。 文章目录 高阶Promp…

使用Flexible实现移动端页面的终端适配,及快捷设置开发工具px转换rem

1、使用Flexible实现移动端页面的终端适配 官网GitHub地址API介绍&#xff1a; ​​​​​​使用Flexible实现手淘H5页面的终端适配 Issue #17 amfe/article GitHub 阿里 cdn 引入地址&#xff1a; <script src"http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??…

JavaWeb公司员工管理系统

1.需求分析 系统角色分别为&#xff1a;最高权限管理员、人力主管、部门主管、员工。总体业务流程图如下图所示。 用例图如下所示。 2.系统设计 系统功能总体设计如下图。 数据库设计如下图所示。 3.系统实现效果 登录功能实现效果如下图所示。 考勤管理模块实现效…

字节同事问我:我的Postman为什么连不了数据库?

postman本身没有数据库连接功能&#xff0c;所以用到了node.js中的xmysql实现Rest API的生成&#xff0c;利用postman进行请求&#xff0c;获取需要的数据&#xff0c;来做数据准备或断言 1 安装 安装node.js&#xff1a;要求版本大于等于7.6 首先保证你的环境上有node.js&…

云计算基础

一、分布式计算 分布式计算将应用分解成许多更小的部分&#xff0c;分配到多台计算机进行处理&#xff0c;这样可以节省整体计算时间&#xff0c;大大提高计算效率。 云计算是分布式计算技术的一种&#xff0c;也是分布式计算这种科学概念的商业实现。分布式计算的优点就是发…

chatgpt赋能python:Python如何分割列表

Python如何分割列表 介绍 在Python编程中&#xff0c;列表是一种非常常见的数据类型。有时候我们需要将一个大的列表分割成几个小的列表&#xff0c;以便更好地处理数据。Python提供了多种方法来实现这个目的。在本文中&#xff0c;我们将介绍Python中如何分割列表的几种方法…

深度学习技巧应用18-OFD格式文件与人工智能结合的技巧应用,实现OFD转文本、OFD自动分类与内容提取

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用18-OFD格式文件与人工智能结合的技巧应用,实现OFD转文本、OFD自动分类与内容提取。OFD格式文件被称为“中国版PDF”。它与PDF格式类似,是一种可读、可打印、可编辑、可存档的电子文档格式,但OFD格式相对于PDF格式更加…

pandas

pandas 数据结构生成数据创建 Series创建 DataFrame 数据处理相关方法功能介绍 数据结构 描述举例Series带有标签的一维数组DataFrame带有标签的二维数组 生成数据 创建 Series # s pd.Series(data, indexindex) s1 pandas.Series([张三,李四,王五,郑六]) s2 pandas.Seri…

电动力学专题:辐射的频谱分析

辐射的频谱分析 韧致辐射就是带电粒子入射到物质靶上时&#xff0c;它和靶内原子中的电子和原子核碰撞&#xff0c;在碰撞过程减速而产生的辐射。X射线的连续谱部分就体现了韧致辐射的性质。在这个过程中的带电粒子速度远小于光速 低速运动带电粒子在碰撞过程中的辐射频谱 频…

设备指纹系列--前端篇

基础篇请看&#xff1a;设备指纹系列–基础篇 我们接着前文继续写关于设备指纹前端接入方面的内容。话不多说&#xff0c;直接步入正题。 我们会在下文展示5种前端接入的方式&#xff0c;包括web接入、安卓接入、ios接入、微信小程序接入以及支付宝小程序接入。 Web接入 第…

SpringBoot整合Mybatis-Plus多数据源

一、前言 随着业务的不断扩展和复杂度的增加&#xff0c;我们在开发过程中往往需要访问多个数据库。 比如&#xff1a; 我们可能需要同时访问主数据库和从数据库&#xff0c;或者访问多个独立的数据库来处理不同的业务逻辑。这时候&#xff0c;我们就需要使用多数据源来实现对…

【python脚本】编写

这里写自定义目录标题 欢迎使用python来编写脚本环境搭建 欢迎使用python来编写脚本 测试方向&#xff0c;测试报告&#xff0c;单元测试 环境搭建 python环境搭建 下载地址 https://www.python.org/ 文档 https://docs.python.org/3/ pycharm的环境 使用chatgpt来实现代码功…