昨日、Tomcat8にJava 8 webappをデプロイした後、興味深い問題に直面しました。この問題を解決する方法よりも、なぜそれが起こるのかを理解することに興味があります。しかし、最初から始めましょう。
次のように定義された2つのクラスがあります。
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クラスをpackage-privateではなくpublicに変更します。
somethingクラスのパッケージを「package1」に変更します(つまり、文字通りFooクラスとBarクラスと同じですが、webappで定義されているSomethingクラスとは物理的に異なります)。
問題のあるコードを実行する前に、Fooのクラスロードを強制します。
try { Class<?> fooClass = Class.forName("package1.Foo"); } catch (ClassNotFoundException e) { }
問題と上記の結果を正当化する明確で技術的な説明を誰かに教えてもらえますか?
3番目のソリューションを試したとき、実際には最初のソリューション(Fooクラスがパッケージprivateではなくpublicであるもの)のcommons.jarを使用していました。すみません。
さらに、私のコメントの1つで指摘されているように、問題のあるコードの直前に、BarクラスとSomethingクラスのクラスローダーをログに記録しようとしました。
WebappClassLoader
context: my-web-app
delegate: false
----------> Parent Classloader:
Java.net.URLClassLoader@681a9515
さて、私はついに謎の1つを解決しました!
私のコメントの1つで、commons.jar。ええと... 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>
「mvncleanpackage」を(Eclipse実行構成として)実行し、Eclipse内からメインを実行すると、すばらしいIllegalAccessError(クール!)が発生します。
maven-> Update project ...を実行し、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クラスがSomethingに表示されないため( Fooパッケージプライベート)、IllegalAccessErrorがスローされますか?
Eclipse(Mars、4.5.1)とMaven(Mavenコンパイラプラグインバージョン.5.1、最新の瞬間)。
exec:Java
_ コンソールから> エラーを介してメインを実行します。exec:Java
_ コンソールから実行> エラーなしjavac
(Eclipseなし、Mavenなし、jdk-8u7)を使用してコマンドラインから直接コンパイルし、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)
_
上記のスタックトレースに注意してください。最初の(Java-8より前の)呼び出しは正常に機能し、2番目の(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()
に追加すると、クラスはシステムクラスローダーによってロードされ始めます。 同じパッケージからクラスにアクセスするが、別のクラスローダーによってロードされると、パッケージプライベートアクセスが拒否され、上記のエラーが発生します。これは、ToolProvider.getSystemJavaCompiler()
の結果をMavenキャッシュすることでさらに悪化します。そのおかげで、2つのコンパイルの間にプラグインを実行してもエラーが発生します。
(注:太字は私のものです)
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
オプションを明示的に使用して、EclipseとMavenコンパイラがまったく同じjavacを使用していることを確認します要約すると、JDKはMavenと一貫性があり、おそらくバグです。私が見つけたいくつかの関連するバグレポートの下に:
パッケージcommons.jarとpackage2のjarが別のクラスによってロードされた場合-ローダー、それからそれは異なりますランタイムパッケージそしてこの事実は何かFooのパッケージメンバーへのアクセスからのクラス。 JVM仕様の 第5.4.4章 および この素晴らしいトピック を参照してください。
すでに試したことに加えて、もう1つの解決策があると思います:オーバーライドメソッドgetFooinBarクラス