source

JVM 인수 없이 Java 9에서 "불법 반사 액세스" 경고를 숨기려면 어떻게 해야 합니까?

factcode 2022. 9. 3. 13:13
반응형

JVM 인수 없이 Java 9에서 "불법 반사 액세스" 경고를 숨기려면 어떻게 해야 합니까?

방금 Java 9로 서버를 실행하려고 했는데 다음 경고가 떴습니다.

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by io.netty.util.internal.ReflectionUtil (file:/home/azureuser/server-0.28.0-SNAPSHOT.jar) to constructor java.nio.DirectByteBuffer(long,int)
WARNING: Please consider reporting this to the maintainers of io.netty.util.internal.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

싶습니다.--illegal-access=denyJVM j j j j j j j j j j j j예를 들어 다음과 같습니다.

System.setProperty("illegal-access", "deny");

그것을 할 수 있는 방법이 있나요?

JVM 옵션을 사용할 것을 제안하는 모든 관련 답변을 코드에서 해제하고 싶습니다.가능할까요?

명확히 하자면, 제 질문은 이 경고를 코드에서 돌리는 것이지, 유사한 질문에서 언급된 JVM 인수/플래그를 사용하는 것이 아닙니다.

부정 액세스 경고를 비활성화하는 방법이 있지만 이 방법은 권장하지 않습니다.

1. 심플한 어프로치

경고는 오류 이 리다이렉트하기만 .stderr로로 합니다.stdout.

public static void disableWarning() {
    System.err.close();
    System.setErr(System.out);
}

주의:

  • 이 접근법은 오류 스트림과 출력 스트림을 병합합니다.그것은 경우에 따라서는 바람직하지 않을 수 있다.
  • 하는 만으로 리다이렉트 할 수 .System.setErr는 에러 스트림에 IllegalAccessLogger.warningStreamJVM j j j j j j j j j j j j j j j j j j j 。

2. stderr를 변경하지 않는 복잡한 접근법

좋은 소식은 이다sun.misc.UnsafeJDK 9입니다.입니다.IllegalAccessLogger안전하지 않은 API를 사용하여 작업을 수행합니다.

public static void disableWarning() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe u = (Unsafe) theUnsafe.get(null);

        Class cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
        Field logger = cls.getDeclaredField("logger");
        u.putObjectVolatile(cls, u.staticFieldOffset(logger), null);
    } catch (Exception e) {
        // ignore
    }
}

스트림 억제가 필요하지 않고 문서화되지 않았거나 지원되지 않는 API에 의존하지 않는 다른 옵션이 있습니다.Java 에이전트를 사용하면 모듈을 재정의하여 필요한 패키지를 내보내거나 열 수 있습니다.이 경우의 코드는 다음과 같습니다.

void exportAndOpen(Instrumentation instrumentation) {
  Set<Module> unnamed = 
    Collections.singleton(ClassLoader.getSystemClassLoader().getUnnamedModule());
  ModuleLayer.boot().modules().forEach(module -> instrumentation.redefineModule(
        module,
        unnamed,
        module.getPackages().stream().collect(Collectors.toMap(
          Function.identity(),
          pkg -> unnamed
        )),
        module.getPackages().stream().collect(Collectors.toMap(
           Function.identity(),
           pkg -> unnamed
         )),
         Collections.emptySet(),
         Collections.emptyMap()
  ));
}

응용 프로그램이 이름 없는 모듈에 포함되어 있기 때문에 다음과 같이 경고 없이 부정 액세스를 실행할 수 있습니다.

Method method = ClassLoader.class.getDeclaredMethod("defineClass", 
    byte[].class, int.class, int.class);
method.setAccessible(true);

「 」를 Instrumentation인스턴스(instance)는 매우 간단한 Java 에이전트를 작성하고 이를 (클래스 경로가 아닌) 명령줄에 지정할 수 있습니다.-javaagent:myjar.jar에는 . . 음음음음음음음음음음음 contain contain contain contain contain contain contain contain contain contain contain contain.premain을 사용하다

public class MyAgent {
  public static void main(String arg, Instrumentation inst) {
    exportAndOpen(inst);
  }
}

