Вчера я столкнулся с интересной проблемой после развертывания своего Java 8 webapp на Tomcat 8. Вместо того, чтобы решить эту проблему, мне больше интересно понять, почему это происходит. Но пусть начнется с самого начала.
У меня есть два класса, которые определены следующим образом:
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";
}
}
Как вы можете видеть, они находятся в одном пакете и, в конечном итоге, попадают в одну и ту же банку, позвольте называть ее 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
Играя и экспериментируя, я смог избежать ошибки в three двумя способами:
-
изменение класса Foo для публичного, а не для пакета-private;
-
изменение пакета класса Something как "package1" (т.е. буквально такое же, как классы Foo и Bar, но физически разные - это класс Something, определенный в webapp);
-
принудительно загружает класс Foo перед выполнением кода нарушения:try { Class<?> fooClass = Class.forName("package1.Foo"); } catch (ClassNotFoundException e) { }
Может ли кто-нибудь дать мне четкое, техническое объяснение, которое оправдывает проблему и приведенные выше результаты?
Обновление 1
Когда я попробовал третье решение, я фактически использовал commons.jar первого (тот, где класс Foo является общедоступным, а не private). Мне очень жаль.
Кроме того, как указано в одном из моих комментариев, я попытался зарегистрировать загрузчик классов класса Bar и Something прямо перед кодом нарушения, и результат для обоих был:
WebappClassLoader
context: my-web-app
delegate: false
----------> Parent Classloader:
[email protected]
Обновление 2
Хорошо, я наконец решил одну из тайн!
В одном из моих комментариев я сказал, что не смог реплицировать проблему, выполнив код нарушения с простого основного, созданного в другом пакете, чем Foo и Bar of 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>
-
если я выполняю "mvn clean package" (как Eclipse Run Configuration) и запускаю main из Eclipse, я получаю замечательный IllegalAccessError (cool!);
-
если я выполнил Maven → Проект обновления... и запустил main из Eclipse. Я не получаю никакой ошибки (не круто!).
Итак, я переключился на командную строку, и я подтвердил первый вариант: ошибка постоянно появляется независимо от того, находится ли код нарушения в webapp или в банке. Ницца!
Затем я смог еще больше упростить класс 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), вызывается IllegalAccessError?