ロンボク実装 JSR-269

ロンボク実装 JSR-269

序文

導入

Lombok は、Google Guava と同様に便利なツールであり、強くお勧めします。すべての Java エンジニアが使用する必要があります。 Lombok は、特に Plain Old Java Object (POJO) の Java の冗長性を開発者が排除するのに役立つ Java™ ユーティリティです。これは注釈を通じて行われます。開発環境に Lombok を実装することで、開発者は hashCode() や equals() などのメソッドの構築にかかる時間や、さまざまなアクセサーやミューテーターのカタログ化にかかる時間を大幅に節約できます。

Lombok はどのように実装されていますか?
LombokのGetterとSetterアノテーションを使用して新しいテストクラスを作成し、IDEAでコンパイルします。

lombok.Getter をインポートします。
lombok.Setter をインポートします。

@ゲッター
@セッター
パブリッククラスUserInfo{
    プライベート文字列ユーザーID;

    プライベート文字列userName;
}

コンパイル後に生成されたUserInfo.classファイルを開きます。

get メソッドと set メソッドが生成されていることがわかり、そこから Lombok がコンパイル時にコードを強化していることが推測できます。では、コンパイル時の強化はどのように実現されるのでしょうか?

コンパイルフェーズ

JSR-269 提案は JDK6 で提案され、可決されました。この提案では、「プラグイン注釈プロセッサ」と呼ばれる一連の標準 API が可決されました。この API は、コンパイル時にコード内の特定の注釈を事前に処理できるため、コンパイラの動作プロセスに影響を与えます。
一部の基礎となる実装では、その実装は仮想マシンのような C++ を使用して実装されていると一般に考えられていますが、これは Java プログラマーにとって特に使いやすいものではありません。しかし、Javac コンパイラは Java で実装されており、使いやすいです。
Javacのコンパイルプロセスは、大まかにいくつかのステップに分かれています。

  1. 準備: プラグ可能な注釈プロセッサの初期化
  2. 構文解析と記号表の記入プロセス 語彙と文法の解析 抽象構文木(AST)の構築
  3. プラグ可能な注釈プロセッサの注釈処理
  4. 分析とバイトコード生成プロセス
  5. 構文ツリーが変更されると、シンボル テーブルが再度解析され、入力されます。構文ツリーが変更されない場合、コンパイラはソース コードの文字ストリームを操作しなくなり、抽象構文ツリーに基づいて操作するようになります。

まとめると、Lombok の効果を実現したい場合は、JSR-269 に準拠し、コンパイル時に AST を操作するだけで済みます。もちろん、この方法で実装できるのは Lombok だけではなく、たとえば FindBug、MapStruct などもこの方法で実装されています。

成し遂げる

ジャンクツリー

JCTree は AST 要素の基本クラスです。効果を実現するには、JCTree ノードを追加するだけで済みます。
JCTreeは、部分的に実装する抽象クラスです。

クラス名から、どのノードが使用されているかを推測できます。一般的な説明をいくつか示します。
JCStatementは構文ツリーノードを宣言する
JCBlock 構文ブロック
JCReturn: return 文の構文ツリーノード
JCClassDecl: クラス定義構文ツリーノード
JCVariableDecl: フィールド/変数定義構文ツリーノード
JCMethodDecl: メソッド定義構文ツリーノード
JCModifiers: フラグ構文ツリーノードへのアクセス
JCExpression: 式構文ツリーノード、共通サブクラスは次のとおりです
JCAssign: 代入文構文ツリーノード
JCIdent: 識別子構文ツリーノードは、例えば次のようになります。

ツリーメーカー

主に構文木ノードを生成するために使用される

コード

まず、アクションのスコープと期間を示すためにクラスに注釈を付ける必要があります。2 つのクラスはそれぞれ Lombok の Getter と Setter に対応します。

@Target({ElementType.TYPE}) //クラスに追加されたアノテーション @Retention(RetentionPolicy.SOURCE) //コンパイル期間に影響します public @interface Getter {
}
@Target({ElementType.TYPE}) //クラスに追加されたアノテーション @Retention(RetentionPolicy.SOURCE) //コンパイル期間に影響します public @interface Setter {
}

新しい抽象注釈プロセッサを作成するには、AbstractProcessor を継承する必要があります。ここでは、テンプレート メソッド モードが使用されます。