또는 byte-buddy-agent 프로젝트(제가 작성한)에 의해 쉽게 접근할 수 있는 attach API를 사용하여 동적으로 연결할 수 있습니다.

exportAndOpen(ByteBuddyAgent.install());

불법 접속 전에 전화해야 할 정보입니다.이는 JDK 및 Linux VM에서만 사용할 수 있는 반면 다른 VM에서 필요한 경우 명령줄에서 바이트 버디 에이전트를 Java 에이전트로 제공해야 합니다.이는 일반적으로 JDK가 설치되어 있는 테스트 및 개발 시스템에서 자가 접속을 원하는 경우에 편리합니다.

다른 사람들이 지적한 바와 같이 이것은 중간 솔루션일 뿐이지만, 현재 동작으로 인해 로그 크롤러와 콘솔 앱이 고장나는 경우가 많다는 것을 충분히 이해하고 있습니다.그래서 저는 Java 9를 사용하기 위한 단기적인 솔루션으로 이 기능을 사용해 왔고, 오랫동안 아무런 문제가 발생하지 않았습니다.

단, 이 솔루션은 향후의 업데이트에 대해서도 모든 조작과 마찬가지로 견고하며 동적 첨부 파일도 합법적이라는 것이 장점입니다.도우미 프로세스를 사용하여 바이트 버디는 일반적으로 금지된 자기 첨부 파일을 처리합니다.

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
    @SuppressWarnings("unchecked")
    public static void disableAccessWarnings() {
        try {
            Class unsafeClass = Class.forName("sun.misc.Unsafe");
            Field field = unsafeClass.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Object unsafe = field.get(null);

            Method putObjectVolatile = unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
            Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);

            Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
            Field loggerField = loggerClass.getDeclaredField("logger");
            Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
            putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
        } catch (Exception ignored) {
        }
    }

    public static void main(String[] args) {
        disableAccessWarnings();
    }
}

JAVA 11에서는 동작합니다.

