Lets say I want to ship a program that runs on java 17 as that is what is available widely, but use reflection to detect if im running on a vm with the ability to produce a thread factory via Thread.ofVirtual().name("abc").factory()
. Java prohibits reflective access to its internals when they are not properly configured with modules. How do I configure my program to be able to access this method reflectively? The reason for reflective access is to continue compiling into <jdk19 bytecode, but use reflection to use jdk19 features if they are present. Is there a combination of arguments or module-info.java
contents that can achieve this goal, or is this not possible?
when you try this in jshell, here is what you get:
jshell --enable-preview
| Welcome to JShell -- Version 19.0.2
| For an introduction type: /help intro
jshell> Thread.class.getMethod("ofVirtual")
...> .invoke(null)
...> .getClass()
...> .getMethod("name", String.class, Long.TYPE)
...> .setAccessible(true)
| Exception java.lang.reflect.InaccessibleObjectException: Unable to make public java.lang.Thread$Builder$OfVirtual java.lang.ThreadBuilders$VirtualThreadBuilder.name(java.lang.String,long) accessible: module java.base does not "opens java.lang" to unnamed module @30dae81
| at AccessibleObject.throwInaccessibleObjectException (AccessibleObject.java:387)
| at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:363)
| at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:311)
| at Method.checkCanSetAccessible (Method.java:201)
| at Method.setAccessible (Method.java:195)
| at (#1:5)
Exception java.lang.reflect.InaccessibleObjectException: Unable to make public java.lang.Thread$Builder$OfVirtual java.lang.ThreadBuilders$VirtualThreadBuilder.name(java.lang.String,long) accessible: module java.base does not "opens java.lang" to unnamed module @30dae81
adding required java.base;
into the module-info.java
does not seem to change the outcome either:
// src/main/java/module-info.java
module test_20230518_ {
requires java.base;
}
// src/main/java/a/A.java
package a;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.ThreadFactory;
public class A {
public static void main(String[] args) {
ThreadFactory threadFactory = tf();
threadFactory.newThread(() ->
System.out.println("hi from " +
Thread.currentThread().getName()));
}
private static ThreadFactory tf() {
Method[] methods = Thread.class.getMethods();
boolean haveVirtual = Arrays.stream(methods)
.anyMatch(m -> m.getName().equals("ofVirtual") &&
m.getParameterCount() == 0);
if (haveVirtual) {
try {
Object b = Thread.class.getMethod("ofVirtual")
.invoke(null);
b = b.getClass().getMethod("name", String.class, Long.TYPE)
.invoke(b, "prefix-", (long) 1);
b = b.getClass().getMethod("factory")
.invoke(b);
return (ThreadFactory) b;
} catch (Throwable t) {
throw new RuntimeException(t);
}
} else {
return Thread::new;
}
}
}
still produces:
Exception in thread "main" java.lang.RuntimeException: java.lang.IllegalAccessException: class a.A cannot access a member of class java.lang.ThreadBuilders$VirtualThreadBuilder (in module java.base) with modifiers "public volatile"
at a.A.tf(A.java:31)
at a.A.main(A.java:9)
Caused by: java.lang.IllegalAccessException: class a.A cannot access a member of class java.lang.ThreadBuilders$VirtualThreadBuilder (in module java.base) with modifiers "public volatile"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:420)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:709)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at a.A.tf(A.java:26)
... 1 more
class a.A cannot access a member of class java.lang.ThreadBuilders$VirtualThreadBuilder (in module java.base) with modifiers "public volatile"
You can use the Expression class from java.beans package (from java.desktop module)
import java.beans.Expression;
var builder = new Expression(Thread.class, "ofVirtual", null).getValue();
builder = new Expression(builder, "name", new Object[] {"ABC"}).getValue();
var factory = new Expression(builder, "factory", null).getValue();