Making reflection fully work on Java 16 and later
Tags: java, reflection, tutorial
Published: 2021-09-14 11:00:33

Due to the strong encapsulation introduced starting from jdk 16 some non-public classes, methods and fields of the java.* package are no longer available via reflection, so how to get the legacy code to work on these new versions of JDK?

In this situation, [**the member handlers**](https://github.com/burningwave/core#Handling-privates-and-all-other-members-of-an-object) of [**Burningwave Core**](https://burningwave.github.io/core/) library comes to our aid, by giving us the ability to retrieve any field, method, or constructor of any class. These components are able to accomplish their task thanks to a [**special driver**](https://burningwave.github.io/jvm-driver/) which requires **no parameters to be passed to the jvm executable**.

The members handlers use to cache all members for faster access, and now let's now see how to use these components.
To start we need to add the following dependency to our _pom.xml_:

{% gist https://gist.github.com/Roberto-Gentili/536ee2d3f775030d46d82a008da10dbe.js %}

For fields handling we are going to use **Fields** component:

```java
org.burningwave.core.classes.Fields fields =
	org.burningwave.core.assembler.StaticComponentContainer.Fields;

ClassLoader classLoader =
	Thread.currentThread().getContextClassLoader();

//Fast access by memory address
Collection<Class<?>> loadedClasses =
	fields.getDirect(classLoader, "classes");

//Access by Reflection
loadedClasses = fields.get(classLoader, "classes");

//Get all field values of an object 
//through memory address access
Map<Field, ?> values =
	fields.getAllDirect(classLoader);

//Get all field values of an object
//through reflection access
values = fields.getAll(classLoader);
```

For methods handling we are going to use **Methods** component and we can accomplish the task in several ways:

* by direct method invocation:

```java
//loading the byte code
org.burningwave.core.io.FileSystemItem fileSystemItem = 
    org.burningwave.core.io.FileSystemItem.ofPath(
        "C:/my/pckg/MyClass.class"
    );
byte[] byteCode = fileSystemItem.toByteArray();

//invoking defineClass method
Class<?> cls = org.burningwave.core.assembler.StaticComponentContainer.Methods.invoke(
	Thread.currentThread().getContextClassLoader(),
	"defineClass",
	"my.pckg.MyClass",
    byteCode,
    0,
    byteCode.length,
    null
);
```

* by direct method handle invocation:
```java
//loading the byte code
org.burningwave.core.io.FileSystemItem fileSystemItem = 
    org.burningwave.core.io.FileSystemItem.ofPath(
    	"C:/my/pckg/MyClass.class"
    );
byte[] byteCode = fileSystemItem.toByteArray();

//invoking defineClass method handle
Class<?> cls = org.burningwave.core.assembler.StaticComponentContainer.Methods.invokeDirect(
	Thread.currentThread().getContextClassLoader(),
	"defineClass",
	"my.pckg.MyClass",
    byteCode,
    0,
    byteCode.length,
    null
);
```

* by retrieving and invoking a method:
```java
org.burningwave.core.classes.Methods methods =
	org.burningwave.core.assembler.StaticComponentContainer.Methods;
//Filtering and obtaining a Method reference
Method method = methods.findFirst(
    MethodCriteria.byScanUpTo((cls) ->
        //We only analyze the ClassLoader class and not all of its hierarchy (default behavior)
        cls.getName().equals(ClassLoader.class.getName())
    ).name(
        "defineClass"::equals
    ).and().parameterTypes(params -> 
        params.length == 5
    ).and().parameterTypesAreAssignableFrom(
        String.class, byte[].class, int.class, int.class, ProtectionDomain.class
    ).and().returnType((cls) -> 
        cls.getName().equals(Class.class.getName())
    ), ClassLoader.class
);

//loading the byte code
org.burningwave.core.io.FileSystemItem fileSystemItem = 
    org.burningwave.core.io.FileSystemItem.ofPath(
    	"C:/my/pckg/MyClass.class"
    );
byte[] byteCode = fileSystemItem.toByteArray();

//invoking defineClass method
Class<?> cls = methods.invoke(
    Thread.currentThread().getContextClassLoader(),
    method,
    "my.pckg.MyClass",
    byteCode,
    0,
    byteCode.length,
    null
);
```

* by retrieving and invoking a method handle:

```java
//Filtering and obtaining a MethodHandle reference
MethodHandle defineClassMethodHandle = org.burningwave.core.assembler.StaticComponentContainer.Methods.findFirstDirectHandle(
    MethodCriteria.byScanUpTo((cls) ->
    	//We only analyze the ClassLoader class and not all of its hierarchy (default behavior)
        cls.getName().equals(ClassLoader.class.getName())
    ).name(
        "defineClass"::equals
    ).and().parameterTypes(params -> 
        params.length == 5
    ).and().parameterTypesAreAssignableFrom(
        String.class, byte[].class, int.class, int.class, ProtectionDomain.class
    ).and().returnType((cls) -> 
        cls.getName().equals(Class.class.getName())
    ), ClassLoader.class
);

//loading the byte code
org.burningwave.core.io.FileSystemItem fileSystemItem = 
	org.burningwave.core.io.FileSystemItem.ofPath(
		"C:/my/pckg/MyClass.class"
	);
byte[] byteCode = fileSystemItem.toByteArray();

//invoking defineClass method handle
Class<?> cls = defineClassMethodHandle.invoke(
	Thread.currentThread().getContextClassLoader(),
	"my.pckg.MyClass",
	byteCode,
	0,
	byteCode.length,
	null
);
```
Another way to use reflection is [**to export all modules to all modules at runtime**](https://dev.to/jjbrt/how-to-avoid-resorting-to-add-exports-and-add-opens-in-jdk-16-and-later-j3m) through **Modules** component: in this case it will be possible to use reflection in the standard style without resorting to the reflection components of Burnignwave Core.

[**From here**](https://github.com/JJBRT/advanced-java-tutorials) you can download/clone the [**tutorial shared on GitHub**](https://github.com/JJBRT/advanced-java-tutorials/tree/master/reflection).