fab*_*cci 21 java maven-3 maven java-8 maven-compiler-plugin
昨天我在Tomcat 8上部署我的Java 8 webapp后遇到了一个有趣的问题.我不想如何解决这个问题,而是更了解为什么会发生这种情况.但是,让我们从头开始.
我有两个类定义如下:
Foo.java
package package1;
abstract class Foo {
public String getFoo() {
return "foo";
}
}
Run Code Online (Sandbox Code Playgroud)
Bar.java
package package1;
public class Bar extends Foo {
public String getBar() {
return "bar";
}
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,它们在同一个包中,并最终在同一个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);
...
}
Run Code Online (Sandbox Code Playgroud)
当它被执行时,我得到:
java.lang.IllegalAccessError: tried to access class package1.Foo from class package2.Something
Run Code Online (Sandbox Code Playgroud)
玩耍和试验我能够通过三种方式避免错误:
将Foo类更改为public而不是package-private;
将Something类的包更改为"package1"(即字面上与Foo和Bar类相同,但物理上不同的是webapp中定义的Something类);
在执行违规代码之前强制加载Foo:
try {
Class<?> fooClass = Class.forName("package1.Foo");
} catch (ClassNotFoundException e) { }
Run Code Online (Sandbox Code Playgroud)
有人可以给我一个明确的技术解释,证明问题和上述结果是正确的吗?
当我尝试第三个解决方案时,我实际上正在使用第一个解决方案的commons.jar(Foo类是public而不是package private).我很抱歉.
此外,正如我的一条评论所指出的,我试图在违规代码之前记录Bar类和Something类的类加载器,两者的结果是:
WebappClassLoader
context: my-web-app
delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@681a9515
Run Code Online (Sandbox Code Playgroud)
好的,我终于解开了其中一个谜团!
在我的一条评论中,我说我无法通过从与commons.jar的 Foo和Bar不同的包中创建的简单主要执行违规代码来复制问题.嗯...... 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>
Run Code Online (Sandbox Code Playgroud)
如果我执行"mvn clean package"(作为Eclipse Run Configuration)并从Eclipse中运行main,我会得到精彩的IllegalAccessError(很酷!);
如果我执行Maven - >更新项目...并从Eclipse中运行main我没有得到任何错误(不酷!).
所以我切换到命令行,我确认了第一个选项:无论违规代码是在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
}
}
Run Code Online (Sandbox Code Playgroud)
我将要在这里亵渎,所以忍受我:可能是Bar :: getFoo方法引用简单地"解析"到Foo :: getFoo方法引用,因为Foo类在Something中不可见(正在Foo包私有),IllegalAccessError被抛出?
A_D*_*teo 19
我能够在Eclipse(Mars,4.5.1)和使用Maven(Maven Compiler Plugin版本3.5.1,目前最新版本)的命令行中重现相同的问题.
exec:java> 错误exec:java从控制台运行主要通道> 无错误直接从命令行编译javac(没有Eclipse,没有Maven,jdk-8u73)并直接从命令行运行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)
Run Code Online (Sandbox Code Playgroud)注意上面的堆栈跟踪,第一个(pre-java-8)调用工作正常,而第二个(基于java-8)抛出异常.
经过一番调查,我发现相关的以下链接:
JDK-8068152错误报告,描述了类似的问题,最重要的是,提到了有关Maven编译器插件和Java的以下内容:
这看起来像是由提供的maven插件引起的问题.提供的maven插件(在"插件"目录中)将"tools.jar"添加到
ClassLoader.getSystemClassLoader(),这就是触发问题.抱歉,我真的没有看到那些可以(或应该)在javac方面做的事.在更多细节中,
ToolProvider.getSystemJavaCompiler()将研究ClassLoader.getSystemClassLoader()查找javac类.如果它在那里找不到javac,它会尝试自动找到tools.jar,并URLClassLoader为tools.jar 创建一个,使用这个类加载器加载javac.当使用此类加载器运行编译时,它使用此类加载器加载类.但是,当插件将tools.jar添加到其中时ClassLoader.getSystemClassLoader(),类将开始由系统类加载器加载.当从同一个包访问一个类但由另一个类加载器加载时,拒绝了包私有访问,从而导致上述错误.由于maven缓存结果,使得这更糟糕ToolProvider.getSystemJavaCompiler(),因为在两个编译之间运行插件仍会导致错误.
(注意:大胆是我的)
Maven编译器插件 - 使用非Javac编译器,描述如何将不同的编译器插入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>
Run Code Online (Sandbox Code Playgroud)
以下内容:
<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>
Run Code Online (Sandbox Code Playgroud)
修复了IllegalAccessError相同代码的问题,不再是问题.但是这样做,我们实际上在这个上下文中删除了Maven和Eclipse之间的差异(使用Eclipse编译器制作Maven),所以这是一种正常的结果.
事实上,这导致了以下结论:
作为参考,我在切换到Maven的eclipse编译器之前尝试了以下但没有太大成功:
executable选项总而言之,JDK与Maven一致,而且很可能是一个bug.下面我发现一些相关的错误报告:
如果包commons.jar和带有package2 的jar由另一个类加载器加载,那么它是不同的运行时包,并且这一事实阻止Something类的方法访问Foo的包成员。请参阅JVM 规范第 5.4.4 章和这个很棒的主题。
我认为除了您已经尝试过的解决方案之外,还有一种解决方案:重写Bar类中的方法getFoo
| 归档时间: |
|
| 查看次数: |
3687 次 |
| 最近记录: |