簡體   English   中英

通過另一個包的公共子類使用包私有類的公共方法引用時出現IllegalAccessError

[英]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

玩耍和試驗我能夠通過三種方式避免錯誤:

  1. 將Foo類更改為public而不是package-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而不是package private)。 我很抱歉。

此外,正如我的一條評論所指出的,我試圖在違規代碼之前記錄Bar類和Something類的類加載器,兩者的結果是:

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

更新2

好的,我終於解開了其中一個謎團!

在我的一條評論中,我說我無法通過從與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>
  1. 如果我執行“mvn clean package”(作為Eclipse Run Configuration)並從Eclipse中運行main,我會得到精彩的IllegalAccessError(很酷!);

  2. 如果我執行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 ,目前最新版本)的命令行中重現相同的問題。

  • 從Eclipse> No Error編譯並運行main
  • 從console / Maven編譯並從Eclipse> Error運行main
  • 從console / Maven編譯並運行main via exec:java from console> Error
  • 從Eclipse編譯並運行主要的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),所以這是一種正常的結果。

事實上,這導致了以下結論:

  • Eclipse Java編譯器與Maven Java編譯器不同,在這種情況下沒什么新東西 ,但這是另一個確認
  • 在這種情況下,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 Compiler使用完全相同的javac,顯式使用executable選項

總而言之,JDK與Maven一致,而且很可能是一個bug。 下面我發現一些相關的錯誤報告:

  • JDK-8029707使用函數使用者調用繼承方法的IllegalAccessError 固定為無法修復(這是完全相同的問題)
  • JDK-8141122IllegalAccessException,通過pub使用對package-private類的方法引用 打開(再次,完全相同的問題)
  • JDK-8143647Javac編譯方法引用,允許結果出現IllegalAccessError 在Java 9中修復(類似的問題,pre-java-8代碼可以正常工作,java-8樣式代碼會中斷)
  • JDK-8138667java.lang.IllegalAccessError:嘗試訪問方法(對於受保護的方法) 打開(類似的問題,編譯正常,但運行時錯誤,非法訪問lambda代碼)。

如果包commons.jar和jar with package2由另一個類加載器加載,那么它是不同的運行時包 ,這一事實阻止了Something類的方法訪問Foo的包成員。 請參閱JVM規范的第5.4.4章和 這個非常棒的主題

我認為除了你已經嘗試過的還有一個解決方案:在Bar類中覆蓋方法getFoo

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM