なぜ、Spring MVCのメソッドパラメータ名に利用できる、とMyBatisのではない持っていますか?[春のMVCを学ぶお楽しみください]

各1

胡:、いくつかの問題についての教義についてはあまりチャットを語ります

序文

Spring MVCそして、MyBatis最も人気のある現在の枠組みの2として、我々は通常の開発に使用されています。あなたが一歩深く行くことを考えるならば、あなたはこの質問を持っている必要があります。

  • 使用時にはSpring MVC、あなたがいる限り、パラメータ名と、要求に対応するパラメータのキーとして、値が自動的にパッケージを完了します、注釈なしで使用する場合
  • 使用中のMyBatis場合(インタフェースモード)、パラメータ、SQL文のXMLインターフェイスメソッドを通過する際に必须(当然不是100%的必须,特殊情况此处不做考虑)用いて@Param('')SQLで、指定されたキー値のみに撮影することができ

私が初めてだったので、私は、それは間違いなく私自身の問題ではないと信じてあえてMyBatis質問はこれについて発生し、また、過去のアウトしようとしたとき@Param、私は名前を二回余分ビットを書くために私に尋ねたと思うので、あまりにも(私をコメント怠け者)。
そしてSpring MVC、それは感じたよりも人道的な治療MyBatisこれに対処するためには、単に弱いバーストです。私は最終的にこの現象を説明することができ、そしてそのベールを破るために限り、今日のために不可解な〜

問題発見

javaユーザーが知っている.java、それが通過する必要があり、ファイルはソースファイルに属しているjavacために、コンパイラ.class可能なファイルをバイトコードJVM実行します。
するには.class、小さなパートナーを少し理解バイトコードもこれを知っている必要があります:Javaメソッドのコンパイル時に、デフォルトではしている不会メソッドのパラメータ名を維持し、私たちはから実行したい場合は.class直接法で取得するバイトコードのパラメータ名です私が行うことはできません。

次のような場合、真のパラメータ未満を得ることが自分自身に名前を付けることは明らかです。

public static void main(String[] args) throws NoSuchMethodException {
    Method method = Main.class.getMethod("test1", String.class, Integer.class);
    int parameterCount = method.getParameterCount();
    Parameter[] parameters = method.getParameters();

    // 打印输出:
    System.out.println("方法参数总数:" + parameterCount);
    Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
}

印刷コンテンツ:

方法参数总数:2
class java.lang.String----arg0
class java.lang.Integer----arg1

この結果から、我々が見ることを得ることができない、真のメソッドのパラメータ名(取得は無意味であるarg0、arg1など、)、これは期待どおりで私たちの理論的な知識を持つ行になり

使用に:あなたは、いくつかの技術的な感度を持っている場合は、この時間は、あなたがこの質問持つべきSpring MVC時、Controllerこのような形を、自動的にああパッケージ化することができます方法は同じアノテーションを使用しませんが。

@GetMapping("/test")
public Object test(String name, Integer age) {
    String value = name + "---" + age;
    System.out.println(value);
    return value;
}

要求:/test?name=fsx&age=18コンソール出力:

fsx---18

結果から見ることができます。一見不可能な場合に、Spring MVCでも行うには(メソッドのパラメータ名を取得して、パッケージを完成)、それは少し奇妙ではないでしょうか?

(パラメータ名を復元するためにシーンを入手Spring MVCの)この例を見てください:

public static void main(String[] args) throws NoSuchMethodException {
    Method method = Main.class.getMethod("test1", String.class, Integer.class);
    MethodParameter nameParameter = new MethodParameter(method, 0);
    MethodParameter ageParameter = new MethodParameter(method, 1);

    // 打印输出:
    // 使用Parameter输出
    Parameter nameOriginParameter = nameParameter.getParameter();
    Parameter ageOriginParameter = ageParameter.getParameter();
    System.out.println("===================源生Parameter结果=====================");
    System.out.println(nameOriginParameter.getType() + "----" + nameOriginParameter.getName());
    System.out.println(ageOriginParameter.getType() + "----" + ageOriginParameter.getName());
    System.out.println("===================MethodParameter结果=====================");
    System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
    System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
    System.out.println("==============设置上ParameterNameDiscoverer后MethodParameter结果===============");
    ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    nameParameter.initParameterNameDiscovery(parameterNameDiscoverer);
    ageParameter.initParameterNameDiscovery(parameterNameDiscoverer);
    System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
    System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
}

出力:

===================源生Parameter结果=====================
class java.lang.String----arg0
class java.lang.Integer----arg1
===================MethodParameter结果=====================
class java.lang.String----null
class java.lang.Integer----null
==============设置上ParameterNameDiscoverer后MethodParameter结果===============
class java.lang.String----name
class java.lang.Integer----age

結果から、見ることができます。Spring MVC意味ParameterNameDiscovererパラメータ名を取得するための方法を完了した後、データパッケージを完了すること。ParameterNameDiscovererその説明、最初に見ることができます:[小]春のホーム春の標準的な処理コンポーネント大規模なコレクション(ParameterNameDiscoverer、AutowireCandidateResolver、ResolvableType ...)

Qは説明ParameterNameDiscoverer基本的な使用および提供する機能はなく、詳細な分析を。なぜそこでこの記事では、分析するSpring MVC理由のバイトコードの詳細な分析の観点から、この問題への分析メソッドのパラメータ名を修正することができます-


理解を容易にするため、最初の二つの概念を簡単にバイトコード:LocalVariableTableLineNumberTableもちろん、この記事の焦点である、2人の兄弟は、しばしば一緒に出てきたLocalVariableTableが、また、ついでにこの機会にLineNumberTable

LineNumberTable

あなたが今まで疑問にされている:オンラインプログラムが例外をスローした行番号が表示されたときに、なぜまさにそのライン上のあなたの源は何さそこについての質問ですので、これはあるJVMの実装.classファイル、およびファイルの行は、および.javaソースファイルに対応する行が上の間違いではありません、なぜに番号を並べることができた.javaファイルに対応?
これがあるLineNumberTableの役割:LineNumberTable属性存在于代码(字节码)属性中, 它建立了字节码偏移量到源代码行号之间的联系

LocalVariableTable

LocalVariableTableソースコード内のローカル変数とローカル変数の方法との間の対応を確立する属性。このプロパティは、コード(バイトコード)にも存在していると〜
それは名前から見ることができます:それはある局部变量コレクション。これは、ローカル変数と記述子について説明したソースとウェル間の対応関係を

今、私が使用javacしてjavap、あなたにこれを表示するコマンド:
.java次のようにソースコードは次のとおりです。

package com.fsx.maintest;
public class MainTest2 {
    public String testArgName(String name,Integer age){
        return null;
    }
}

説明:私はいつもの行番号に注意してください、ヘッドを書くソース〜

使用javac MainTest2.javaにコンパイル.class使用し、その後、バイトコードとjavap -verbose MainTest2.class次のようにバイトコード情報が表示され
ここに画像を挿入説明
、私は上記の質問に答える我々対応する回線番号の赤い行番号とソースコードをマークまったく同じように、図から分かります:LineNumberTableそれは、ソースコードの行番号を記録します。
ヒント:ここでは、ノー、ノー、ノーLocalVariableTable

同じソースは、私が使用しjavac -g MainTest2.javaてコンパイルした後、以下の情報に対応するバイトコード(上記の差に注意してください)を見て:
ここに画像を挿入説明
ここに1つ以上LocalVariableTable、すなわち、ローカル変数テーブル、それは我々のアプローチのパラメータにパラメータ名を記録します。記録以来、私たちはバイトコード情報を解析することで、この名前を得ることができるように-

説明:javacのデバッグオプションは、主に3つのサブオプションで構成されています:lines,source,vars
あなたが唯一のソースファイルと行番号情報を保持するには、-gでコンパイルしない場合は、持っている-g付きでコンパイルされている場合〜

そして、-parametersの違いは何ですか?

知っている-gに小さな、手のコンパイル・パラメータをJava8新しい-parameters人々はいくつかのより多くを知っています。それと-gパラメータは、それがどのような違いを生むのでしょうか?

信じているを見て、私はこの問題を説明するために前方に自分の例を置くことを好む、.java次のようにソースコードは次のとおりです。

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class MainTest2 {

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = MainTest2.class.getMethod("testArgName", String.class, Integer.class);
        System.out.println("paramCount:" + method.getParameterCount());
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getType().getName() + "-->" + parameter.getName());
        }
    }

    public String testArgName(String name, Integer age) {
        return null;
    }
}

次のものを使用してjavac、javac -g、javac -parameters、以下のような結果の実装は後にコンパイルするために
ここに画像を挿入説明
、私はペンとインク、疑いも私にメッセージを与えることができないだろう、別々にコンパイルから、図の印刷ランで見られた結果、およびそれらの間の差の結果は非常に明確になっています。

また、添付-parametersあなたは分析や比較を行うためにできるように、コンパイルされたバイトコードの情報を:
ここに画像を挿入説明


==取得方法のパラメータ名紹介する3つの方法==

Javaのコンパイラのデフォルトのパラメータ名は、例の方法を消去しますが、上記の関連する知識のバイトコードを記述しますが、それは我々がまだパラメータ名のメソッドを取得する方法を持っていることを示しています。ここでは、参照のための3つのシナリオがあります。

方法の一つ:使用-parameters

最も簡単な方法、Java8プライマル・サポート:からjava.lang.reflect.Parameter得ることができ、そのようにフォーム:

public class MainTest2 {

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = MainTest2.class.getMethod("testArgName", String.class, Integer.class);
        System.out.println("paramCount:" + method.getParameterCount());
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getType().getName() + "-->" + parameter.getName());
        }
    }

    public String testArgName(String name, Integer age) {
        return null;
    }
}

出力:

paramCount:2
java.lang.String-->name
java.lang.Integer-->age

もちろん、それは2つの最大の欠点があります。

  1. 必要がありますJava8以上(既にによるjava8非常に高い普及率なので、この良いです)
  2. 翻訳パラメータを持っている必要があります-parameters(移行は非常に友好的ではないので、これはより致命的である、コンパイラ依存パラメータので)

指定-parametersコンパイラ引数の方法を:

  1. コマンドを手動でコンパイルします。javac -parameters XXX.java
  2. コンパイルする(例えばアイデアを)IDE:
    ここに画像を挿入説明
  3. Mavenのビルド:によってプラグコンパイル移行プロジェクトの正確さを保証するために、指定をする(推奨)
<!-- 编译环境在1.8编译 且附加编译参数:-parameters-->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <compilerVersion>${java.version}</compilerVersion>
        <encoding>${project.build.sourceEncoding}</encoding>
    </configuration>
</plugin>

