Как обнаружить виртуальные потоки в 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();
Другие вопросы по тегам