はじめに
知識としてはリフレクションを使って private なメソッドやフィールドにアクセスできることは知っていた。特に unit test の際に重宝できることも。ただ、今まではどうも黒魔術的な API を使うのに腰が引けていたのと、そもそも unit test の際に private にアクセスしなくてはならないこと自体、ダメな設計であるということで出来る限り元ソースの方の修正を依頼する方向で仕事をしていた。
ただ考えてみたらホンの少しのコードを自分のライブラリに加えておくだけでささっと unit test できるわけで、使いどころさえしっかりしていれば別にそんなに極端に悪いことでもない。それに「Javaの小枝」っぽいネタだし。ということで以下のコードを自前ライブラリに加えることにする。(例によって Tuple は以前示したものを使っている)
VisibilityBreaker.java
public static class VisibilityBreaker {
public static interface Invokable {public Object invoke(Object...args) throws Exception;}
public static interface Readable {public Object read() throws Exception;}
public static interface Writable {public void write(Object value) throws Exception;}
private final List<Tuple2<Method, Invokable>> methods = new ArrayList<>();
private final List<Tuple3<Field, Readable, Writable>> fields = new ArrayList<>();
public Invokable getMethodInvokerBy(Predicate<Method> condition) {return methods.stream().filter(tpl2 -> condition.test(tpl2.car)).map(tpl2 -> tpl2.cdr.car).findAny().get();}
public Readable getFieldReaderBy(Predicate<Field> condition) {return fields.stream().filter(tpl3 -> condition.test(tpl3.car)).map(tpl3 -> tpl3.cdr.car).findAny().get();}
public Writable getFieldWriterBy(Predicate<Field> condition) {return fields.stream().filter(tpl3 -> condition.test(tpl3.car)).map(tpl3 -> tpl3.cdr.cdr.car).findAny().get();}
public Invokable getMethodInvokerByName(String name) {return getMethodInvokerBy(m -> name.equals(m.getName()));}
public Readable getFieldReaderByName(String name) {return getFieldReaderBy(f -> name.equals(f.getName()));}
public Writable getFieldWriterByName(String name) {return getFieldWriterBy(f -> name.equals(f.getName()));}
public VisibilityBreaker(Object source) {
for (Method m : source.getClass().getDeclaredMethods()) methods.add(Tuple.of(m, args -> m.invoke(source, args)));
for (Field f : source.getClass().getDeclaredFields()) fields.add(Tuple.of(f, () -> f.get(source), value -> f.set(source, value)));
methods.stream().forEach(m -> m.car.setAccessible(true));
fields.stream().forEach(f -> f.car.setAccessible(true));
}
}
こんな風に使う。これでみんなでバリバリ private フィールド壊しまくって遊ぼうず(違
Main.java
public static class Sample {
private void privMethod(String hoge) {System.out.println("priv " + secret + " " + hoge);}
private int secret = 234;
}
public static void main(String[] args) throws Exception {
Sample s = new Sample();
VisibilityBreaker broken = new VisibilityBreaker(s);
System.out.println(broken.getFieldReaderByName("secret").read());
broken.getFieldWriterByName("secret").write(456);
System.out.println(broken.getFieldReaderByName("secret").read());
broken.getMethodInvokerByName("privMethod").invoke("hogehoge");
}