Javaリフレクションメカニズム
反射の概要
Javaリフレクションとは、プログラムが実行中にクラスの構造情報を取得し、動的にオブジェクトを作成したり、フィールドにアクセスしたり、メソッドを呼び出したり、コンストラクタを呼び出したりするメカニズムです。
多くの場合、コードはコンパイル時に使用する型を指定します。リフレクションを使用すると、プログラムはクラス名や設定ファイルに基づいて実行時にどのクラスをロードするかを決定できるため、リフレクションはフレームワーク、依存注入、オブジェクトマッピング、汎用ツールクラスでよく使われます。
クラスのロードとClassオブジェクト
JVMがクラスをロードすると、その型に対して一意のClassオブジェクトが作成されます。Classオブジェクトは,クラス名,フィールド,メソッド,コンストラクタ,アノテーションなどの構造情報を保持し,リフレクションを用いるエントリである.
Classオブジェクトを取得する
Class.forName()から入手可能
try {
Class<?> personClass = Class.forName("com.hyxy.Person");
System.out.println(personClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
完全なクラス名でクラスをロードするこの方法は、クラス名が設定ファイルから取得された場合や、コンパイル時にターゲット型を直接参照できない場合に適しています。
クラスリテラルで取得
Class<Person> personClass = Person.class;
コンパイル時にターゲット型がわかっている場合は、类型.classを最も簡単に使用し、ClassNotFoundExceptionをスローしません。
オブジェクトからのアクセス
Person person = new Person();
Class<?> personClass = person.getClass();
オブジェクトがすでに保持されているが、そのランタイム型を取得する必要がある場合、getClass()を呼び出すことができます。
public static void printType(Object object) {
Class<?> objectClass = object.getClass();
System.out.println(objectClass.getName());
}
Classクラスの一般的な方法
| 方法 | 作用 |
|---|---|
getName | パッケージ名を含む完全なクラス名の取得 |
getSimpleName | パッケージ名を含まないクラス名の取得 |
getDeclaredField String name | 指定された名前の現在のクラス内のフィールドを取得する |
getDeced | 現在のクラス宣言の全フィールドを取得 |
getDecedString name Class<>... parerTypes | メソッド名とパラメータの型に基づいてメソッドを取得 |
getDeclaredMethods | 現在のクラス宣言のすべてのメソッドを取得 |
getDecedConstrClass <>... parameterTypes | パラメータの型に基づいてコンストラクタを取得する |
getDecedConstrors | 現在のクラス宣言のすべてのコンストラクタを取得 |
Class.newInstance()は古い。オブジェクトを作成するときは、コンストラクタのnewInstance()を呼び出す前にコンストラクタを取得してください。
Object object = personClass.getDeclaredConstructor().newInstance();
Fieldオペレーションフィールドの使用
各Fieldオブジェクトは、クラス内で宣言されたフィールドを表します。Fieldでは、フィールド名、フィールドタイプ、指定されたオブジェクトのフィールドの読み取りや割り当てを取得できます。
一般的な方法は以下の通り
| 方法 | 作用 |
|---|---|
getName | フィールド名の取得 |
getType | フィールドの種類を取得 |
setAccessible true | 非パブリックフィールドへの反射的アクセスを許可する |
set Object object Object value | 指定したオブジェクトのフィールドに値を割り当てる |
get Object object | 指定されたオブジェクトのフィールド値の読み取り |
次の例では、Studentオブジェクトを作成し、反射によってそのnameフィールドを修正します。
package com.hyxy;
import java.lang.reflect.Field;
public class FieldTest {
public static void main(String[] args) {
try {
Class<?> studentClass = Class.forName("com.hyxy.Student");
Object student = studentClass.getDeclaredConstructor().newInstance();
Field nameField = studentClass.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(student, "Tom");
Object name = nameField.get(student);
System.out.println(name);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}getDeclaredField()はプライベートフィールドを含む現在のクラス宣言のフィールドを取得できますが、自動的に親クラスを検索しません。プライベートフィールドへのアクセスには通常、setAccessible(true)を呼び出す必要がある。
Methodを使用したメソッドの呼び出し
各Methodオブジェクトは、クラス内のメソッドを表します。invoke()を呼び出すと、最初の引数はメソッドが属するオブジェクトであり、それ以降の引数はメソッドを呼び出すために必要な引数です。
package com.hyxy;
import java.lang.reflect.Method;
public class MethodTest {
public static void main(String[] args) {
try {
Class<?> studentClass = Class.forName("com.hyxy.Student");
Object student = studentClass.getDeclaredConstructor().newInstance();
Method method1 = studentClass.getDeclaredMethod("study");
Method method2 = studentClass.getDeclaredMethod(
"study",
String.class,
int.class
);
method1.setAccessible(true);
method2.setAccessible(true);
method1.invoke(student);
method2.invoke(student, "Java", 24);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}オーバーロードされたメソッドを検索するときは、メソッド名だけでなく、引数型の正確なリストも提供します。メソッド呼び出し中にターゲットメソッド内でスローされる例外はInvocationTargetExceptionとしてラップされます。
静的メソッドの場合、invoke()を呼び出すときに最初の引数をnullに渡すことができます。
method.invoke(null, "参数");
Constructorでオブジェクトを作成するには
各Constructorオブジェクトは、クラス内のコンストラクタを表します。パラメータ型から指定したコンストラクタを取得し、newInstance()を呼び出してオブジェクトを作成します。
package com.hyxy;
import java.lang.reflect.Constructor;
public class ConstructorTest {
public static void main(String[] args) {
try {
Class<Student> studentClass = Student.class;
Constructor<Student> constructor = studentClass.getDeclaredConstructor(
String.class,
int.class,
int.class
);
constructor.setAccessible(true);
Student student = constructor.newInstance("Tom", 20, 110011);
System.out.println(
student.getName() + "," +
student.getAge() + "," +
student.getNo()
);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}リフレクション汎用 DAOの実装
基本的な考え
従来のDAOは通常、各テーブルに追加、削除、変更、チェックコードを記述します。汎用 DAOは、リフレクションによってエンティティークラスのクラス名とフィールド情報を読み取り、SQLを動的に生成することができる。
簡単な合意は以下の通り。
- エンティティークラス名はテーブル名に対応します。
- エンティティークラスのフィールド名はカラム名に対応します。
- プライマリ·キー·フィールド名は呼び出し元によって渡されます。
- インクリメンタル·プライマリキーは、挿入時にプレースホルダパラメータとして渡されません。
実際のプロジェクトでは、クラス名とテーブル名、フィールド名とカラム名はしばしば同一ではないため、マッピングはしばしば注釈とともに行われます。
一般的な新規メソッドの例
package com.hyxy.dao;
import org.apache.commons.dbutils.QueryRunner;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class CommonDao {
public int insert(Object entity, String primaryKeyName, boolean autoIncrement) {
Class<?> entityClass = entity.getClass();
String tableName = entityClass.getSimpleName();
Field[] fields = entityClass.getDeclaredFields();
List<String> columnNames = new ArrayList<>();
List<String> placeholders = new ArrayList<>();
List<Object> parameterValues = new ArrayList<>();
for (Field field : fields) {
String fieldName = field.getName();
if (autoIncrement && fieldName.equals(primaryKeyName)) {
continue;
}
field.setAccessible(true);
columnNames.add(fieldName);
placeholders.add("?");
try {
parameterValues.add(field.get(entity));
} catch (IllegalAccessException e) {
throw new RuntimeException("读取字段失败:" + fieldName, e);
}
}
String sql = "insert into " + tableName +
"(" + String.join(",", columnNames) + ") values(" +
String.join(",", placeholders) + ")";
QueryRunner queryRunner = new QueryRunner();
try (Connection connection = DBUtil.getConnection()) {
return queryRunner.update(
connection,
sql,
parameterValues.toArray()
);
} catch (SQLException e) {
throw new RuntimeException("执行新增操作失败", e);
}
}
}この例では、リフレクションとJDBCカプセル化の基本的な組み合わせを示しますが、以下の制限があります。
- エンティティークラス名は、テーブル名と一貫している必要があります。
- フィールド名はカラム名と一致する必要があります。
- フィールドの順序はリフレクションに依存しており、データベースの固定順序として使用しないでください。
- 実際のプロジェクトでは、主キー、継承フィールド、静的フィールド、トランザクション、例外システムも扱う必要があります。
- テーブル名と列名は事前コンパイルされたプレースホルダを使用できないため、信頼できるマッピング情報から取得する必要があり、ユーザー入力は直接使用できません。
反射の使用に関する注意事項
リフレクションはプログラムのダイナミクスを向上させるが、コードの可読性を低下させ、コンパイル時の型チェックの一部をバイパスする。使用にあたっては以下の原則に従ってください。
- 直接呼び出す場合は、通常のJavaコードを優先します。
- リフレクションエントリのタイプとパラメータのチェックを行います。
- 未検証のユーザー入力をクラス名、メソッド名、テーブル名、カラム名として直接使用しないでください。
ReflectiveOperationExceptionに対して一括処理を行った.- モジュラー Javaプロジェクトでは、非 publicメンバへのアクセスもモジュールアクセス規則によって制限される場合があります。
気に入ったならばコメントを残してくださいね~