23

为什么有些公司不让用Lombok?

 4 years ago
source link: http://developer.51cto.com/art/202010/629471.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

V7rmimR.jpg!mobile

图片来自 Pexels

但是,在使用的过程当中,也发现了一些坑,开始的时候并没有察觉到是 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; 
 
/** 
 * @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 方法就行。

作者:liuxuzxx

编辑:陶家龙

出处:https://juejin.im/post/6881432532332576781

ma6Brii.gif!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK