作者:liuxuzxx

来源:juejin.im/post/6881432532332576781

下面开始我们的正文。去年在某项目当中引入了 Lombok 插件,着实解放了双手,代替了一些重复的简单工作(Getter,Setter,toString 等方法的编写)。

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

# Setter-Getter 方法的坑

问题发现

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

问题描述如下:

我们有个实体类:@Datapublic 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方法@Datapublic 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;/** * @author Clinton Begin */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(github.com/alibaba/eas…)导出的时候,发现以前的实体类导出都很正常,但是现在新加的实体类不正常了。

比对了发现,新加的实体类增加了 @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 方法就行。

推荐阅读

完美,竟然用一个脚本就把系统升级到https了,且永久免费!

Redisson 是如何实现分布式锁的?

一整套Java线上故障排查技巧!

Java中的BigDecimal,你真的会用吗?

lombok链式调用_使用Lombok翻车了相关推荐

  1. lombok链式调用_记一次使用 Lombok 翻车造成的事故!

    本文同名博客老炮说Java:https://www.laopaojava.com/,每天更新Spring/SpringMvc/SpringBoot/实战项目等文章资料 Sentinel+Nacos 是 ...

  2. lombok链式调用_翻车!记一次使用 Lombok 造成的事故!

    ★★★建议星标我们★★★ 公众号改版后文章乱序推荐,希望你可以点击上方"Java进阶架构师",点击右上角,将我们设为★"星标"!这样才不会错过每日进阶架构文章呀 ...

  3. promise链式调用_这一次,彻底弄懂 Promise

    Promise 必须为以下三种状态之一:等待态(Pending).执行态(Fulfilled)和拒绝态(Rejected).一旦Promise 被 resolve 或 reject,不能再迁移至其他任 ...

  4. lombok @data 忽略属性_使用lombok编写优雅的Bean对象

    题外话:欢迎将公众号设置为星标,技术文章第一时间看到.我们将一如既往精选技术好文,提供有价值的阅读.如有读者想要投稿,可以在公众号任意文章下留言,技术博主奖励丰厚. 推荐阅读 1. SpringBoo ...

  5. Lombok链式调用,子类对象set父类属性,返回父类对象

    前言 lombok相信大家都用过,没用过肯定也不会点进来,一直用着都很舒服.今但是天在码代码时,碰到了一个问题,卡了半天.问题是这样的:Lombok链式调用父类中的set属性时,返回的居然是一个父类对 ...

  6. lombok依赖_使用Lombok 前你需要知道这些

    转自:树下魅狐 链接:https://ramostear.com/blog/2020/04/28/uk1860p8.html 不得不承认,Lombok 是一个很不错的 Java 库,它可以让你在少写代 ...

  7. lombok链式编程

    lombok链式编程 lombok能通过注解的方式,在编译时自动为属性生成构造器,getter/setter.equals.hashcode.toString方法. 1.添加依赖 <depend ...

  8. eclipse集成lombok了但是无法使用_编码神奇Lombok!搭配IDEA更爽哦!

    作者:沉默王二 微信公众号:沉默王二 上一篇:面试官 5 连问:一个 TCP 连接可以发多少个 HTTP 请求? 01.Lombok 的自我介绍 Lombok 在官网是这样作自我介绍的: Projec ...

  9. import lombok 报错_使用lombok

    Lombok是一种JavaArchive(JAR)文件,可用来消除Java代码的冗长.通过在开发环境中实现Lombok,开发人 员可以节省构建诸如hashCode()和equals()这样的方法以及以 ...

最新文章

  1. 几种Windows进程通信
  2. 为什么MySQL数据库要用B+树存储索引?
  3. 17.C#类型判断和重载决策(九章9.4)
  4. Maven下Flex国际化配置
  5. 信息网络传播权保护条例(2006)
  6. React开发(161):onref绑定
  7. 商务搜索引擎_网络营销外包——网络营销外包公司如何做好电子商务网站优化?...
  8. apple pay php 文档,Apple Pay 终于可以支付 iTunes、App Store 中的内容了
  9. CODE[VS] 1548 贝贝的车牌问题
  10. module 'tensorflow.python.keras.backend' has no attribute 'get_graph'
  11. 服务器lsass系统错误,Win7系统提示Lsass.exe系统错误的原因及解决
  12. 在线文档编辑插件——KindEditor
  13. “双料王”傍身,极智嘉Geek+成功开辟行业新蓝海
  14. 【耀扬表情包语音包】
  15. html如何查看字体样式,css2.0文档查阅及字体样式
  16. 如何实现微信和淘宝的扫码登录 ?
  17. 你说你很爱这首诗,我说我也有首很爱的诗
  18. 使用python批量下载需要的分子的SDF文件
  19. python画建筑物_函数绘制带有门窗的建筑
  20. nodejs优雅的使用es6语法

热门文章

  1. ElasticSearch查询 第四篇:匹配查询(Match)
  2. Tcpdump(linux)下载、安装、使用说明
  3. Windows下给SourceTree配置外部比较工具BeyondCompare
  4. openvswitch2.8.1 centos7.4 源码编译安装
  5. java读取配置文件properties
  6. 【SpringMVC】SpringMVC 对 Date 类型转换
  7. 看动画学算法之:排序-冒泡排序
  8. JUC锁-CyclicBarrier(七)
  9. Effective Java之努力使失败保持原子性(六十四)
  10. Effective Java之慎用重载(四十一)