長所:簡単
短所:特別指定-parameters、あまり便利ではない(プログラムを指定するには、エディタをプラグインコース利用の達人で、比較的信頼性があると推奨

オプション2:使用-g+のjavapコマンドを

例を使用することができるようにjavac -gコンパイルされ、次に使用javapその後、フォーマット情報に基づいて抽出されたパラメータのうちの名前(それら自身、自分のは、それを自分で行う)、取得した情報のバイトコードを

これは、一般的にhttpプロトコルを解析せるのに等しいです、あなたがそれを行うのだろうか?したがって、このアプローチは方法ですが、それは不便を使用することは明らかであるが、

オプション3:付ASM(推奨)

いえばASM、少なくとも小さなパートナー名のためには、全く見知らぬ人はいけません。それはJavaバイトコード操作フレームは、動的にクラスまたは両方の拡張のクラスを生成するために使用することができ、それはクラス情報の挙動解析を変更することができる、新しいクラスは、ユーザーの要求に応じても生成することができます。

以下のためにASM、それはJava class全体のバイナリ構造を横断する「訪問者」モード(モードガイド)を使用して;ツリーとして記載されているユーザのみがかなりの部分をプログラムする必要があるように集中するイベント駆動型アプローチ(例えば、物品は、メソッドパラメータに焦点を当てJavaクラス・ファイル・フォーマットを知っていることなく)他の非金利、すべての詳細を

----

ASMの方法、それはまだ物事のコンパイルバイトコードに基づいており、いわゆるわらせずにレンガを作ることができないので、それはまだコンパイル時に依存しなければならないLocalVariableTable(-gパラメータ)。

あなたが尋ねるかもしれません:私は自分自身がああ-gパラメータを指定していない/ Mavenのコンパイラをコンパイルするためにアイデアを使用し、なぜあなたがたはそれを作りますか?あなたの質問は、また私の質問です私はまだもっと根本的な理由を把握することはできませんが、私は、次の2つの現象を言うことができます

  1. デフォルトのjavacコンパイラで使用されているアイデアがでバイトコードにコンパイルされますLocalVariableTable下図のようにしかし、あなたはまた、それをオフにすることができます:
    ここに画像を挿入説明
  2. Mavenは、デフォルトのjavacは、やはりとバイトコードをコンパイルし使用していますLocalVariableTable(Mavenのは、熟練した学生が指し促す。私が知っていることはできませんが、Mavenの時間は、コマンドとパラメータをコンパイルコンパイル〜)

----

エピソード:科学に作用する(プロキシ、CGLIB、Javassistの、ASM):

  1. ASMJavaバイトコード操作フレームワークのオープンソース。JVMの基本的なレベルの操作アセンブラ命令レベルクラス構造および組立説明書のユーザーは非常に厳しい、ある程度の知識を持っているJVMが必要です。
  2. Javassist:上記の効果。比較するとASM、それは簡単な操作によって特徴付けられ、そしてまた(もちろん何の迅速なASM)を高速化することはできません。それは重要である:それはJVMの指示/アセンブリ命令を理解する必要はありません-
  3. Proxyダイナミックエージェント:動的に生成された(非翻訳だけでなく、事前に)プロキシクラス:$Proxy0 extends Proxy implements MyInterface{ ... }、それは唯一のインターフェイスにすることができます(またはクラスがインタフェースを実装)プロキシをすることを決定し、単一継承をも決定することができない(抽象)プロキシクラス〜
  4. CGLIB:ですベースのASMパワフル、高性能、高品質なバイトコード生成ライブラリは。これは、ランタイムJavaクラスで拡張され、Javaインタフェースを実装することができます。

    > Spring AOPと同様にHibernateプロキシオブジェクトを作成するために使用されCGLIB

前記事が直接使用導入しているCGLIBAPI/プロキシオブジェクトを生成する操作バイトコードを、物品は単純で直接的に実証するASM例を動作させるためにフレーム。

使用のASM例

まず輸入asm依存関係:

<!-- https://mvnrepository.com/artifact/asm/asm -->
<dependency>
    <groupId>asm</groupId>
    <artifactId>asm</artifactId>
    <version>3.3.1</version>
</dependency>

説明:ASMは、バージョン7.xにアップグレードされている、およびGAVが変更されました。私はここで私は保守的だった、右〜、お馴染みのポイント3.xの午前ので、

ベースのASMツール方法getMethodParamNames(Method)の任意に取得するためのMethodパラメータ名の:

public class MainTest2 {

    // 拿到指定的Method的入参名们(返回数组,按照顺序返回)
    public static String[] getMethodParamNames(Method method) throws IOException {
        String methodName = method.getName();
        Class<?>[] methodParameterTypes = method.getParameterTypes();
        int methodParameterCount = methodParameterTypes.length;
        String className = method.getDeclaringClass().getName();
        boolean isStatic = Modifier.isStatic(method.getModifiers());
        String[] methodParametersNames = new String[methodParameterCount];


        // 使用org.objectweb.asm.ClassReader来读取到此方法
        ClassReader cr = new ClassReader(className);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);


        // 这一步是最红要的,开始visitor浏览了
        // ClassAdapter是org.objectweb.asm.ClassVisitor的子类~~~~
        cr.accept(new ClassAdapter(cw) {

            // 因为此处我们只关心对方法的浏览,因此此处只需要复写此方法即可
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                final Type[] argTypes = Type.getArgumentTypes(desc);

                // 只visitor方法名相同和参数类型都相同的方法~~~
                if (!methodName.equals(name) || !matchTypes(argTypes, methodParameterTypes)) {
                    return mv;
                }

                // 构造一个MethodVisitor返回 重写我们关心的方法visitLocalVariable~~~
                return new MethodAdapter(mv) {

                    //特别注意:如果是静态方法,第一个参数就是方法参数,非静态方法,则第一个参数是 this ,然后才是方法的参数
                    @Override
                    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
                        // 处理静态方法与否~~
                        int methodParameterIndex = isStatic ? index : index - 1;
                        if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) {
                            methodParametersNames[methodParameterIndex] = name;
                        }
                        super.visitLocalVariable(name, desc, signature, start, end, index);
                    }
                };
            }
        }, 0);
        return methodParametersNames;
    }

    /**
     * 比较参数是否一致
     */
    private static boolean matchTypes(Type[] types, Class<?>[] parameterTypes) {
        if (types.length != parameterTypes.length) {
            return false;
        }
        for (int i = 0; i < types.length; i++) {
            if (!Type.getType(parameterTypes[i]).equals(types[i])) {
                return false;
            }
        }
        return true;
    }
}

実行ケース:

public class MainTest2 {
    
    // 使用工具方法获取Method的入参名字~~~
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, IOException {
        Method method = MainTest2.class.getDeclaredMethod("testArgName", String.class, Integer.class);
        String[] methodParamNames = getMethodParamNames(method);

        // 打印输出
        System.out.println(StringUtils.arrayToCommaDelimitedString(methodParamNames));
    }

    private String testArgName(String name, Integer age) {
        return null;
    }
}

出力:

name,age

コンポジット、使用したASMの期待される効果は、私たちは本物のメソッドのパラメータ名を(ああ、コンパイル済みの任意のパラメータを指定しないでください)期待ものを手に入れます。ASMベースの方法、あなたはJava8以下のバージョンであっても、それはコンパイラのパラメータに依存しないので、通常に取得することができます~~~