パブリック抽象クラス MyAbstractProcessor は AbstractProcessor を拡張します {
    //構文ツリー protected JavacTrees trees;

    //構文ツリーノードを構築 protected TreeMaker treeMaker;

    //識別子 protected Names names のオブジェクトを作成します。

    @オーバーライド
    パブリック同期されたvoid init(処理環境 processingEnv) {
        super.init(処理環境);
        this.trees = JavacTrees.instance(processingEnv);
        コンテキスト context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(コンテキスト);
        this.names = Names.instance(コンテキスト);
    }

    @オーバーライド
    パブリックブールプロセス(Set<? extends TypeElement> アノテーション、RoundEnvironment roundEnv) {
        //注釈付きクラスを取得します Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(getAnnotation());
        //構文ツリーを取得します。set.stream().map(element -> trees.getTree(element)).forEach(jcTree -> jcTree.accept(new TreeTranslator() {
            //クラス定義を取得する@Override
            パブリック void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                リスト<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                // (JCTree ツリー: jcClassDecl.defs) のすべてのメンバー変数を取得します {
                    tree.getKind() が Tree.Kind.VARIABLE と等しい場合
                        JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) ツリー;
                        jcVariableDeclList に jcVariableDeclList を追加します。
                    }
                }

                jcVariableDeclList.forEach(jcVariableDecl -> {
                    jcClassDecl.defs = jcClassDecl.defs.prepend(makeMethodDecl(jcVariableDecl));
                });
                super.visitClassDef(jcClassDecl);
            }
        }));
        true を返します。
    }

    /**
     * 作成メソッド * @param jcVariableDecl
     * @戻る
     */
    パブリック抽象 JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl);

    /**
     * どのような注釈を取得するか * @return
     */
    パブリック抽象クラス<? extends Annotation> getAnnotation();
}

Setterアノテーション継承MyAbstractProcessorの処理に使用される

@SupportedAnnotationTypes("com.ingxx.processor.Setter") //アノテーションプロセッサはどのアノテーションに対して動作します。getSupportedAnnotationTypesを書き換えることもできます。
@SupportedSourceVersion(SourceVersion.RELEASE_8) //任意のバージョンを処理でき、getSupportedSourceVersionをオーバーライドすることもできます
パブリッククラス SetterProcessor は MyAbstractProcessor を拡張します {

    @オーバーライド
    パブリックJCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> ステートメント = 新しい ListBuffer<>();
        //関数本体を生成 this.name = name;
        ステートメントを追加します(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),treeMaker.Ident(jcVariableDecl.getName()))));
        JCTree.JCBlock 本体 = treeMaker.Block(0, statements.toList());
        //メソッドを生成する return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC), //アクセス フラグ getNewMethodName(jcVariableDecl.getName()), //名前 treeMaker.TypeIdent(TypeTag.VOID), //戻り値の型 List.nil(), //汎用パラメータ リスト List.of(getParameters(jcVariableDecl)), //パラメータ リスト List.nil(), //例外リスト body, //メソッド本体 null //デフォルト メソッド (インターフェイスのデフォルトである可能性があります)
        );
    }

    @オーバーライド
    パブリッククラス<? extends Annotation> getAnnotation() {
        Setter.class を返します。
    }

    プライベートName getNewMethodName(Name name) {
        文字列フィールド名 = name.toString();
        names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length())); を返します。
    }

    プライベートJCTree.JCVariableDecl getParameters(JCTree.JCVariableDecl prototypeJCVariable) {
        treeMaker.VarDef( を返す
                treeMaker.Modifiers(Flags.PARAMETER), // アクセス フラグ prototypeJCVariable.name, // 名前 prototypeJCVariable.vartype, // 型 null // 初期化ステートメント);
    }
}

MyAbstractProcessor から継承された Getter アノテーションを処理するために使用されます

@SupportedAnnotationTypes("com.ingxx.processor.Getter") //アノテーションプロセッサはどのアノテーションに対して動作します。getSupportedAnnotationTypesを書き換えることもできます。
@SupportedSourceVersion(SourceVersion.RELEASE_8) //任意のバージョンを処理でき、getSupportedSourceVersionをオーバーライドすることもできます
パブリッククラス GetterProcessor は MyAbstractProcessor を拡張します {

    @オーバーライド
    パブリックJCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> ステートメント = 新しい ListBuffer<>();
        //関数本体を生成します。 return this.Field name statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
        JCTree.JCBlock 本体 = treeMaker.Block(0, statements.toList());
        //メソッドを生成します。 return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
    }

    @オーバーライド
    パブリッククラス<? extends Annotation> getAnnotation() {
        Getter.class を返します。
    }

    プライベートName getNewMethodName(Name name) {
        文字列フィールド名 = name.toString();
        names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length())); を返します。
    }
}

