blog

Java上級 - リフレクション

Javaのリフレクション機構は、実行中の状態で、任意のクラスに対して、クラスのすべての属性とメソッドを知ることができます;任意のオブジェクトに対して、そのメソッドのいずれかを呼び出すことができます。 ...

Apr 27, 2020 · 10 min. read
シェア

まず、Javaがオブジェクトを生成する方法を考えてみましょう。

Javaがオブジェクトを生成する方法は、おおよそ3つあります:

  1. newキーワードを使う:これは、オブジェクトを作成する最も一般的で簡単な方法です。
  2. cloneメソッドを使う: オブジェクト上でcloneメソッドを呼び出すと、JVMは新しいオブジェクトを作成し、前のオブジェクトの内容をすべてコピーします。
  3. Class.forName("com.example.Person");

I. 概要

Javaのリフレクション・メカニズムは、実行状態において、任意のクラスに対して、そのクラスのすべてのプロパティとメソッドを知ることができます。

リフレクションとは何かを自分で考えてみてください。

簡単に言うと、Javaのリフレクションは次のようなものです:

オブジェクトが必要で、Javaのリフレクション機構を介してクラスの情報を取得することができます、プロパティとクラスのメソッドは、Javaオブジェクトにマッピングされたリフレクション技術を使用して、これらのJavaオブジェクトを取るように何かを行うには。言い換えれば、リフレクションは、Javaクラスの個々のコンポーネントを反映しています。

第二に、リフレクションの役割

一般的に言って、リフレクションはいくつかの基本的なフレームワークを行うために使用されるか、または抽象度が高い基礎となるコードを記述するために使用されます。リフレクションを理解すると、フレームワークの原則の一部を理解するのに役立ちます!

第三に、目的のオブジェクトを取得するためにリフレクションを介して

Javaはオブジェクト指向言語であり、すべてがオブジェクトですので、Javaは、これらのコンパイルされたクラスファイルは、また、クラスに抽象化することができ、このクラスはクラスです!

したがって、Javaの反射機構の使用を開始するには、Classクラスのオブジェクトを取得することです。

Classクラスでは、静的メソッドを持っています。
このメソッドは、リフレクションを使用するための開始メソッドです。
Class clazz = Class.forName("com.example.Person");

Classクラスのオブジェクトを取得する3つの方法

  • .getClass();
  • .class;
  • Class.forName.class。

Class クラスの共通メソッド

Class クラスには、以下の共通メソッドがあります。

Constructor<T> getConstructor(Class<?>... parameterTypes) 
このClassオブジェクトで表されるクラスの指定されたパブリック・コンストラクタ・メソッドを反映したコンストラクタ・オブジェクトを返す。 
Method getMethod(String name, Class<?>... parameterTypes) 
このClassオブジェクトが表すクラスまたはインタフェースの指定されたパブリック・メンバ・メソッドを反映したMethodオブジェクトを返す。 
Field getField(String name) 
このClassオブジェクトが表すクラスまたはインタフェースの指定されたパブリック・メンバ・フィールドを反映したFieldオブジェクトを返す。
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 
このClassオブジェクトが表すクラスまたはインターフェースの指定されたコンストラクタ・メソッドを反映したコンストラクタ・オブジェクトを返す。 
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 
このClassオブジェクトが表すクラスまたはインターフェースの宣言されたメソッドを反映したMethodオブジェクトを返す。 
Field getDeclaredField(String name) 
このClassオブジェクトによって表されるクラスまたはインターフェースの宣言されたフィールドを反映したFieldオブジェクトを返す。 

第四に、リフレクションの使用

まず、以下のように Person クラスを作成します:

package com.example;
public class Person {
 private String name;
 private int age;
 public Person() {
 System.out.println("パラメータなしのコンストラクタだ」)。;
 }
 public Person(String name) {
 System.out.println("名前:"+ name);
 }
 private Person(int age) {
 System.out.println("年齢:"+ age);
 }
 public Person(String name, int age) {
 System.out.println("名前:"+ name + ",年齢:"+ age);
 }
}
  • リフレクション・コンストラクタ
    • パラメータなしコンストラクタのリフレクション
    • 引数 1 つのコンストラクタのリフレクション
    • 複数パラメータ・コンストラクタの反映
    • プライベートコンストラクタの反映
      • clazz.getConstructor()getDeclaredConstructor(Class<?>... parameterTypes);注意: プライベートコンストラクタをリフレクションすると、それがプライベートであるためにノーマルはエラーを報告します。そのため、プライベートコンストラクタをリフレクションするための特別なメソッドを提供します。 //プライベートコンストラクタを読み込んだ後、このメソッドでブルートフォースリフレクションを設定する必要があります。constructor.setAccessible(true);
    • 共通メソッドのリフレクション
  • : public オブジェクト invoke
package com.example;
import java.util.Date;
public class Person {
 private String name;
 private int age;
 public Person() {
 System.out.println("パラメータなしのコンストラクタだ」)。;
 }
 public Person(String name) {
 System.out.println("名前:"+ name);
 }
 private Person(int age) {
 System.out.println("年齢:"+ age);
 }
 public Person(String name, int age) {
 System.out.println("名前:"+ name + ",年齢:"+ age);
 }
 public void m1() {
 System.out.println("m1");
 }
 public void m2(String name) {
 System.out.println(name);
 }
 public String m3(String name,int age) {
 System.out.println(name+":"+age);
 return "aaa";
 }
 private void m4(Date d) {
 System.out.println(d);
 }
 public static void m5() {
 System.out.println("m5");
 }
 public static void m6(String[] strs) {
 System.out.println(strs.length);
 }
 public static void main(String[] args) {
 System.out.println("main");
 }
}
@org.junit.jupiter.api.Test//public void m1()
public void test1() throws Exception{
 Class clazz = Class.forName("com.cj.test.Person");
 Person p = (Person)clazz.newInstance();
 Method m = clazz.getMethod("m1", null);
 m.invoke(p, null);
}
@org.junit.jupiter.api.Test//public void m2(String name)
public void test2() throws Exception{
 Class clazz = Person.class;
 Person p = (Person) clazz.newInstance();
 Method m = clazz.getMethod("m2", String.class);
 m.invoke(p, "張さん");
}
@org.junit.jupiter.api.Test//public String m3(String name,int age)
public void test3() throws Exception{
 Class clazz = Person.class;
 Person p = (Person) clazz.newInstance();
 Method m = clazz.getMethod("m3", String.class,int.class);
 String returnValue = (String)m.invoke(p, " ,23);
 System.out.println(returnValue);
}
@org.junit.jupiter.api.Test//private void m4(Date d)
public void test4() throws Exception{
 Class clazz = Person.class;
 Person p = (Person) clazz.newInstance();
 Method m = clazz.getDeclaredMethod("m4", Date.class);
 m.setAccessible(true);
 m.invoke(p,new Date());
}
@org.junit.jupiter.api.Test//public static void m5()
public void test5() throws Exception{
 Class clazz = Person.class;
 Method m = clazz.getMethod("m5", null);
 m.invoke(null,null);
}
@org.junit.jupiter.api.Test//private static void m6(String[] strs)
public void test6() throws Exception{
 Class clazz = Person.class;
 Method m = clazz.getDeclaredMethod("m6",String[].class);
 m.setAccessible(true);
 m.invoke(null,(Object)new String[]{"a","b"});
}
@org.junit.jupiter.api.Test
public void test7() throws Exception{
 Class clazz = Person.class;
 Method m = clazz.getMethod("main",String[].class);
 m.invoke(null,new Object[]{new String[]{"a","b"}});
}

注意: 上のコードでは、test6 と test7 の invoke メソッドに渡されるパラメータが他と少し異なります。

これは、jdk1.4とjdk1.5でinvokeメソッドの扱い方に違いがあるためです。

  • 1.5: public Object invoke(Object obj,Object...args)
  • 1.4: public Object invoke(Object obj,Object[] args)

JDK1.4と1.5ではinvokeメソッドの扱いが異なるため、引数の配列を受け取るmain(String[] args)のようなメソッドを反映させる場合は、特別な処理が必要になります。

Javaアプリケーションのmainメソッドは文字列の配列、つまりpublic static void main(String[] args)で始まります。では、リフレクションによってmainメソッドを呼び出す場合、invokeメソッドにどのようにパラメータを渡せばよいのでしょうか。jdk1.5の構文によると、配列全体がパラメータになりますが、jdk1.4の構文によると、配列の各要素がパラメータに対応します。文字列配列をinvokeメソッドのパラメータとして渡す場合、javacは最終的にどちらの構文に従って処理するのでしょうか。 jdk1.5はjdk1.4の構文と互換性がなければならないため、jdk1.4の構文に従って処理されます。メインメソッドは、jdk1.4の構文に従って処理されます。つまり、配列は多数の個々のパラメータに分割されます。したがって、メインメソッドでパラメータを渡すには、コードmainMethod.invoke(null、新しい文字列[]{"xxx"})を使用することはできません、javacはjdk1.4の構文としてのみ理解するが、jdk1.5の構文としてではなくjdk1.5構文として解釈するので、引数の数が間違っている問題が発生します。

上記の問題の解決策

  • mainMethod.invoke(null,new Object[ ]{new String[ ]{"xxx"}}); - この方法では、パラメータの配列を渡しているので、1.4の構文との後方互換性を保つために、javacは配列に出会ったときに配列を複数のパラメータに分割してくれますが、Object[ ]配列の要素値は1つしかないので、パラメータの数が間違っていても配列を分割しても問題ありません。
  • mainMethod.invoke(null,new String[]{"xxx"}); - この方法は、渡すパラメータが配列ではなくオブジェクトであることと同じなので、1.4の構文に従っても分割されることはなく、問題は解決されます - コンパイラは特別な処理を行い、コンパイルはパラメータを配列として扱わないので、複数の配列に分割されることはありません。パラメータは

上記の説明を要約すると:メソッドを反映するとき、メソッドのパラメータが配列である場合、後方互換性の問題を考慮して、JDK1.4の構文に従って扱われます 解決策:JVMが配列を分割しないようにします。

  • 方法1:配列をObjectオブジェクトとして見る
  • 方法2:Object配列を再構築し、パラメータ配列が唯一の要素として存在するようにします。
  • 属性フィールドの反映
package com.example;
import java.util.Date;
public class Person {
 public String name = " ;
 
 private int age = 18;
 
 public static Date time;
 public int getAge() {
 return age;
 }
 public Person() {
 System.out.println("デフォルトのインスツルメントなしコンストラクタが実行される」)。;
 }
 public Person(String name) {
 System.out.println(" :" + name);
 }
 public Person(String name, int age) {
 System.out.println(name + "=" + age);
 }
 private Person(int age) {
 System.out.println(" :" + age);
 }
 public void m1() {
 System.out.println("m1");
 }
 public void m2(String name) {
 System.out.println(name);
 }
 public String m3(String name, int age) {
 System.out.println(name + ":" + age);
 return "aaa";
 }
 private void m4(Date d) {
 System.out.println(d);
 }
 public static void m5() {
 System.out.println("m5");
 }
 public static void m6(String[] strs) {
 System.out.println(strs.length);
 }
 public static void main(String[] args) {
 System.out.println("main");
 }
}
//public String name=" ;
@org.junit.jupiter.api.Test
public void test8() throws Exception{
 Class clazz = Person.class;
 Person p = (Person)clazz.newInstance();
 Field f = clazz.getField("name");
 String s = (String)f.get(p);
 System.out.println(s);
 //nameの値を変更する
 f.set(p, "王劉");
 System.out.println(p.name);
}
@org.junit.jupiter.api.Test//private int age = 18;
public void test9() throws Exception{
 Class clazz = Person.class;
 Person p = (Person)clazz.newInstance();
 Field f = clazz.getDeclaredField("age");
 f.setAccessible(true);
 int age = (Integer)f.get(p);
 System.out.println(age);
 f.set(p, 28);
 age = (Integer)f.get(p);
 System.out.println(age);
}
@org.junit.jupiter.api.Test//public static Date time;
public void test10() throws Exception{
 Class clazz = Person.class;
 Field f = clazz.getField("time");
 f.set(null, new Date());
 System.out.println(Person.time);
}

上記の考察を読んだ後、一般的なフレームワークの設定ファイルについていくつかのアイデアを持つかもしれません。

xmlを解析し、パラメータとしてxmlの内容、オブジェクトを作成するためのリフレクションの使用:上記は、簡単にリフレクションを使用することができると思われる共通のBeanの構成で、Springの設定ファイルです。

これに加えて、多くの一般的なリフレクションの使用のフレームワークで使用され、リフレクションは、フレームワークの魂は、リフレクションの知識とアイデアで、基礎のフレームワークを理解することです!

通常、設定ファイルの形式に加えて、フレームワークを使用し、多くは現在、アノテーションの形式を使用し、実際には、アノテーションとリフレクションが密接にリフレクションの使用に関連している簡単にクラス、フィールド、メソッドのアノテーションを取得することができますし、解析するためにこれらのアノテーションにアノテーションパーサーを書き込むと、いくつかの関連処理を行うので、設定ファイルまたはアノテーションの形式であるかどうか、それらはすべてリフレクションに関連しています!

Read next

SpringBoot入門: JWTベースのシンプルな認証と認可

このプロジェクトに関与するプロジェクトの大半の認証と認可は、Spring SecurityでJWTを使用します。 その中で、@ConfigurationPropertiesアノテーションを介して、上記の構成情報になります...

Apr 27, 2020 · 10 min read