- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
昨天我在 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
在三个中,我可以通过以下两种方式避免错误:
将 Foo 类更改为公共(public)而不是包私有(private);
将 Something 类的包更改为“package1”(即,字面上与 Foo 和 Bar 类相同,但物理上不同的是 webapp 中定义的 Something 类);
在执行违规代码之前强制加载 Foo 的类:
try {
Class<?> fooClass = Class.forName("package1.Foo");
} catch (ClassNotFoundException e) { }
谁能给我一个清晰的技术解释来证明问题和上述结果是合理的?
当我尝试第三种解决方案时,我实际上使用的是第一种解决方案的 commons.jar(Foo 类是公共(public)的而不是包私有(private)的那个)。很抱歉。
此外,正如我在其中一条评论中指出的那样,我试图在违规代码之前记录 Bar 类和 Something 类的类加载器,结果是:
WebappClassLoader
context: my-web-app
delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@681a9515
好的,我终于解开了一个谜!
在我的一条评论中,我说我无法通过从与 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>
如果我执行“mvn clean package”(作为 Eclipse 运行配置)并从 Eclipse 中运行主程序,我会得到很棒的 IllegalAccessError(酷!);
如果我执行 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 的命令行)中编译的相同问题,目前最新)。
exec:java
运行主程序从控制台 > 错误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 intoClassLoader.getSystemClassLoader()
to find javac classes. If it does not find javac there, it tries to find tools.jar automatically, and creates anURLClassLoader
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 theClassLoader.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 ofToolProvider.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),所以这是一种正常的结果。
确实,这导致了以下结论:
作为引用,在切换到 Maven 的 eclipse 编译器之前,我还尝试了以下方法,但没有取得多大成功:
executable
选项总而言之,JDK 与 Maven 是一致的,而且很可能是一个错误。下面是我发现的一些相关错误报告:
关于java - 通过来自另一个包的公共(public)子类使用包私有(private)类的公共(public)方法引用时出现 IllegalAccessError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36100552/
我有这个 html 代码: HELLO WORLD! X V HELLO WORLD! X V 我想按 X(类关闭)将父 div 的高度更改为 20px 并显示 V(类打开),但在每个 d
在会计应用程序的许多不同实现中,有两种主要的数据库设计方法来保存日志和分类帐数据。 只保留 Journal 信息,然后 Ledger 只是 Journal 的一个 View (因为 journal 总
我想在另一个子里面有一个子, sub a { sub b { } } 我想为每次调用 sub b 创建一个新的 sub a 实例。有没有办法在 Perl 中做到这一点? 当我运行上面的
我有一些代码正在查找重复项并突出显示单元格: Private Sub cmdDups_Click() Dim Rng As Range Dim cel As Range Set Rng = ThisW
可能有一个简单的解决方案,但我很难过。 我有一个包含一个 ID 字段的主表。在两个可能的字段中有一个具有该 ID 的子表。想象一个由选手 A 和选手 B 组成的 double 队。Master 表将有
假设我有一个包含对象的数组: [ { "id": "5a97e047f826a0111b754beb", "name": "Hogwarts", "parentId": "
我正在尝试对 MySQL 数据库表执行一对父/子模型的批量插入,但似乎无法使用标准的 ActiveRecord 功能来完成。所以,我尝试了 activerecord-import gem,但它也不支持
我有一个带有多个子类的父抽象类。最终,我希望通过 GUI 中的进度条显示子类中完成的进度。 我目前所做的,我意识到这是行不通的,是在父类中声明为每个子类将覆盖的虚拟方法的事件方法定义。所以像: pub
是否可以通过键数组在对象中设置变量?例如我有这个对象: var obj = {'outer': {'inner': 'value'} }; 并希望设置由键数组选择的值: var keys = ['ou
我有一个名为 companies 的 MySQL 表,如下所示: +---------+-----------+-----------+ | id_comp | comp_name | id_pare
我正在尝试使用 sublime text 在 sublime text 上的 ionic 上打开我的第一个应用程序。它给了我一个“找不到命令”的错误。如何修复? 我试过这些命令: sudo rm -r
不好意思问,但我正在使用 webapp2,我正在设计一个解决方案,以便更容易定义路由 based on this google webapp2 route function .但这完全取决于能够在子级
我有代表树的数字字符串(我不知道是否有官方名称): 012323301212 上面的例子代表了 2 棵树。根用 0 表示。根的直接子代为“1”,“1”的直接子代为“2”,依此类推。我需要将它们分组到由
是否可以在当前 Activity 之上添加 Activity 。例如,假设我单击一个按钮,然后它将第二个 Activity 添加到当前 Activity 。而第二个 Activity 只覆盖了我当前
我很难思考如何为子资源建模。 以作者的书籍为例。你可以有 N 本书,每本书只有一位作者。 /books GET /books POST /books/id PUT /books/id DELETE 到
有人可以向我解释以下内容(python 2.7) 来自已解析文件的两个字符串数字: '410.9''410.9 '(注意尾随空格) A_LIST = ['410.9 '] '410.9' in '41
背景 在 PowerShell 中构建 hash table 是很常见的通过特定属性快速访问对象,例如以 LastName 为基础建立索引: $List = ConvertFrom-Csv @' I
我真的很难弄清楚如何调用嵌套 Polymer Web 组件的函数。 这是标记: rise-distribution组件有 canPlay我想从 rise-playlist
我写了一个小工具转储(以 dot 格式)一个项目的依赖关系图,其中所有位于同一目录中的文件都聚集在一个集群中。当我尝试生成包含相应图形的 pdf 时,dot开始哭: 命令 dot -Tpdf trim
给定一个 CODE ref,是否可以: 访问该 CODE ref 的解析树 通过指定 CODE ref 的解析树来创建一个新的 CODE ref,该解析树可以包含在 1 中返回的解析树的元素 通常我们
我是一名优秀的程序员,十分优秀!