通过另一个包的公共子类使用包私有类的公共方法引用时出现IllegalAccessError [英] IllegalAccessError when using a public method reference of a package-private class through a public subclass from another package

查看:155
本文介绍了通过另一个包的公共子类使用包私有类的公共方法引用时出现IllegalAccessError的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

昨天我在Tomcat 8上部署我的Java 8 webapp后遇到了一个有趣的问题。我不想如何解决这个问题,而是更了解为什么会这样。但是让我们从头开始。

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 happens. But let's start from the beginning.

我有两个类定义如下:

Foo。 java

package package1;

abstract class Foo {

    public String getFoo() {
        return "foo";
    }

}

Bar.java

package package1;

public class Bar extends Foo {

    public String getBar() {
        return "bar";
    }

}

如你所见,它们是在同一个包中,最终,在同一个jar中,我们称之为 commons.jar 。这个jar是我的webapp的依赖(即在我的webapp的pom.xml中定义为依赖)。

As you can see, they are in the same package and, ultimately, end up in the same jar, let's call it commons.jar. This jar is a dependency of my webapp (i.e. as been defined as dependency in my webapp's pom.xml).

在我的webapp中,有一段代码可以做:

In my webapp, there is a piece of code which does:

package package2;

public class Something {

    ...

    Bar[] sortedBars = bars.stream()
                           .sorted(Comparator.comparing(Bar::getBar)
                                             .thenComparing(Bar::getFoo))
                           .toArray(Bar[]::new);

    ...

}

和当它被执行时,我得到:

and when it is executed I get:

java.lang.IllegalAccessError: tried to access class package1.Foo from class package2.Something

玩耍和试验我能够避免错误三种两种方式:

Playing around and experimenting I was able to avoid the error in three two ways:


  1. 将Foo类更改为public而不是package-private;

  1. changing the Foo class to be public instead of package-private;

将Something类的包更改为package1(即字面上与Foo和Bar类相同,但物理上不同的是webapp中定义的Something类);

changing the package of the Something class to be "package1" (i.e. literally the same as the Foo and Bar classes but physically different being the Something class defined in the webapp);

在执行违规代码之前强制加载Foo:

forcing the class-loading of Foo before executing the offending code:

try {
    Class<?> fooClass = Class.forName("package1.Foo");
} catch (ClassNotFoundException e) { }

有人能给我一个明确的技术解释,证明问题和上述结果是正确的吗?

Can someone give me a clear, technical explanation that justifies the issue and the above results?

当我尝试第三个解决方案时,我实际上正在使用第一个解决方案的commons.jar(Foo类公开的那个)包私有)。我很抱歉。

When I tried the third solution I was actually using the commons.jar of the first one (the one where the Foo class is public instead of package private). My bad sorry.

此外,正如我的一条评论所指出的那样,我试图在违规代码之前记录Bar类和Something类的类加载器。两者的结果是:

Moreover, as pointed out in one of my comments, I tried to log the classloader of the Bar class and Something class, right before the offending code and the result for both was:

WebappClassLoader
context: my-web-app
delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@681a9515



更新2



好的,我终于解开了其中一个谜团!

Update 2

Ok, I finally solved one of the mysteries!

在我的一条评论中,我说我无法复制通过从与 commons.jar 的Foo和Bar不同的包中创建的简单主要执行违规代码来解决问题。好吧...... Eclipse(4.5.2)和Maven(3.3.3)在这里欺骗了我!

In one of my comments I said that I wasn't able to replicate the problem by executing the offending code from a simple main created in a different package than Foo and Bar of the commons.jar. Well...Eclipse (4.5.2) and Maven (3.3.3) fooled me here!

这个简单的pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>my.test</groupId>
    <artifactId>commons</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

</project>




  1. 如果我执行mvn clean package(作为Eclipse)运行配置)并从Eclipse中运行main我得到了精彩的IllegalAccessError(很酷!);

  1. if I execute "mvn clean package" (as Eclipse Run Configuration) and run the main from within Eclipse I get the wonderful IllegalAccessError (cool!);

如果我执行Maven - >更新项目...并且从Eclipse中运行main我没有得到任何错误(不酷!)。

if I execute Maven -> Update project... and run the main from within Eclipse I don't get any error (not cool!).

所以我切换到命令行和我确认了第一个选项:无论违规代码是在webapp中还是在jar中,都会始终出现错误。很好!

So I switched to the command-line and I confirmed the first option: the error consistently appears regardless by whether the offending code is in the webapp or in the jar. Nice!

然后,我能够进一步简化Something类并发现一些有趣的东西:

Then, I was able to further simplify the Something class and discovered something interesting:

package package2;

import java.util.stream.Stream;
import package1.Bar;

public class Something {

    public static void main(String[] args) {

        System.out.println(new Bar().getFoo());
        // "foo"

        Stream.of(new Bar()).map(Bar::getFoo).forEach(System.out::println);
        // IllegalAccessError

    }

}

我要在这里亵渎神明,所以请耐心等待:可能是Bar :: getFoo方法引用只是解析到Foo :: getFoo方法引用,因为Foo类不可见在Something(Foo包私有)中,抛出了IllegalAccessError?

I'm about to be blasphemous here so bear with me: could it be that the Bar::getFoo method reference simply get "resolved" to the Foo::getFoo method reference and, since the Foo class is not visible in Something (being Foo package private), the IllegalAccessError is thrown?

推荐答案

我能够在Eclipse中重现相同的问题编译(Mars, 4.5.1 )和使用Maven的命令行(Maven编译器插件版本 3.5.1 ,目前最新版本。)

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).


  • 从Eclipse编译并运行main> 无错误

  • 从console / Maven编译并运行Eclipse的主要内容> 错误

  • 从console / Maven编译并运行主要的 <$来自控制台的c $ c> exec:java 错误

  • 从Eclipse编译并运行主要的通过 exec:java 来自控制台> 无错误

  • 直接使用 javac 从命令行进行编译(否) Eclipse,没有Maven, jdk-8u73 )并直接从命令行运行 java > 错误

  • 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)


注意上面的堆栈跟踪,第一个(pre-java-8)调用工作正常,而第二个(java-8)基于)引发异常。

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错误报告,描述类似问题,最重要的是,提到关于M的以下内容aven编译器插件和Java:

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


这看起来像是由提供的maven插件引起的问题。提供的maven插件(在插件目录中)将tools.jar添加到 ClassLoader.getSystemClassLoader(),这就是触发问题。对于javac方面,我真的没有看到太多可能(或者应该)做的,抱歉。

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.

更多细节, ToolProvider.getSystemJavaCompiler ()将查看 ClassLoader.getSystemClassLoader()以查找javac类。如果在那里找不到javac,它会自动尝试查找tools.jar,并为tools.jar创建一个 URLClassLoader ,使用这个类加载器加载javac。使用此类加载器运行编译时,它使用此类加载器加载类。但是,当插件将tools.jar添加到 ClassLoader.getSystemClassLoader()时,类将开始由系统类加载器加载。 当从同一个包访问一个类但由另一个类加载器加载时,拒绝了包私有访问,从而导致上述错误。 maven缓存 ToolProvider.getSystemJavaCompiler()的结果会使情况更糟,这要归功于在两个编辑之间运行插件仍然会导致错误。

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.

(注意:粗体是我的)

Maven编译器插件 - 使用非Javac编译器,描述如何插入与Maven编译器插件不同的编译器并使用它。

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>

以下内容:

<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>

修正了问题,没有 IllegalAccessError 以上,对于相同的代码。但是这样做,我们实际上在这个上下文中删除了Maven和Eclipse之间的差异(使用Eclipse编译器制作Maven),所以这是正常的结果。

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:


  • Eclipse Java编译器与Maven Java编译器不同,在这种情况下没有新的,但这是另一个确认

  • 在这种情况下,Maven Java编译器存在问题,而Eclipse Java编译器则没有。 Maven编译器与JDK编译器一致。所以它实际上可能是JDK对Maven编译器产生影响的错误。

  • 使用相同的Eclipse编译器使Maven修复问题,或隐藏它。

  • 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.

作为参考,我在切换到eclipse之前尝试了以下但没有太大成功Maven的编译器:

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


  • 更改Maven编译器插件版本,每个版本从 2.5 直到 3.5.1

  • 尝试使用JDK-8u25,JDK-8u60,JDK-8u73

  • 确保Eclipse和Maven编译器使用完全相同javac,明确使用 可执行文件 选项

  • 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

总结一下, JDK与Maven一致,很可能是一个bug。下面我发现一些相关的错误报告:

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 。已修复为无法修复(问题完全相同)

  • JDK-8141122 IllegalAccessException使用对pub-private类的方法引用通过pub 。打开(再次,完全相同的问题)

  • JDK-8143647 Javac编译方法引用,允许结果出现IllegalAccessError 。修复了Java 9(类似的问题,pre-java-8代码可以正常工作,java-8样式代码会中断)

  • JDK-8138667 java.lang.IllegalAccessError:尝试访问方法(for a受保护的方法)。打开(类似的问题,编译正常但比lambda代码上的非法访问的运行时错误)。

  • 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).

这篇关于通过另一个包的公共子类使用包私有类的公共方法引用时出现IllegalAccessError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