java.lang.LinkageError: ClassCastException

У меня действительно очень неприятная проблема с TestNG и RESTeasy.

У меня есть класс, который запускает несколько тестов с классом API, который использует инфраструктуру RESTeasy, чтобы выставить себя.

Однако, если я позволю запустить тест с maven (mvn test), я получу следующее исключение:

java.lang.LinkageError: ClassCastException: attempting to castjar:file:/C:/Users/rit/.m2/repository/org/jboss/resteasy/jaxrs-api/2.3.0.GA/jaxrs-api-2.3.0.GA.jar!/javax/ws/rs/ext/RuntimeDelegate.classtojar:file:/C:/Users/rit/.m2/repository/org/jboss/resteasy/jaxrs-api/2.3.0.GA/jaxrs-api-2.3.0.GA.jar!/javax/ws/rs/ext/RuntimeDelegate.class
at javax.ws.rs.ext.RuntimeDelegate.findDelegate(RuntimeDelegate.java:126)
at javax.ws.rs.ext.RuntimeDelegate.getInstance(RuntimeDelegate.java:96)
at javax.ws.rs.core.Response$ResponseBuilder.newInstance(Response.java:394)
at javax.ws.rs.core.Response.status(Response.java:116)
at javax.ws.rs.core.Response.status(Response.java:130)
at com.pd.api.TokenAPI_V1.validateAccessToken(TokenAPI_V1.java:141)
at com.test.pd.api.TokenAPI_V1Test.testIfValidAccessTokenReturnsCorrectHTTPHeadersWhenTokenIsNotFound(TokenAPI_V1Test.java:359)

Тест не делает ничего, кроме вызова метода объекта API, который возвращает объект Response (из RESTeasy). В качестве основы тестирования я использую TestNG.

Метод испытания

@Test
public void testIfValidAccessTokenReturnsCorrectHTTPHeadersWhenTokenIsNotFound() throws InvalidAccessTokenException {
    Mockito.when(tokenService.validateAccessToken(TestConstants.ACCESS_TOKEN)).thenThrow(new InvalidAccessTokenException());

    Response response = tokenAPI_v1.validateAccessToken(TestConstants.ACCESS_TOKEN, TestConstants.USER_AGENT);
    assert "no-store".equals(response.getMetadata().getFirst("Cache-Control"));
    assert "no-cache".equals(response.getMetadata().getFirst("Pragma"));
}

Описание проблемы

Похоже, что среда RESTeasy загружает RuntimeDelegate в другой загрузчик классов. Если я взгляну на исходный код, то в RuntimeDelegate (который охватывает строку 126) есть следующий метод: RuntimeDelegate.java.

Поэтому основным утверждением, связанным с ошибкой, является проверка экземпляра:

if (!(delegate instanceof RuntimeDelegate))

Если я проверяю загрузчик классов экземпляра делегата против загрузчика классов RuntimeDelegate, то я получаю следующий вывод:

delegate.getClass().getClassLoader() -> org.powermock.core.classloader.MockClassLoader@31e46a68

RuntimeDelegate.class.getClassLoader() -> sun.misc.Launcher$AppClassLoader@3c0fabe9

Я знаю, что это, конечно, не работает, но мне интересно, почему материал RESTeasy загружается в MockClassLoader, а не в другой. Тем более, что я не издеваюсь над TokenAPI, который проходит проверку.

Странные факты

Странно то, что когда я запускаю тесты из IntelliJ (я выбираю только запускать все тесты из данного класса, который содержит метод, который выдает ошибку), тогда он проходит. Похоже, это как-то связано с тем, что mvn test запускает все тесты из проекта maven (или, по крайней мере, я так думаю).

2 ответа

Решение

К сожалению, я не могу сказать вам, почему это произошло, но я могу рассказать вам, как обойти эту проблему.

Проблема заключалась в том, что PowerMockito сканировал путь к классам, а также добавил классы RESTeasy (которые находятся в пакете 'javax.ws.*'. Поэтому вышеупомянутый RuntimeDelegate был загружен загрузчиком классов PowerMockito и впоследствии вызвал проблему, что класс сравнивали с одним из другого загрузчика классов.

Чтобы обойти эту проблему, попросите PowerMockito игнорировать пакет javax.ws при сканировании классов:

@PowerMockIgnore({"javax.ws.*"})

Я решил эту проблему, изменив org.glassfish.jersey версия в моем pom.xml

Ниже мой pom.xml

 <dependency>
      <groupId>javax.ws.rs</groupId>
      <artifactId>javax.ws.rs-api</artifactId>
      <version>2.1.1</version>
 </dependency>
 <dependency>
      <groupId>org.glassfish.jersey.core</groupId>
      <artifactId>jersey-client</artifactId>
      <version>2.31</version>
 </dependency>

Я столкнулся с той же проблемой при развертывании моего приложения в JBoss v6.1 - каким-то образом я понял, что проблема была в той же структуре пакета RuntimeDelegate.class, существующего в jesrey-core и jboss 'jaxrs-api jar

Ниже мой pom.xml

    <!-- For Restful Client -->
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-client</artifactId>
        <version>1.8</version>
        <exclusions>
            <exclusion>
                <groupId>com.sun.jersey</groupId>
                <artifactId>resteasy-jaxrs</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- For Jackson (JSON Utility) -->
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>1.8.5</version>
    </dependency>

    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-json</artifactId>
        <version>1.9</version>
         <exclusions>
            <exclusion>
                <groupId>com.sun.jersey</groupId>
                <artifactId>jersey-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

После того, как моя война развернута на JBoss, я просто удалил папку resteasy.deployer из папки jboss' server\default\deployers. Запустил мое приложение, и оно заработало.

Я был бы счастлив, если бы кто-то объяснил, почему эта ошибка была решена таким образом? (Я думаю, что JBoss'излишне поставлялся с jas-файлами resteasy.deployers, когда разработчики могли сами включать jar-файлы в свои приложения) - Не скрывать, что я пытался развернуть свою войну на Tomcat, и она работала без сбоев.

Другие вопросы по тегам