この記事は私のGitHub:Java Development Bookに含まれています、スターを与えることを歓迎します:https://github.com/eson15/javaAll
私は更新を続けます、スターを歓迎します!
WeChat検索:Wu Geがプログラミングについて話し、「メモ」と返信すると、私が書いた10万語のSpringbootクラシックスタディノートのコピーを受け取ることができます。
記事ディレクトリ
リフレクションはフレームデザインの魂の要素です!
1.リフレクションとは何ですか?
いわゆるリフレクションは、実行時にjava言語が持つ一種の自己監視機能です。リフレクションを使用すると、プログラムコードでJVMにロードされたクラスの内部情報を取得できるため、プログラムの実行時に必要なクラスの内部情報を取得できます。コードを書くときに必要なクラスの内部情報を知る必要はありません。情報を動的に取得してオブジェクトを動的に呼び出すこのメソッドは、Javaのリフレクションメカニズムとも呼ばれます。
Javaのリフレクションメカニズムにより、プログラマーは、プログラムの実行中にユーザーが入力した情報を確認するなど、プログラムの実行プロセスをより詳細に制御できます。また、プログラムの実行プロセスを逆にすることもできます。これにより、リフレクションが柔軟なアプリケーションになります。メインツール。
2.リフレクションの原理
2.1リフレクションの一般的なクラスと機能
Javaリフレクションメカニズムを実現するには、次の4つのクラスの助けが必要です。
- クラスオブジェクト
- ConstructorクラスのConstructorオブジェクト
- Fieldクラスのプロパティオブジェクト
- Methodクラスのメソッドオブジェクト
2.2クラスクラスに含まれるメソッド
これらの4つのオブジェクトを通して、クラスのさまざまなコンポーネントを大まかに見ることができます。これらの中核は、リフレクションを実装するための基礎となるClassクラスです。Classクラスに含まれるメソッドは次のとおりです。
シリアルナンバー | 名前 | 特徴 |
---|---|---|
1 | getName() | このタイプの完全修飾名を取得します |
2 | getSuperClass() | このタイプの直接スーパークラスの完全修飾名を取得します |
3 | isInterface() | このタイプがクラスタイプであるかインターフェイスタイプであるかを判別します |
4 | getTypeParamters() | このタイプのアクセス修飾子を取得します |
5 | getInterfaces() | 直接スーパーインターフェースの完全修飾名の順序付きリストを取得します |
6 | getFields() | フィールド情報を取得する |
7 | getMethods() | メソッド情報を取得する |
2.3主な反省方法
リフレクションを適用するときは、通常、クラスのコンストラクター、属性、およびメソッドに関心があります。以下では、主に、Classクラスのこれら3つの要素のメソッドを紹介します。
2.3.1コンストラクターを取得する方法
文法 | 特徴 |
---|---|
コンストラクターgetConstructor(Class [] params) | 特別なパラメータタイプを使用するパブリックコンストラクタを取得します |
コンストラクター[] getConstructors() | クラスのすべてのパブリックコンストラクターを取得します |
コンストラクターgetDeclaredConstructor(Class [] params) | 特定のパラメータータイプを使用するコンストラクターを取得します(アクセスレベルに関係なく) |
ビルダー[] getDeclaredConstructors() | クラスのすべてのコンストラクターを取得します(アクセスレベルに関係なく) |
2.3.2フィールド情報の取得方法
文法 | 特徴 |
---|---|
フィールドgetField(文字列名) | 名前付きパブリックフィールドを取得する |
Field [] getFields() | クラスのすべてのパブリックフィールドを取得します |
フィールドgetDeclaredField(文字列名) | クラス宣言の名前付きフィールドを取得します |
Field [] getDeclaredFields() | クラスによって宣言されたすべてのフィールドを取得します |
2.3.3メソッド情報の取得方法
文法 | 特徴 |
---|---|
メソッドgetMethod(String name、Class [] params) | 特定のパラメータタイプを使用して、名前付きパブリックメソッドを取得します |
Method [] getMethods() | クラスのすべてのパブリックメソッドを取得します |
メソッドgetDeclaredMethod(String name、Class [] params) | クローズアップパラメータタイプを使用して、クラス宣言の名前付きメソッドを取得します |
Method [] getDeclaredMethods() | クラス宣言のすべてのメソッドを取得します |
2.4リフレクション実際の戦闘の基本的な手順
Class actionClass=Class.forName(“MyClass”);
Object action=actionClass.newInstance();
Method method = actionClass.getMethod(“myMethod”,null);
method.invoke(action,null);
上記はリフレクションの使用の最も一般的な例です。最初の2行はクラスのロード、リンク、初期化を実装し(newInstanceメソッドは実際にはリフレクションを使用して<init>
メソッドを呼び出します)、次の2行はクラスオブジェクトから取得して実行されるメソッドオブジェクトを実装します。リフレクティブコール。コードの特定の原理を簡単に分析してみましょう。
2.4.1クラスのClassオブジェクトを取得する
通常、3つの異なるメソッドがあります
。1)クラスc = Class.forName( "java.lang.String")
2)クラスc = MyClass.class
3)基本的なデータタイプの場合、クラスc = int.classまたはクラスc =を使用できます。 Integer.TYPEのようなステートメント。
举个小栗子:先通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例
PS:反射的原理之一其实就是动态的生成类似于上述的字节码,加载到jvm中运行。
上記のコードでmethod.invoke(action,null)
、アクションオブジェクトmyMethod
を呼び出すメソッドを実装する場合は、次のようなメソッドクラスを実装するだけでよいと想像してください。
Class Method{
public Object invoke(Object obj,Object[] param){
MyClass myClass=(MyClass)obj;
return myClass.myMethod();
}
}
2.4.2メソッドオブジェクトの取得
首先来看一下Method对象是如何生成的:
- 使用Method m =myclass.getMethod("myMethod")获得了一个Class对象
- 接着对其进行判断,如果没有对应的cache,那么JVM就会为其创建一个并放入缓冲空间
- 处理器再判断Cache中是否存在"myMethod"
- 如果没有则返回NoSuchMethodException
- 如果存在那么就Copy一份"myMethod"对象并返回
上記のClassオブジェクトは、クラスがロードされるときにJVMによって構築されます。JVMはクラスごとに一意のClassオブジェクトを管理します。このClassオブジェクトは、クラスのすべてのMethod、Field、およびConstructorキャッシュを維持します。このキャッシュも使用できます。これはルートオブジェクトと呼ばれます。getMethodによって取得されたMethodオブジェクトがルートオブジェクトへの参照を保持するたびに、いくつかの重いMethodメンバー変数(主にMethodAccessor)があるため、Methodオブジェクトが作成されるたびに再初期化する必要はないため、すべてが同じを表します。メソッドのMethodオブジェクトはすべて、ルートオブジェクトのMethodAccessorを共有し、作成されるたびに、ルートオブジェクトのcopyメソッドが呼び出されてコピーが作成されます。
Method copy() {
Method res = new Method(clazz, name, parameterTypes, returnType,
exceptionTypes, modifiers, slot, signature,
annotations, parameterAnnotations, annotationDefault);
res.root = this;
res.methodAccessor = methodAccessor;
return res;
}
2.4.3 invoke()メソッドを呼び出す
Methodオブジェクトを取得した後、invokeメソッドを呼び出すプロセスは次のとおりです。
m.invoke(obj,param);
- 首先调用MethodAccess.invoke
- 如果该方法的累计调用次数<=15,会创建出NativeMethodAccessorImp
- 如果该方法的累计调用次数>15,会由java代码创建出字节码组装而成的MethodAccessorImpl
Method.invokeを呼び出した後、直接調整されることがわかりMethodAccessor.invoke
ます。MethodAccessorは、ReflectionFactoryによって作成された、上記の同じ名前のすべてのメソッドによって共有されるインスタンスです。作成メカニズムは、インフレーションと呼ばれるメソッドを使用します(JDK1.4以降)。このメソッドの累積呼び出し数が15未満の場合、NativeMethodAccessorImplが作成され、その実装では、ネイティブメソッドを直接呼び出して、リフレクションを実現します。このメソッドの累積呼び出し数の場合呼び出し数が15を超える場合、バイトコードでアセンブルされたMethodAccessorImplはJavaコードで作成されます。(インフレーションと15を使用するかどうかはjvmパラメーターで調整できます)
呼び出しMyClass.myMethod(String s)
を例にとると、Javaコードに変換された生成されたMethodAccessorImplバイトコードはおおよそ次のとおりです。
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public Object invoke(Object obj, Object[] args) throws Exception {
try {
MyClass target = (MyClass) obj;
String arg0 = (String) args[0];
target.myMethod(arg0);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}
javaの実行中のプロセスを詳細に分析すると、最初と16番目の呼び出しに最も時間がかかることがわかります(NativeMethodAccessorImplの初期化とバイトコードアセンブリMethodAccessorImpl)。初期化は避けられないため、ネイティブメソッドの初期化が高速になるため、最初の数回の呼び出しでネイティブメソッドが使用されます。
呼び出しの数が増えると、ネイティブ境界を越えるためにJNIを使用する各リフレクションは最適化を妨げます。相対的に言えば、アセンブルされたバイトコードを使用すると、Java呼び出しの形式でリフレクションを直接実装でき、JIT最適化の役割を果たします。 OopMap(正確なGCを実現するためにHotSpotが使用するデータ構造)を維持するために、JNIのカプセル化/カプセル化解除のパフォーマンス低下を回避します。
MethodAccessorが作成されている場合、Javaバージョンを使用した実装はネイティブバージョンよりも高速になります。そのため、呼び出しの数が特定の数(15回)に達すると、Java実装バージョンに切り替わり、将来、より頻繁なリフレクション呼び出しの可能性を最適化します。
3. Javaリフレクションアプリケーション(Hibernateフレームワーク)
すでに知っているように、Javaリフレクションメカニズムは、プログラムコンポーネントを動的にリンクする多様な方法を提供します。これにより、プログラムは、ターゲットクラスを事前にハードコーディングしなくても、任意のクラスのオブジェクトを作成および制御できます(セキュリティ制限に従って)。これらの特性により、リフレクションは、非常に一般的な方法でオブジェクトと連携するライブラリを作成するのに特に適しています。たとえば、リフレクションは、永続ストレージオブジェクトがデータベース、XML、またはその他の外部形式であるフレームワークでよく使用されます。リフレクションの重要性を説明する例として、Hibernateフレームワークを取り上げましょう。
Hibernateは、JDBCを保護し、ORMを実装するJavaフレームワークです。このフレームワークを使用すると、面倒なSQLステートメントを破棄し、HibernateのSessionクラスのsave()メソッドを使用して、特定のクラスのオブジェクトをデータベースに直接格納できます。それを行うためのsqlステートメントを含むのはコードHibernateです。このとき、疑問が生じます。Hibernateは、保存したいオブジェクトの属性をどのようにして知るのでしょうか。これらの属性は何ですか?それについて考えてみてください。オブジェクトのプロパティをデータベースに格納するときにSQLステートメントを作成する方法は?OK、リフレクションの役割が反映されました!
説明のために例を見てみましょう。たとえば、Userクラスを定義します。このUserクラスには20の属性があり、これらの属性のgetメソッドとsetメソッドがあります。これに対応して、データベースにUserテーブルがあり、このUserテーブルはに対応します。 20フィールド。Userテーブルからレコードを抽出し、このレコードの20フィールドの内容をUserオブジェクトmyUserの20属性に割り当てる必要があり、Hibernateフレームワークはコンパイル時にこのUserクラスを認識しないとします。 myUser.getXXXまたはmyUser.setXXXメソッドを直接呼び出すことはできません。現時点では、リフレクションが使用されます。具体的な処理プロセスは次のとおりです。
-
クエリ条件に従ってPreparedStamentステートメントを作成します。これは20フィールドの値を返します。
-
Hibernateは、構成ファイルを読み取ることにより、Userクラスのプロパティリストリスト(文字列配列)とこれらのプロパティのタイプを取得します。
-
myUserが属するクラスのClassオブジェクトcを作成します; c = myUser.getClass();
-
forループを作成します。ループの数はリストの長さです。
-
list [i]の値を読み取り、属性に対応するsetメソッドを作成します。
-
list [i]のタイプXXXを判別し、PreparedStamentステートメントでgetXXX(i)を呼び出してから、iフィールドの値を取得します。
-
4.2で取得した値を4.1で取得したsetメソッドのパラメーターとして使用します。これにより、属性などのフィールドの割り当てが完了し、プログラムが終了するまでループします。
-
反省がなければ、このような複雑な機能を実装することがどれほど難しいか想像するのは難しいです!
とはいえ、リフレクションは便利ですが、パフォーマンスの低下、セキュリティの低下、プロセスの複雑化など、独自の欠点もあります。興味のある読者は、実際の作業を深く学ぶこともできます。
著者情報
[作成者]:Wu Ge
[公開アカウント]:WuGeがプログラミングについて話します。みなさん、どうぞよろしくお願いします〜
【作者プロフィール】:東寺大学修士。Huawei、HKUST Xunfei、Pinduoduoで連続してピットを採掘します。独学のJavaルーキー、あなたの注意を楽しみにしています。
私の最大の励ましのように
↓↓↓↓↓↓