※当サイトの記事には、広告・プロモーションが含まれます。

java.lang.reflectパッケージのAPIで、privateなstaticインナークラスを扱う

nazology.net

⇧ amazing...

java.lang.reflectパッケージのAPIで、privateなstaticインナークラスを扱う

Java単体テストを行っていて、privateなstaticインナークラスのコンストラクターを実施する必要があったので、調べてみました。

stackoverflow.com

ksino.hatenablog.com

stackoverflow.com

⇧ 上記サイト様を参考にさせていただきました。

今回は、JUnit5を使ってみました。

EclipseMavenプロジェクトを作成。

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なメソッド呼び出しが実施できたようです。

何か、

dev.classmethod.jp

⇧ GraalVMとか使ってる場合は、GraalVM向けの設定ファイルが必要なんだとか。

あと、リフレクションとは関係ないですが、JUnit5を使うための依存関係がJUnit4とだいぶ違うのも戸惑いますかね...

ちなみに、staticなclassってインスタンス化できるのか認識が曖昧だったのだけど、

www.geeksforgeeks.org

qiita.com

⇧ staticなclassのインスタンス化できるようです。

毎度モヤモヤ感が半端ない...

今回はこのへんで。