⇧ amazing...
java.lang.reflectパッケージのAPIで、privateなstaticインナークラスを扱う
Javaで単体テストを行っていて、privateなstaticインナークラスのコンストラクターを実施する必要があったので、調べてみました。
⇧ 上記サイト様を参考にさせていただきました。
今回は、JUnit5を使ってみました。
pom.xmlにJUnit5を利用できるようにするための依存関係を追加。
■/reflect/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com</groupId> <artifactId>reflect</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <junit.jupiter.version>5.9.2</junit.jupiter.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M8</version> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.jupiter.version}</version> </dependency> </dependencies> </plugin> </plugins> </build> <dependencies> <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.jupiter.version}</version> <scope>test</scope> </dependency> </dependencies> </project>
リフレクションの動作確認とは関係ないけど、プロパティファイルを用意。
■/reflect/src/main/resources/application.properties
propertyLocation=/reflect/src/main/resources/application.properties propertyVersion=main-ver-0.1
■/reflect/src/test/resources/application.properties
propertyLocation=/reflect/src/test/resources/application.properties propertyVersion=test-ver-0.1
リフレクションの検証のためのソースコードは以下のようになりました。全くもって実用的でない処理ですが、ご容赦ください。
■/reflect/src/main/java/reflect/dto/InnerBase.java
package reflect.dto; import java.util.Properties; class InnerBase { private Properties superProp; public InnerBase(Properties prop) { this.superProp = prop; } public Properties superTakeProperties() { return this.superProp; } }
■/reflect/src/main/java/reflect/dto/Outer.java
package reflect.dto; import java.util.Properties; public class Outer { private Properties prop; public Properties setUpProperties(Properties prop) { Inner inner = new Inner(prop); this.prop = inner.takeProperties(); inner.test("Outer.setUpProperties: ", this.prop); return this.prop; } private static class Inner extends InnerBase { private Properties innerProp; private Properties takeProperties() { return this.innerProp; } private String test (String str, Properties prop) { return str + prop.toString(); } private Inner (Properties prop) { super(prop); this.innerProp = prop; } } }
■/reflect/src/test/java/reflect/OuterTest.java
package reflect; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map.Entry; import java.util.Properties; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import reflect.dto.Outer; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class OuterTest { private static Properties prop; @BeforeAll public void setUpProperties() { InputStream is = null; try { prop = new Properties(); is = ClassLoader.class.getResourceAsStream("/application.properties"); prop.load(is); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @Test public void test() { ClassLoader loader = ClassLoader.getSystemClassLoader(); try { // test private static inner class Class<?> innerClazz = loader.loadClass("reflect.dto.Outer$Inner"); Constructor<?>[] constructorArr = innerClazz.getDeclaredConstructors(); Object obj = null; for (int index = 0; index < constructorArr.length; index++) { // check constructor argument if (constructorArr[index].getParameterTypes().length == 1) { constructorArr[index].setAccessible(true); obj = constructorArr[index].newInstance(prop); String targetName = obj.getClass().getCanonicalName(); String expectName = Outer.class.getName() + "." + "Inner"; if (Objects.nonNull(targetName) && targetName.equals(expectName)) { // test inner class private method Method method = innerClazz.getDeclaredMethod("takeProperties"); method.setAccessible(true); Object methodResult = method.invoke(obj); System.out.println(methodResult); // test inner class private method with arguments method = innerClazz.getDeclaredMethod("test", String.class, Properties.class); method.setAccessible(true); methodResult = method.invoke(obj, "OuterTest: ", prop); System.out.println(methodResult); // test super class method method = innerClazz.getSuperclass().getDeclaredMethod("superTakeProperties"); method.setAccessible(true); methodResult = method.invoke(obj); System.out.println(methodResult); } } } Outer outer = new Outer(); Properties properties = outer.setUpProperties(prop); for(Entry<Object, Object> e : properties.entrySet()) { System.out.println(e); } } catch (ClassNotFoundException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } } }
で、実行すると、
privateなイstaticンナークラスのprivateなコンストラクターを実施して、privateなメソッド呼び出しが実施できたようです。
何か、
⇧ GraalVMとか使ってる場合は、GraalVM向けの設定ファイルが必要なんだとか。
あと、リフレクションとは関係ないですが、JUnit5を使うための依存関係がJUnit4とだいぶ違うのも戸惑いますかね...
ちなみに、staticなclassってインスタンス化できるのか認識が曖昧だったのだけど、
⇧ staticなclassのインスタンス化できるようです。
毎度モヤモヤ感が半端ない...
今回はこのへんで。