ロンボク実装 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システムでコマンドを実行する方法

推薦する

CentOS6.5にpython3.7をインストールする詳細な手順

1. Python 3をダウンロードする https://www.python.org/ftp/py...

PrometheusはGrafanaディスプレイを使用してMySQLを監視します

目次Prometheusはエクスポーターを介してMySQLを監視し、Grafanaチャートで表示しま...

dig/nslookup コマンドを使用して DNS 解決手順を表示する方法

dig - DNS ルックアップ ユーティリティドメイン名のアクセス障害が発生した場合、ドメイン名の...

MySQLフィルタリングレプリケーションのアイデアの詳細な説明

目次mysql フィルター レプリケーションメインデータベースに実装ライブラリから実装いくつかの質問...

MySQL8インストーラーバージョングラフィックチュートリアル

インストール必要な書類は下部に記載されていますステップ1 mysql-installer-web-c...

Vue プロジェクトで Baidu Map API を使用する方法

目次1. Baidu Map Open Platformにアカウントを登録してログインする2. 必要...

Linux 向けの強化されたスクリーンショットと共有ツール: ScreenCloud

ScreenCloud は、必要だとは思わなかった素晴らしい小さなアプリです。デスクトップ Lin...

JavaScript キャンバスで 9 マスのグリッドカットの効果を実現

この記事では、9グリッドカット効果を実現するためのキャンバスの具体的なコードを紹介します。具体的な内...

MySQL 5.5.27 インストール グラフィック チュートリアル

1. MYSQLのインストール1. ダウンロードしたMySQLインストールファイルmysql-5.5...

Reactコンポーネント通信の詳細な説明

目次コンポーネント通信の概要コンテンツ3つの方法まとめコンポーネントコミュニケーション - 父から息...

JavaScriptカスタムオブジェクトメソッドの概要

目次1. オブジェクトを使用してオブジェクトを作成する2. コンストラクタを使用してオブジェクトを作...

画像にマウスを置いたときにズームイン/ズームアウトするには JS を使用します

マウスが画像上にあるときにズームインおよびズームアウトするには、JS を使用します。具体的なコードは...

Dockerコマンドの自動補完の実装

序文この友人がどれくらいDockerを使っていなかったのかは分かりませんが、突然Dockerコマンド...

CentOS 7 で MySQL 接続数が 214 に制限される問題の解決方法

問題を見つける最近、プロジェクトで問題が発生しました。接続が多すぎるため、「接続が多すぎます」という...

Docker+daocloudはフロントエンドプロジェクトの自動構築とデプロイを実現します

自動プロジェクト展開は大企業やユニコーン企業でよく使用され、手動でプロジェクトを展開するよりも効率的...