DEV Community

Cover image for Making reflection fully work on Java 16 and later
JJBRT
JJBRT

Posted on • Updated on

Making reflection fully work on Java 16 and later

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 of Burningwave 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 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:

For fields handling we are going to use Fields component:

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);
Enter fullscreen mode Exit fullscreen mode

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

  • by direct method invocation:
//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
);
Enter fullscreen mode Exit fullscreen mode
  • by direct method handle invocation:
//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
);
Enter fullscreen mode Exit fullscreen mode
  • by retrieving and invoking a method:
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
);
Enter fullscreen mode Exit fullscreen mode
  • by retrieving and invoking a method handle:
//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
);
Enter fullscreen mode Exit fullscreen mode

Another way to use reflection is to export all modules to all modules at runtime 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 you can download/clone the tutorial shared on GitHub.

Discussion (0)