[英]IllegalAccessError when using a public method reference of a package-private class through a public subclass from another package
昨天我在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而不是package-private;
將Something類的包更改為“package1”(即字面上與Foo和Bar類相同,但物理上不同的是webapp中定義的Something類);
在執行違規代碼之前強制加載Foo:
try { Class<?> fooClass = Class.forName("package1.Foo"); } catch (ClassNotFoundException e) { }
有人可以給我一個明確的技術解釋,證明問題和上述結果是正確的嗎?
當我嘗試第三個解決方案時,我實際上正在使用第一個解決方案的commons.jar(Foo類是public而不是package private)。 我很抱歉。
此外,正如我的一條評論所指出的,我試圖在違規代碼之前記錄Bar類和Something類的類加載器,兩者的結果是:
WebappClassLoader
context: my-web-app
delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@681a9515
好的,我終於解開了其中一個謎團!
在我的一條評論中,我說我無法通過從與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>
如果我執行“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
}
}
我將要在這里褻瀆,所以忍受我:可能是Bar :: getFoo方法引用簡單地“解析”到Foo :: getFoo方法引用,因為Foo類在Something中不可見(正在Foo包私有),IllegalAccessError被拋出?
我能夠在Eclipse(Mars, 4.5.1 )和使用Maven(Maven Compiler Plugin版本3.5.1 ,目前最新版本)的命令行中重現相同的問題。
exec:java
from console> Error exec:java
from console> No Error 直接從命令行編譯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)
注意上面的堆棧跟蹤,第一個(pre-java-8)調用工作正常,而第二個(基於java-8)拋出異常。
經過一番調查,我發現相關的以下鏈接:
JDK-8068152錯誤報告 ,描述了類似的問題,最重要的是,提到了有關Maven編譯器插件和Java的以下內容:
這看起來像是由提供的maven插件引起的問題。 提供的maven插件(在“plugin”目錄中)將“tools.jar”添加到
ClassLoader.getSystemClassLoader()
,這就是觸發問題。 抱歉,我真的沒有看到那些可以(或應該)在javac方面做的事。更詳細地說,
ToolProvider.getSystemJavaCompiler()
將查看ClassLoader.getSystemClassLoader()
以查找javac類。 如果在那里找不到javac,它會嘗試自動查找tools.jar,並為tools.jar創建一個URLClassLoader
,使用這個類加載器加載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>
以下內容:
<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一致,而且很可能是一個bug。 下面我發現一些相關的錯誤報告:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.