各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
理由のバイトコードの詳細な分析の観点から、この問題への分析メソッドのパラメータ名を修正することができます-
理解を容易にするため、最初の二つの概念を簡単にバイトコード:LocalVariableTable
とLineNumberTable
。もちろん、この記事の焦点である、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つの最大の欠点があります。
- 必要がありますJava8以上(既にによるjava8非常に高い普及率なので、この良いです)
- 翻訳パラメータを持っている必要があります
-parameters
(移行は非常に友好的ではないので、これはより致命的である、コンパイラ依存パラメータので)
指定-parameters
コンパイラ引数の方法を:
- コマンドを手動でコンパイルします。
javac -parameters XXX.java
- コンパイルする(例えばアイデアを)IDE:
- 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つの現象を言うことができます:
- デフォルトのjavacコンパイラで使用されているアイデアがでバイトコードにコンパイルされます
LocalVariableTable
。下図のようにしかし、あなたはまた、それをオフにすることができます:
- Mavenは、デフォルトのjavacは、やはりとバイトコードをコンパイルし使用しています
LocalVariableTable
(Mavenのは、熟練した学生が指し促す。私が知っていることはできませんが、Mavenの時間は、コマンドとパラメータをコンパイルコンパイル〜)
----
エピソード:科学に作用する(プロキシ、CGLIB、Javassistの、ASM):
ASM
:Java
バイトコード操作フレームワークのオープンソース。JVMの基本的なレベルの操作アセンブラ命令レベルクラス構造および組立説明書のユーザーは非常に厳しい、ある程度の知識を持っているJVMが必要です。Javassist
:上記の効果。比較するとASM
、それは簡単な操作によって特徴付けられ、そしてまた(もちろん何の迅速なASM)を高速化することはできません。それは重要である:それはJVMの指示/アセンブリ命令を理解する必要はありません-Proxy
ダイナミックエージェント:動的に生成された(非翻訳だけでなく、事前に)プロキシクラス:$Proxy0 extends Proxy implements MyInterface{ ... }
、それは唯一のインターフェイスにすることができます(またはクラスがインタフェースを実装)プロキシをすることを決定し、単一継承をも決定することができない(抽象)プロキシクラス〜CGLIB
:ですベースのASMパワフル、高性能、高品質なバイトコード生成ライブラリは。これは、ランタイムJavaクラスで拡張され、Javaインタフェースを実装することができます。>
Spring AOP
と同様にHibernate
プロキシオブジェクトを作成するために使用されCGLIB
前記事が直接使用導入しているCGLIB
のAPI
/プロキシオブジェクトを生成する操作バイトコードを、物品は単純で直接的に実証する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 MVC
Haoshiが、それはに依存しない-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、手動で離陸したグループにあなたを招待 ==