나는 네가 원하는 것을 이룰 방법이 없다.지적하신 바와 같이 명령줄 옵션을 추가해야 합니다(--add-opens하지만, 아니다--illegal-access=denyJVM의 기동에 대해 설명하겠습니다.

다음과 같이 기입:

최종 사용자에 대한 추가 지시는 피하는 것이 목표입니다.서버가 설치되어 있는 사용자가 많기 때문에 매우 불편합니다.

보아하니 귀사의 요구사항은 프로젝트가 Java 9에 대한 준비가 되지 않았다는 결론만 남았습니다.Java 9와 완전히 호환되려면 시간이 좀 더 걸린다는 사실을 사용자에게 솔직히 알려야 합니다.발매 후 이른 시간에는 전혀 문제가 없습니다.

위의 답변에서는 언급되지 않은 다른 방법이 있습니다.해킹에 근거는 없습니다.단, classpath에서 실행되고 있는 코드에 대해서만 동작합니다.따라서 Java 9+에서의 실행을 지원해야 하는 라이브러리는 클래스 경로에서 실행되는 한 이 기술을 사용할 수 있습니다.

클래스 패스(즉, 이름 없는 모듈)에서 실행되는 코드가 임의의 모듈의 패키지를 동적으로 열 수 있다는 사실에 기초하고 있습니다(타깃 모듈 자체 또는 이름 없는 모듈에서만 가능합니다).

예를 들어, 이 코드를 지정하면 개인 필드에 액세스하여java.io.Console클래스:

Field field = Console.class.getDeclaredField("formatter");
field.setAccessible(true);

경고를 발생시키지 않기 위해 대상 모듈의 패키지를 모듈로 열어야 합니다.

if (!ThisClass.class.getModule().isNamed()) {
    Console.class.getModule().addOpens(Console.class.getPackageName(), ThisClass.class.getModule());
}

클래스 패스를 실제로 실행하고 있는지 체크도 추가했습니다.

안전하지 않거나 문서화되어 있지 않은 API에 액세스하지 않고 경고를 비활성화할 수 있는 방법을 생각해냈습니다.리플렉션(Reflection)을 사용하여FilterOutputStream::outSystem.err무효로 합니다.

물론 Reflection을 사용하려고 하면 실제로 억제하려는 경고가 발생하지만 동시성을 이용하여 이 문제를 해결할 수 있습니다.

  1. System.err다른 스레드가 여기에 쓸 수 없도록 합니다.
  2. '2'를 합니다.setAccessible out하나는 경고를 표시하려고 할 때 매달려 있지만 다른 하나는 완료됩니다.
  3. 를 합니다.outSystem.errSystem.err이제 두 번째 스레드는 완료되지만 경고는 표시되지 않습니다.
  4. 까지 기다렸다가 하십시오.outSystem.err.

다음 코드는 이 문제를 설명합니다.

public void suppressWarning() throws Exception
{
    Field f = FilterOutputStream.class.getDeclaredField("out");
    Runnable r = () -> { f.setAccessible(true); synchronized(this) { this.notify(); }};
    Object errorOutput;
    synchronized (this)
    {
        synchronized (System.err) //lock System.err to delay the warning
        {
            new Thread(r).start(); //One of these 2 threads will 
            new Thread(r).start(); //hang, the other will succeed.
            this.wait(); //Wait 1st thread to end.
            errorOutput = f.get(System.err); //Field is now accessible, set
            f.set(System.err, null); // it to null to suppress the warning

        } //release System.err to allow 2nd thread to complete.
        this.wait(); //Wait 2nd thread to end.
        f.set(System.err, errorOutput); //Restore System.err
    }
}

는 사용해도 할 수 .--illegal-access는, 2회 에, 「syslog되어 있습니다.

「」의 원래 에, 「」의 상태를 원래대로 되돌립니다.System.err , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , .out「Output Stream」의 「Output Stream」의 「Output Stream」을 참조해 주세요.이것에 의해, 이후의 경고를 필터링 할 수 있습니다.

이것이 나에게 효과가 있었다.

-Djdk.module.illegalAccess=deny

로그 메시지를 폐기하지 않고 리다이렉트하고 싶은 사람이 있는 경우 Java 11에서 이 방법을 사용할 수 있습니다.부정한 액세스 로거가 쓰는 스트림을 대체합니다.

public class AccessWarnings {

  public static void redirectToStdOut() {
    try {

      // get Unsafe
      Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
      Field field = unsafeClass.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      Object unsafe = field.get(null);

      // get Unsafe's methods
      Method getObjectVolatile = unsafeClass.getDeclaredMethod("getObjectVolatile", Object.class, long.class);
      Method putObject = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
      Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
      Method objectFieldOffset = unsafeClass.getDeclaredMethod("objectFieldOffset", Field.class);

      // get information about the global logger instance and warningStream fields 
      Class<?> loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
      Field loggerField = loggerClass.getDeclaredField("logger");
      Field warningStreamField = loggerClass.getDeclaredField("warningStream");

      Long loggerOffset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
      Long warningStreamOffset = (Long) objectFieldOffset.invoke(unsafe, warningStreamField);

      // get the global logger instance
      Object theLogger = getObjectVolatile.invoke(unsafe, loggerClass, loggerOffset);
      // replace the warningStream with System.out
      putObject.invoke(unsafe, theLogger, warningStreamOffset, System.out);
    } catch (Throwable ignored) {
    }
  }
}

하면 돼요.open「」의 module-info.java '만들다'를 만들 수도 있습니다.open module.

예:프로젝트를 직쏘로 이행하는 단계별 5단계 및 6단계 확인

module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
    opens net.javacrumbs.shedlockexample to spring.core, spring.beans, spring.context;
}

open module shedlock.example {
    requires spring.context;
    requires spring.jdbc;
    requires slf4j.api;
    requires shedlock.core;
    requires shedlock.spring;
    requires HikariCP;
    requires shedlock.provider.jdbc.template;
    requires java.sql;
}

언급URL : https://stackoverflow.com/questions/46454995/how-to-hide-warning-illegal-reflective-access-in-java-9-without-jvm-argument

반응형