Как обнаружить виртуальные потоки в Java 19
Допустим, я хочу выпустить программу, работающую на Java 17, поскольку она широко доступна, но использую отражение, чтобы определить, работаю ли я на виртуальной машине с возможностью создания фабрики потоков черезThread.ofVirtual().name("abc").factory()
. Java запрещает рефлексивный доступ к своим внутренним компонентам, если они неправильно настроены с помощью модулей. Как мне настроить мою программу, чтобы иметь возможность рефлекторного доступа к этому методу? Причиной рефлексивного доступа является продолжение компиляции в байт-код <jdk19, но использование отражения для использования функций jdk19, если они присутствуют. Существует ли комбинация аргументов или содержания, которая может достичь этой цели, или это невозможно?
когда вы попробуете это в jshell, вот что вы получите:
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)
Исключение java.lang.reflect.InaccessibleObjectException: невозможно сделать общедоступным java.lang.Thread$Builder$OfVirtual java.lang.ThreadBuilders$VirtualThreadBuilder.name(java.lang.String,long): модуль java.base не "открывается" java.lang» в безымянный модуль @30dae81
добавлениеrequired java.base;
вmodule-info.java
похоже, не меняет результат:
// 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;
}
}
}
до сих пор производит:
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
класс aA не может получить доступ к члену класса java.lang.ThreadBuilders$VirtualThreadBuilder (в модуле java.base) с модификаторами «public voluty».
2 ответа
Вы можете использовать
Expression
из
java.beans
пакет , если вы не против создать зависимость отjava.desktop
модуль, но стоит подчеркнуть, что он отличается от вашего подхода.
Ваша проблема связана не с модульной системой, а с широко распространенным неправильным использованием Reflection API. Когда вы используетеgetClass()
, вы получаете фактический класс (реализации) объекта, и возможно, что этот класс им не является, и, следовательно, методы, которые вы получаете с помощьюgetClass().getMethod(…)
недоступны, несмотря на переопределение или реализациюpublic
, доступный API.
Вместо того, чтобы пытаться исправить это, позвонивsetAccessible(true)
в методе вам следует попытаться перейти к нужному классу API либо черезgetSuperclass()
илиgetInterfaces()
. В данном конкретном случае мы хотим, чтобы Thread.Builder.OfVirtual
интерфейс , который соответствует объявленному типу возвращаемого значения Thread.ofVirtual()
:
private static ThreadFactory tf() {
Optional<Method> virtual = Arrays.stream(Thread.class.getMethods())
.filter(m -> m.getParameterCount() == 0 && m.getName().equals("ofVirtual"))
.findAny();
if(virtual.isPresent()) {
try {
Method method = virtual.orElseThrow();
Object b = method.invoke(null);
// matches b.getClass().getInterfaces()[0]
// but method.getReturnType() is more reliable
Class<?> factoryClass = method.getReturnType();
b = factoryClass.getMethod("name", String.class, long.class)
.invoke(b, "prefix-", 1L);
return (ThreadFactory)factoryClass.getMethod("factory").invoke(b);
}
catch(ReflectiveOperationException t) {
throw new RuntimeException(t);
}
}
else {
return Thread::new;
}
}
Вы можете использовать класс Expression из пакета java.beans (из модуля java.desktop).
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();