==これらの基本、そしてブック実話では、最初の質問のテキストを説明します==

Spring MVC为何好使?

最初の使用に関する明確にするSpring MVCHaoshiが、それはに依存しない-parametersで、パラメータも-gそれは手段であるため、コンパイラのパラメータASM-を達成するために

spring-coreあるParameterNameDiscovererパラメータ名、下塗り層ASM分解能を得るために使用される、しかしインターフェイスメソッドパラメータ名を得ることができない、すなわち、唯一の非メソッドパラメータ名インタフェースクラス缶。
見ることができる最初の例のテキストからSpring MVCその最終依存しているDefaultParameterNameDiscovererコードのこの作品を見て、パラメータ名に入るのを助けるために:

// @since 4.0
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

    public DefaultParameterNameDiscoverer() {
        if (!GraalDetector.inImageCode()) {
            if (KotlinDetector.isKotlinReflectPresent()) {
                addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
            }
            addDiscoverer(new StandardReflectionParameterNameDiscoverer());
            addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
        }
    }
}

DefaultParameterNameDiscovererそれは、処理するために、クラスの実現に追加頼って、それは、2人の兄弟であるChain of Responsibilityパターンの現れです:
StandardReflectionParameterNameDiscoverer:依存する-parameters(Javaのバージョンがあるコンパイル必要とし、パラメータが必要)有効であることが
LocalVariableTableParameterNameDiscoverer:ベースはASM、noバージョンを達成していないとパラメータを構築します要件〜

注:Spring使用しないASM自給ので、追加のガイドパッケージ:
ここに画像を挿入説明

MyBatis为何不好使?

最初はこれを明確にする必要性を使用するには:MyBatisあるインタフェースを介して、その後達成するためにバインドするためにSQL文を使用してプロキシクラスを生成します。

強いがあるのでASM、その疑問が生じる:それはASMまた助けないMyBatis開発を簡素化するために?
おそらくあなたのせい理解していないことができるようになり、私が与えた例を見てMyBatis、それを:

public class MainTest2 {
    
    // 使用工具方法获取Method的入参名字~~~
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, IOException {
        Method method = MainTest2.class.getDeclaredMethod("testArgName", String.class, Integer.class);
        String[] methodParamNames = getMethodParamNames(method);

        // 打印输出
        System.out.println(StringUtils.arrayToCommaDelimitedString(methodParamNames));
    }
}

// 接口方法
interface MyDemoInterface{
    String testArgName(String name, Integer age);
}

出力:

null,null

見ることができるようにしても強いようASM、ウッド型のインタフェースパラメータ名を直接取得する方法があります。
これは、インターフェイスメソッドが実用的な方法ではないので、そのパラメータ名は〜実装クラスは、これほど少ないのインターフェイスメソッドのパラメータ名の意味を説明しますで、理解することができます

ヒント:インターフェースのdefaultメソッドとstaticパラメータ名の通常の方法は、に到達するためにあるかもしれ、パートナーシップにはほとんど関心があることは〜に手を試すことができます

ASMなぜ無効なインタフェースは、私はあなたが明確なバイトコードが表示されます示し根本的な理由:
ここに画像を挿入説明
手立て抽象メソッド本体が存在しないためには、ローカル変数が存在しない、自然にローカル変数テーブルが存在しない、そうしてもASM得ることができませんその変数名〜

説明:InがJava8使用-parameter直接メソッドの名前への参照を取得することができ、さらに界面のパラメータを、一対のMyBatisに有用です。もちろん、互換性を確保するために、個人的な推奨事項は素直にそれを指定するには、@Paramアノテーションを使用〜

これまでのところ、私はそれ~~~この質問をSpring MVCのは、行うことができる理由を完全に理解、小さなパートナーは私のようであることを信じる理由を持っていますが、できないMyBatisの

概要

そこも、この長いあなたの質問に悩まさ(タイトルなどの質問)深いバイトコードに紙、あなたのための質問に答えるのを期待して分析することができます。また、紹介ASMおそらくあなたは他の後続フレームワークが参考になると理解し、基本的な使い方を-

== 春、SpringBoot、MyBatisのソースコード解析およびその他の利害私にWXを追加することができため場合:fsx641385712、手動でグループに離陸することを勧め ==
== 春、SpringBoot、MyBatisのソースコード解析および他のために興味が私を追加WXことができる場合:fsx641385712、手動で離陸したグループにあなたを招待 ==

おすすめ

転載: www.cnblogs.com/fangshixiang/p/11532689.html
おすすめ