gpt4 book ai didi

java - 通过来自另一个包的公共(public)子类使用包私有(private)类的公共(public)方法引用时出现 IllegalAccessError

转载 作者:IT老高 更新时间:2023-10-28 20:53:14 25 4
gpt4 key购买 nike

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

我有两个类定义如下:

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 中定义为依赖项)。

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

package package2;

public class Something {

...

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

...

}

当它被执行时,我得到:

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

三个中,我可以通过以下两种方式避免错误:

  1. 将 Foo 类更改为公共(public)而不是包私有(private);

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

  3. 在执行违规代码之前强制加载 Foo 的类:

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

谁能给我一个清晰的技术解释来证明问题和上述结果是合理的?

更新 1

当我尝试第三种解决方案时,我实际上使用的是第一种解决方案的 commons.jar(Foo 类是公共(public)的而不是包私有(private)的那个)。很抱歉。

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

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

更新 2

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

在我的一条评论中,我说我无法通过从与 commons.jar 的 Foo 和 Bar 不同的包中创建的简单 main 执行有问题的代码来复制问题.嗯...Eclipse (4.5.2) 和 Maven (3.3.3) 在这里骗了我!

有了这个简单的 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 中运行主程序,我会得到很棒的 IllegalAccessError(酷!);

  2. 如果我执行 Maven -> 更新项目...并从 Eclipse 中运行主程序,我不会收到任何错误(不酷!)。

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

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

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 类在某些东西(被 Foo 包私有(private)),抛出 IllegalAccessError?

最佳答案

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

  • 从 Eclipse 编译和运行 main > 没有错误
  • 从控制台/Maven 编译并从 Eclipse 运行 main > 错误
  • 从控制台/Maven 编译并通过 exec:java 运行主程序从控制台 > 错误
  • 从 Eclipse 编译并通过 exec:java 运行 main从控制台 > 无错误
  • 直接使用 javac 从命令行编译(没有 Eclipse,没有 Maven,jdk-8u73)并直接使用 java< 从命令行运行 > 错误

    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)

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

经过一番调查,我发现以下相关链接:

  • JDK-8068152 bug report ,描述了一个类似的问题,最重要的是,提到了有关 Maven 编译器插件和 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.

    (注意:粗体是我的)

  • Maven Compiler Plugin - Using Non-Javac Compilers ,描述了如何将不同的编译器插入 Maven 编译器插件并使用它。

所以,只需从下面的配置切换:

<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),所以这是一种正常的结果。

确实,这导致了以下结论:

  • Eclipse Java 编译器不同于 Maven Java 编译器,nothing new在这种情况下,但这是另一个确认
  • 在这种情况下,Maven Java 编译器存在问题,而 Eclipse Java 编译器则没有。不过,Maven 编译器与 JDK 编译器是一致的。所以它实际上可能是 JDK 上的一个错误,对 Maven 编译器有影响。
  • 使用相同的 Eclipse 编译器制作 Maven 可以解决问题,或者隐藏它。

作为引用,在切换到 Maven 的 eclipse 编译器之前,我还尝试了以下方法,但没有取得多大成功:

  • 更改 Maven 编译器插件版本,从 2.53.5.1
  • 的每个版本
  • 尝试使用 JDK-8u25、JDK-8u60、JDK-8u73
  • 确保 Eclipse 和 Maven 编译器使用完全相同的 javac,明确使用 executable选项

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

  • JDK-8029707 :IllegalAccessError 使用功能性消费者调用继承方法。已修复为不会修复(这是完全相同的问题)
  • JDK-8141122 : IllegalAccessException 使用通过 pub 对包私有(private)类的方法引用。打开(再次,完全相同的问题)
  • JDK-8143647 : Javac 编译允许导致 IllegalAccessError 的方法引用。在 Java 9 中修复(类似问题,pre-java-8 代码可以正常工作,java-8 样式代码会中断)
  • JDK-8138667 : java.lang.IllegalAccessError: 试图访问方法(对于 protected 方法)。打开(类似的问题,编译正常,但对 lambda 代码的非法访问会出现运行时错误)。

关于java - 通过来自另一个包的公共(public)子类使用包私有(private)类的公共(public)方法引用时出现 IllegalAccessError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36100552/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com