servletをjunitでテストするにあたってprotectedにアクセスする必要が出た。
調べたらリフレクションを使えばアクセスができるっぽい。
備忘録として記載。
【目標】 Q.リフレクションとは??
:参考記事達:
・リフレクションについて
https://qiita.com/suke_masa/items/0df3bb92bcb69f4a95d1
↑個人的にすごくわかりやすかった。
・ジェネリクスの<?>について
https://qiita.com/pebblip/items/1206f866980f2ff91e77
↑たまにでてくる<?>について
・junitでリフレクションのやり方
https://qiita.com/11295/items/b632850389831041b987
https://www.sejuku.net/blog/33252#:~:text=%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82-,private%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E3%81%AE%E5%8F%82%E7%85%A7%E3%81%A8%E5%A4%89%E6%9B%B4,-%E3%81%93%E3%81%93%E3%81%A7%E3%81%AFprivate
・フィールドの場合
https://qiita.com/11295/items/773e7395889e05f605ae
・JavaのリファレンスやAPIの読み方も勉強した方が効率良さそう。
https://www.sejuku.net/blog/33352#:~:text=8%20%E3%81%BE%E3%81%A8%E3%82%81-,Java%E3%81%AE%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9%E3%81%A8%E3%81%AF,-Java%E3%81%AE%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9
・getDeclaredField()であれば修飾子関係なくフィールドを取得できるっぽい
https://pppurple.hatenablog.com/entry/2016/08/01/225120#:~:text=is(arrayContainingInAnyOrder(expected)))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D-,getDeclaredField(),-getDeclaredField()%E3%81%A7%E3%81%AF%E3%80%81getField
:メモ書き:
・こんな感じでフィールドをアクセス可能にできるっぽい
Field f = testClass.getClass().getDeclaredField("str");
f.setAccessible(true);
・privateとかの修飾子のものを他クラスで扱うには?
Classクラス,Methodクラス,Fieldクラスの
getDeclaredなんちゃら()とかを使って
.setAccessible(true)でアクセス可能にすれば
private,protectedでも扱えるようになるらしい。
試しに作ってみたコード
環境:エクリプス、java11
package testreflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestReflection {
public static void main(String[] args) {
try {
/* クラスの場合 */
Class<?> clazz = Sample.class;
// クラスの完全修飾子を取得 Class.forNameには完全修飾子がいるらしい
String fqcn = clazz.getName();
// クラスを見つける
Class<?> sampleClass = Class.forName(fqcn);
/* コンストラクタの取得 */
// <?>を使ったためint.classで型を指定しないとエラーになる
Constructor<?> constructor = sampleClass.getConstructor(int.class);
// コンストラクタに100を渡す
Object sampleInstance = constructor.newInstance(100);
System.out.println(sampleInstance);
// ↓短縮版
// System.out.println(Class.forName(Sample.class.getName()).getConstructor(int.class).newInstance(100));
//
/* フィールドの場合 */
// フィールドのvalueを取得
Field valueField = sampleClass.getDeclaredField("value");
// privateだからアクセス可能にする
valueField.setAccessible(true);
// フィールドに200をセット
valueField.set(sampleInstance, 200);
// フィールドの値の取得
Object fieldObject = valueField.get(sampleInstance);
System.out.println(fieldObject);
/* メソッドの場合 */
// getDeclaredMethod()であればprotecedでも取得できる
Method multiMethod = sampleClass.getDeclaredMethod("multi", int.class);
// アクセス可能にする
multiMethod.setAccessible(true);
// メソッドの実行
Object ret = multiMethod.invoke(sampleInstance, 2);
System.out.println(ret);
// おびただしいほどの例外 短縮とか省けそうだけど??
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException
| IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchFieldException e) {
e.printStackTrace();
}
}
}
class Sample {
private int value;
public Sample(int value) {
this.value = value;
}
protected int multi(int value2) {
return value * value2;
}
@Override
public String toString() {
return "Sample{" + "value=" + value + '}';
}
}
【結論】
Classクラス,Methodクラス,Fieldクラスを使っていけばリフレクションができるらしい。
終わり。