既存のクラスをコンパイルする

javac -cp $JAVA_HOME/lib/tools.jar *.java -d 。

新しいテスト クラスを作成します。IDEA では、get set メソッドが見つからないためエラーが報告されます。無視してかまいません。

@ゲッター
@セッター
パブリッククラスUserInfo{
    プライベート文字列ユーザーID;

    プライベート文字列userName;

    パブリック静的voidメイン(String[] args) {
        ユーザー情報 userInfo = 新しいユーザー情報();
        ユーザー情報.setUserId("001");
        userInfo.setUserName("Dewu");
        System.out.println("id = "+userInfo.getUserId()+" name = "+userInfo.getUserName());
    }
}

次にコンパイルする

//複数のプロセッサはコンマで区切られます javac -processor com.ingxx.processor.GetterProcessor,com.ingxx.processor.SetterProcessor UserInfo.java -d .

コンパイルされたファイルを確認し、getメソッドとsetメソッドが生成されていることを確認します。

以上がLombokからJSR-269までの詳細です。JSリフレクションメカニズムの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • IDEA2020.2 プラグイン lombok のエラー問題を解決する (テスト済みで効果的)
  • ライブラリ ソースは、IDEA の Lombok プラグインによって生成されたバイトコード エラーの問題と解決策と一致しません (プロフェッショナル テストが利用可能)
  • IDEAにlombokプラグインをインストールし、注釈処理を有効にする設定を行った後も、コンパイルでエラーが報告される解決策
  • IDEA で lombok をインストールし、get と set が見つからない問題を解決する方法
  • IDEA2020.1がLombokと互換性がない問題を解決
  • Idea 2019.2 で lombok プラグインのインストールに失敗する問題の解決策の詳細な説明

<<:  MySQLインスタンスクラッシュ事例の詳細な分析

>>:  SSH経由でリモートLinuxシステムでコマンドを実行する方法

推薦する

MySQL 8.0.26 のインストールと簡易チュートリアル (インターネット上で最も完全)

目次1. MySQLをダウンロードする1.1 ダウンロード1.2 インストール1. MySQLをダウ...

Dockerfileを使用して独自のイメージを作成する方法

1. 空のディレクトリを作成する $ cd /home/xm6f/dev $ mkdir myapp...

node.jsミドルウェアの種類についての簡単な説明

目次概要1. アプリケーションレベルのミドルウェア2. 組み込みミドルウェア3. サードパーティミド...

Linux ソースコードからのソケット (TCP) バインドの詳細な説明

目次1. 最も単純なサーバー側の例2. バインドシステムコール2.1、inet_bind 2.2、i...

HTMLの基礎を徹底解説(第1部)

1. WEBを理解するWeb ページは主にテキスト、画像、ハイパーリンクなどの要素で構成されていま...

HTML ドラッグ アンド ドロップ機能の実装コード

Vueベースこの機能の核となるアイデアは、JavaScript コードを通じてページ上のノードの左余...

Nginx 最適化サービスで Web ページ圧縮を実装する方法

リソースを節約するためにWebページの圧縮を設定する1.まず、設定を変更しましょう vim /usr...

MySQL の高度な機能 - データ テーブル パーティショニングの概念とメカニズムの詳細な説明

目次パーティション分割メカニズムSELECTクエリINSERT操作DELETE操作更新操作パーティシ...

不規則な絵の滝の流れ原理の分析と応用

プロジェクトで発生した不規則な絵画壁のレイアウト問題は、次のように分析されます。 1.img dis...

CentOS VPS に SSH 経由で MySQL をインストールする方法

yum install mysql-serverと入力します。続行するにはYを押してくださいインスト...

ローカル写真をアップロードする前にプレビューコード例を実装するための HTML5 と jQuery

HTML5 と jQuery はアップロード前にローカル画像のプレビューを実装しており、その効果は...

HTML テーブル マークアップ チュートリアル (30): セルの暗い境界線の色属性 BORDERCOLORDARK

セルでは、暗い境界線の色を個別に定義できます。基本的な構文<TD ボーダーコロダーク=colo...

thead、tfoot、tbodyを使用して表を作成します

これらの 3 つのタグを間違った方法で使用して、タイトルを表に沿わせたり、tbody の高さを固定し...

MySQL での order by の使用に関する詳細

目次1. はじめに2. 本文2.1 単一列のソート2.2 複数の列を並べ替える2.3 ソート方法2....