ロンボク島の落とし穴を知る

coldplay.xixi
リリース: 2020-10-09 17:00:02
転載
2690 人が閲覧しました

java Basic Tutorial コラムでは、簡単に使える Lombok の落とし穴を紹介します。

ロンボク島の落とし穴を知る

まえがき

昨年このプロジェクトに Lombok プラグインが導入されました。これにより、手が本当に解放され、いくつかの反復的な単純なタスク (Getter、 Setter, toString などのメソッド記述) を使用していましたが、使用する過程でいくつかの落とし穴も発見されました。最初は Lombok 問題であることに気づきませんでした。後に、対応する他のコンポーネントのソース コードを追跡し、それはロンボク島の問題であることがわかりました。

Setter-Getter メソッドの落とし穴

問題発見

プロジェクトでは主に Lombok の Setter-Getter メソッドのアノテーションを使用します。これは、結合アノテーション @Data です。 , しかし、プロジェクトでは Mybatis を使用してデータを挿入するプロセス中に問題が発生しました。問題は次のように説明されています:

我们有个实体类:
@Data
public class NMetaVerify{
    private NMetaType nMetaType;
    private Long id;
    ....其他属性
}复制代码
ログイン後にコピー

Mybatis を使用してデータを挿入すると、他の属性は正常に挿入できることがわかりました。しかし、nMetaType 属性はデータベース内にあり、常に null でした。

解決策

Mybatis で SQL の挿入に対応するメソッドを呼び出すプロジェクト コードをデバッグすると、NMetaVerify オブジェクトの nMetaType 属性にはまだデータがあることがわかりますが、挿入を実行した後、データベース nMetaType フィールドは常に null です。最初は列挙型が間違って記述されていると思いました。同様に列挙型を持つ他のフィールドを確認した後、それらはデータベースに正常に挿入できるため、さらに混乱します。 .そこでMybatisのソースコードを辿ってみたところ、Mybatisではリフレクションを利用してnMetaType属性を取得しており、getxxxxメソッドを使用して取得していることが分かりましたが、nMetaTypeのgetメソッドが必要なgetxxxxメソッドとは少し違うようであることが分かりました。 Mybatis著、同じです。問題が見つかりました!

理由

最初の文字が小文字で 2 番目の文字が大文字である属性に対して Lombok によって生成された get-set メソッドは 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) アノテーションの問題

問題発見

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 A メソッドを使用します。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);
    }
}复制代码
ログイン後にコピー

解決策

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

関連する無料学習の推奨事項: Java の基本チュートリアル ##

以上がロンボク島の落とし穴を知るの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート