Яма Ломбока

Java

Преамбула

В прошлом году в проект был введен плагин Lombok, который реально развязал мне руки и заменил некоторые повторяющиеся и простые задачи (написание методов типа Getter, Setter, toString и т.д.), однако в процессе его использования, Я также нашел некоторые ямки.Сначала я не понимал, что это проблема Ломбока.Потом, после отслеживания исходного кода других компонентов, я узнал, что это проблема Ломбока!

Яма метода Setter-Getter

поиск проблемы

В основном мы используем в проекте аннотацию Ломбокского метода Setter-Getter, то есть комбинированную аннотацию @Data, но в процессе вставки данных с помощью Mybatis возникла проблема, описанная ниже:

我们有个实体类:
@Data
public class NMetaVerify{
    private NMetaType nMetaType;
    private Long id;
    ....其他属性
}

Когда мы используем Mybatis для вставки данных, мы обнаруживаем, что другие атрибуты могут быть вставлены обычным образом, но атрибут nMetaType в базе данных всегда равен нулю.

решать

Когда я отлаживаю код проекта и вызываю метод, соответствующий вставке SQL Mybatis, я вижу, что свойство nMetaType объекта NMetaVerify все еще имеет данные, но после выполнения вставки поле nMetaType базы данных всегда равно нулю, что я мысль была моя.Тип перечисления написан неправильно.Я видел, что другие поля с таким же типом перечисления могут быть вставлены в базу данных нормально, что меня еще больше озадачило. Итак, я отследил исходный код Mybatis и обнаружил, что Mybatis использовал отражение для получения этого атрибута nMetaType и использовал метод getxxxx для его получения, но я обнаружил, что метод get nMetaType немного отличается от требуемого метода getxxxx. от Mybatis. То же самое. Проблема найдена!

причина

Метод get-set, сгенерированный Lombok для свойств, у которых первая буква строчная, а вторая буква прописная, отличается от сгенерированного Mybatis и idea или метода get-set, официально признанного Java:

#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

решение

1.修改属性名字,让第二个字母小写,或者说是规定所有的属性的前两个字母必须小写
2.如果数据库已经设计好,并且前后端接口对接好了,不想修改,那就专门为这种特殊的属性使用idea生成get-set方法

Проблемы с аннотациями @Accessor(chain = true)

поиск проблемы

С помощью простого экселя(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 использует метод класса Introspector в rt.jar Java:

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

решение

1.去掉Accessor注解
2.要么就等待easyexcel的作者替换掉底层的cglib或者是其他,反正是支持获取返回值不是void的setxxx方法就行