>  기사  >  Java  >  롬복의 함정에 대해 알아보세요

롬복의 함정에 대해 알아보세요

coldplay.xixi
coldplay.xixi앞으로
2020-10-09 17:00:022648검색

기본 Java Tutorial 칼럼에서는 사용하기 쉬운 Lombok의 함정을 소개합니다.

롬복의 함정에 대해 알아보세요

서문

Lombok 플러그인은 작년에 프로젝트에 도입되어 실제로 손을 자유롭게 하고 일부 반복적인 간단한 작업(Getter, Setter, toString 및 기타 방법 작성)을 대체했습니다. 사용하면서 몇 가지 함정을 발견했는데, 처음에는 롬복 문제인지 몰랐는데, 나중에 해당하는 다른 컴포넌트의 소스 코드를 추적해 보니 롬복 문제였다는 걸 알게 됐어요!

Setter-Getter 방식의 함정

문제 발견

저희는 프로젝트에서 Lombok의 Setter-Getter 방식의 Annotation인 @Data를 결합한 Annotation을 주로 사용하는데, Mybatis를 이용하여 데이터를 삽입하는 과정에서 문제가 발생했습니다.

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

Mybatis를 사용하여 데이터를 삽입할 때 다른 속성은 정상적으로 삽입할 수 있지만 데이터베이스에서는 nMetaType 속성이 항상 null인 것을 발견했습니다.

Solution

Mybatis에서 SQL 삽입에 해당하는 메서드를 호출하기 위해 프로젝트 코드를 디버깅하면 NMetaVerify 개체의 nMetaType 속성에 여전히 데이터가 있지만 삽입을 실행한 후에는 데이터베이스의 nMetaType 필드가 항상 null 원래는 내 열거 유형이 잘못 작성되었다고 생각했는데, 열거 유형이 있는 다른 필드도 데이터베이스에 정상적으로 삽입될 수 있어서 더욱 혼란스러웠습니다. 그래서 Mybatis의 소스 코드를 추적해 보니 Mybatis가 리플렉션을 사용하여 nMetaType 속성을 가져오고, 이를 얻기 위해 getxxxx 메서드를 사용하는 것을 발견했습니다. 그런데 nMetaType의 get 메서드가 필요한 getxxxx 메서드와 약간 다른 것 같았습니다. 마이바티스(Mybatis)로. 문제가 발견되었습니다!

이유

첫 글자가 소문자이고 두 번째 글자가 대문자인 속성에 대해 Lombok에서 생성한 get-set 메서드는 Mybatis 및 idea 또는 공식적으로 인정된 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에서 생성한 get-set 메서드와 다릅니다. (버전 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는 속성 이름 test

    @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复制代码

Solution

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

@Accessor(chain = true에 대한 get-set 메소드를 구문 분석합니다. ) Annotation 문제

문제 발견

easyexcel(github.com/alibaba/eas…)을 사용하여 내보낼 때 이전 엔터티 클래스는 정상적으로 내보내졌으나 새로 추가된 엔터티 클래스는 정상이 아닌 것을 확인했습니다. 새로 추가된 @Accessor(chain = true) 주석이 엔터티 클래스에 추가된 것으로 나타났습니다. 우리의 목적은 주로 체인에서 set 메서드를 호출하는 것을 용이하게 하는 것입니다:

new UserDto()
.setUserName("")
.setAge(10)
........
.setBirthday(new Date());复制代码

Reason

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);
    }
}复制代码

Solution

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

관련 무료 학습 권장사항: java basic tutorial

위 내용은 롬복의 함정에 대해 알아보세요의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제