IllegalAccessError when using a public method reference of a package-private class through a public subclass from another package

后端 未结 2 1928
梦如初夏
梦如初夏 2020-12-15 17:58

Yesterday I faced an interesting issue after deploying my Java 8 webapp on Tomcat 8. Rather than how to solve this issue I\'m more interested in understanding why that happe

相关标签:
2条回答
  • 2020-12-15 18:45

    I was able to reproduce the same issue compiling in Eclipse (Mars, 4.5.1) and from command line using Maven (Maven Compiler Plugin version 3.5.1, the latest at the moment).

    • Compiling and running the main from Eclipse > No Error
    • Compiling from console/Maven and running the main from Eclipse > Error
    • Compiling from console/Maven and running the main via exec:java from console > Error
    • Compiling from Eclipse and running the main via exec:java from console > No Error
    • Compiling from command line directly with javac (no Eclipse, no Maven, jdk-8u73) and running from command line directly with java > Error

      foo
      Exception in thread "main" java.lang.IllegalAccessError: tried to access class com.sample.package1.Foo from class com.sample.package2.Main   
      at com.sample.package2.Main.lambda$MR$main$getFoo$e8593739$1(Main.java:14)   
      at com.sample.package2.Main$$Lambda$1/2055281021.apply(Unknown Source)   
      at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)   
      at java.util.stream.Streams$StreamBuilderImpl.forEachRemaining(Unknown Source)   
      at java.util.stream.AbstractPipeline.copyInto(Unknown Source)   
      at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)   
      at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)   
      at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)   
      at java.util.stream.AbstractPipeline.evaluate(Unknown Source)   
      at java.util.stream.ReferencePipeline.forEach(Unknown Source)   
      at com.sample.package2.Main.main(Main.java:14)
      

    Note the stacktrace above, the first (pre-java-8) invocation works fine while the second (java-8 based) throws an exception.

    After some investigation, I found relevant the following links:

    • JDK-8068152 bug report, describing a similar issue and, above all, mentioning the following concerning the Maven Compiler Plugin and Java:

      This looks like a problem induced by the provided maven plugin. The provided maven plugin (in the "plugin" directory) adds "tools.jar" to the ClassLoader.getSystemClassLoader(), and this is triggering the problem. I don't really see much that could (or should) be done on the javac side, sorry.

      In more details, ToolProvider.getSystemJavaCompiler() will look into ClassLoader.getSystemClassLoader() to find javac classes. If it does not find javac there, it tries to find tools.jar automatically, and creates an URLClassLoader for the tools.jar, loading the javac using this class loader. When compilation runs using this class loader, it loads the classes using this classloader. But then, when the plugins adds tools.jar to the ClassLoader.getSystemClassLoader(), the classes will begin to be loaded by the system classloader. And package-private access is denied when accessing a class from the same package but loaded by a different classloader, leading to the above error. This is made worse by maven caching the outcomes of ToolProvider.getSystemJavaCompiler(), thanks to which running the plugin in between two compilations still leads to the error.

      (NOTE: bold is mine)

    • Maven Compiler Plugin - Using Non-Javac Compilers, describing how you can plug a different compiler to the Maven Compiler Plugin and use it.

    So, simply switching from the configuration below:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
    

    To the following:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <compilerId>eclipse</compilerId>
        </configuration>
        <dependencies>
            <dependency>
                <groupId>org.codehaus.plexus</groupId>
                <artifactId>plexus-compiler-eclipse</artifactId>
                <version>2.7</version>
            </dependency>
        </dependencies>
    </plugin>
    

    Fixed the issue, no IllegalAccessError any more, for the same code. But doing so, we actually removed the diff between Maven and Eclipse in this context (making Maven using the Eclipse compiler), so it was kind of normal result.

    So indeed, this leads to the following conclusions:

    • The Eclipse Java compiler is different than the Maven Java Compiler, nothing new in this case, but that's yet another confirmation
    • The Maven Java compiler in this case has an issue, while the Eclipse Java compiler has not. The Maven Compiler is coherent with the JDK compiler though. So it might actually be a bug on the JDK having effect on the Maven Compiler.
    • Making Maven using the same Eclipse compiler fixes the issue, or hides it.

    For reference, I tried also the following without much success before switching to the eclipse compiler for Maven:

    • Changing Maven Compiler Plugin version, every version from 2.5 till 3.5.1
    • Trying with JDK-8u25, JDK-8u60, JDK-8u73
    • Making sure Eclipse and Maven Compiler were use exactly the same javac, explicitly using the executable option

    To summarize, the JDK is coherent with Maven, and it is most probably a bug. Below some related bug reports I found:

    • JDK-8029707: IllegalAccessError using functional consumer calling inherited method. Fixed as Won't Fix (it was exactly the same issue)
    • JDK-8141122: IllegalAccessException using method reference to package-private class via pub. Open (again, exactly the same issue)
    • JDK-8143647: Javac compiles method reference that allows results in an IllegalAccessError. Fixed in Java 9 (similar issue, pre-java-8 code would work fine, java-8 style code would break)
    • JDK-8138667: java.lang.IllegalAccessError: tried to access method (for a protected method). Open (similar issue, compilation fine but than runtime error for illegal access on lambda code).
    0 讨论(0)
  • 2020-12-15 18:49

    If packages commons.jar and jar with package2 are loaded by another class-loader, then it is different runtime packages and this fact preventing methods of Something class from access to package members of Foo. See chapter 5.4.4 of JVM spec and this awesome topic.

    I think there is one more solution in addition to what you already tried: override method getFoo in Bar class

    0 讨论(0)
提交回复
热议问题