带有 jax-rs 的 Tomee 9 总是 returns 找不到错误 404

Tomee 9 with jax-rs always returns error 404 not found

我正在尝试使用 Java 11、jax-rs 和 Tomee Plume 9 为服务器创建一个简单的 Rest API。
javax.Xjakarta.X 之间多次冲突后,我终于能够编译该项目。 但是当我尝试在 Intellij Idea 上 运行 它时,我的所有请求都出现 404 错误...... 我在日志中没有错误。

这是我的 web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0"
         metadata-complete="true">

    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>fr.theogiraudet.rest</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
</web-app>

以及我的 Rest 资源的摘录,位于包 fr.theogiraudet.rest:

@Path("/pianos")
public class PianoResource {

    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response getAllPianos(@Context UriInfo uriInfo) {
        final var optParams = filter(uriInfo);
        if (optParams.isEmpty())
            return Response.status(Response.Status.BAD_REQUEST).build();

        final var listOpt = dao.getAllPianos(optParams.get());
        if (listOpt.isEmpty())
            return Response.serverError().build();

        return Response.ok().entity(listOpt.get().toArray(new Piano[0])).build();
    }
}

我的请求:GET - http://localhost:8080/api/pianos
应用程序上下文是 /.
在服务器启动时,war 似乎位于和部署得很好,如日志中所示:

10-Aug-2021 21:09:58.897 INFOS [http-nio-8080-exec-4] org.apache.openejb.assembler.classic.Assembler.createApplication Deployed Application(path=E:\Programmation\IntelliJ\Pause Piano - Backend\target\pause-piano-backend-1.0-SNAPSHOT)
10-Aug-2021 21:09:59.188 INFOS [http-nio-8080-exec-4] org.apache.jasper.servlet.TldScanner.scanJars Au moins un fichier JAR a été analysé pour trouver des TLDs mais il n'en contenait pas, le mode "debug" du journal peut être activé pour obtenir une liste complète de JAR scannés sans succès ; éviter d'analyser des JARs inutilement peut améliorer sensiblement le temps de démarrage et le temps de compilation des JSPs
[2021-08-10 09:10:00,169] Artifact Pause Piano - Backend:war: Artifact is deployed successfully
[2021-08-10 09:10:00,169] Artifact Pause Piano - Backend:war: Deploy took 4,904 milliseconds

如果您需要有关代码的更多信息(如 pom.xml),可以在此处找到该项目: https://github.com/Pause-Piano/PausePiano-Backend

tl;博士

您使用的 swagger 版本不支持 jakarta 命名空间。您必须升级到 2.1.7 或更高版本,并将相关的 -jakarta 后缀添加到工件描述符中。此外,您使用的是不受支持的杰克逊,它也不支持 jakarta。您必须切换到 jackson-jakarta-rs-providers(请参阅下面的详细信息)。

详细解释

为了重现部署过程,我通过 tomee:run

从 Maven 中快速将以下插件配置添加到 pom.xml 到 运行 TomEE
        <plugin>
            <groupId>org.apache.tomee.maven</groupId>
            <artifactId>tomee-maven-plugin</artifactId>
            <version>8.0.7</version>
            <configuration>
                <tomeeVersion>9.0.0-M7</tomeeVersion>
                <tomeeClassifier>plume</tomeeClassifier>
                <debug>true</debug>
                <tomeeHttpPort>8282</tomeeHttpPort>
                <debugPort>5005</debugPort>
                <args>-Dfoo=bar</args>
                <config>${project.basedir}/src/test/tomee/conf</config>
                <skipCurrentProject>true</skipCurrentProject>
                <webapps>
                    <webapp>
                        fr.pause-piano:pause-piano-backend:1.0-SNAPSHOT?name=ROOT
                    </webapp>
                </webapps>
            </configuration>
        </plugin>

然后它会尝试部署您的 webapp,它会在控制台输出中打印一些 ClassNotFoundExceptions,表明 javaxjakarta 命名空间之间存在一些问题。

您使用的 swagger 版本不支持 jakarta 命名空间。 official repository on GitHub 状态:

NOTE: Since version 2.1.7 Swagger Core supports also Jakarta namespace, with a parallel set of artifacts with -jakarta suffix, providing the same functionality as the "standard" javax namespace ones. Please check Wiki for more details

这意味着,您必须将代码中的相关工件和后缀升级为:

    <dependency>
        <groupId>io.swagger.core.v3</groupId>
        <artifactId>swagger-jaxrs2-jakarta</artifactId>
        <version>2.1.10</version>
    </dependency>

    <dependency>
        <groupId>io.swagger.core.v3</groupId>
        <artifactId>swagger-jaxrs2-servlet-initializer-v2-jakarta</artifactId>
        <version>2.1.10</version>
    </dependency>

但是,这还不够,因为 webapp 仍在使用不受支持的 jackson 版本,该版本不支持 jakarta 命名空间(参见 GitHub):

(*) NOTE! JAX-RS is the "old" API defined under javax.ws.rs; in 2019 or so, Oracle decided to force a forking of this into "Jakarta" variant under jakarta.ws.ws. As of 2021 most frameworks still use the old API but if you do need/want to use newer one, check out Jakarta-RS provider repo at jackson-jakarta-rs-providers

您必须通过将项目中的 jackson 依赖项替换为

来切换到 jackson-jakarta-rs-providers
    <dependency>
        <groupId>com.fasterxml.jackson.jakarta.rs</groupId>
        <artifactId>jackson-jakarta-rs-json-provider</artifactId>
        <version>2.13.0-rc2</version>
    </dependency>

应用这些更改后,webapp 仍会抛出另一个 ClassNotFoundException。使用 mvn dependency:tree,我们可以看到,更新后的 swagger-jaxrs2-jakarta 引入了 jackson-jaxrs-provider 依赖项。奇怪,不是吗?所以我们需要排除它:

    <dependency>
        <groupId>io.swagger.core.v3</groupId>
        <artifactId>swagger-jaxrs2-jakarta</artifactId>
        <version>2.1.10</version>
        <exclusions>
            <exclusion>
                <groupId>com.fasterxml.jackson.jaxrs</groupId>
                <artifactId>jackson-jaxrs-json-provider</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

更改之后,我们可以通过 运行 宁 mvn clean installmvn tomee:run 再试一次。日志输出记录良好并显示已部署的端点:

31-Aug-2021 14:46:20.894 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints REST Application: http://localhost:8282/sample                          -> fr.theogiraudet.swagger.Swagger@f25176a
31-Aug-2021 14:46:20.903 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints      Service URI: http://localhost:8282/sample/openapi                  -> Pojo io.swagger.v3.jaxrs2.integration.resources.AcceptHeaderOpenApiResource
31-Aug-2021 14:46:20.903 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints               GET http://localhost:8282/sample/openapi                  ->      Response getOpenApiJson(HttpHeaders, UriInfo) throws Exception
31-Aug-2021 14:46:20.903 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints               GET http://localhost:8282/sample/openapi                  ->      Response getOpenApiYaml(HttpHeaders, UriInfo) throws Exception
31-Aug-2021 14:46:20.905 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints      Service URI: http://localhost:8282/sample/openapi.{type:json|yaml} -> Pojo io.swagger.v3.jaxrs2.integration.resources.OpenApiResource            
31-Aug-2021 14:46:20.905 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints               GET http://localhost:8282/sample/openapi.{type:json|yaml} ->      Response getOpenApi(HttpHeaders, UriInfo, String) throws Exception
31-Aug-2021 14:46:20.905 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints      Service URI: http://localhost:8282/sample/pianos                   -> Pojo fr.theogiraudet.rest.PianoResource                                    
31-Aug-2021 14:46:20.905 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints            DELETE http://localhost:8282/sample/pianos                   ->      Response deleteAllPianos()    
31-Aug-2021 14:46:20.906 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints               GET http://localhost:8282/sample/pianos                   ->      Response getAllPianos(UriInfo)
31-Aug-2021 14:46:20.906 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints               GET http://localhost:8282/sample/pianos/{id}              ->      Response getPiano(int)        
31-Aug-2021 14:46:20.906 INFORMATION [main] org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logEndpoints              POST http://localhost:8282/sample/pianos                   ->      Response postPiano(PianoData) 
31-Aug-2021 14:46:20.956 INFORMATION [main] jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke Deployment of web application archive [/home/zowallar/IdeaProjects/PausePiano-Backend/target/apache-tomee/webapps/ROOT.war] has finished in [4.433] ms
31-Aug-2021 14:46:20.968 INFORMATION [main] org.apache.catalina.core.StandardContext.setClassLoaderProperty Unable to set the web application class loader property [clearReferencesRmiTargets] to [true] as the property does not exist.
31-Aug-2021 14:46:20.969 INFORMATION [main] org.apache.catalina.core.StandardContext.setClassLoaderProperty Unable to set the web application class loader property [clearReferencesObjectStreamClassCaches] to [true] as the property does not exist.
31-Aug-2021 14:46:20.969 INFORMATION [main] org.apache.catalina.core.StandardContext.setClassLoaderProperty Unable to set the web application class loader property [clearReferencesObjectStreamClassCaches] to [true] as the property does not exist.
31-Aug-2021 14:46:20.969 INFORMATION [main] org.apache.catalina.core.StandardContext.setClassLoaderProperty Unable to set the web application class loader property [clearReferencesThreadLocals] to [true] as the property does not exist.
31-Aug-2021 14:46:20.989 INFORMATION [main] jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke Starting ProtocolHandler ["http-nio-8282"]
31-Aug-2021 14:46:21.003 INFORMATION [main] jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke Server startup in [4564] milliseconds

但是,调用相关端点会导致另一个异常:

jakarta.servlet.ServletException: Servlet.init() for servlet [fr.theogiraudet.swagger.Swagger] threw exception
    org.apache.tomee.catalina.OpenEJBValve.invoke(OpenEJBValve.java:45)
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    org.apache.tomee.catalina.OpenEJBSecurityListener$RequestCapturer.invoke(OpenEJBSecurityListener.java:97)
    org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:353)
    org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:870)
    org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1696)
    org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    java.base/java.lang.Thread.run(Thread.java:829)

引起
java.lang.IllegalStateException: The resource configuration is not modifiable in this context.
    org.glassfish.jersey.server.ResourceConfig$ImmutableState.register(ResourceConfig.java:248)
    org.glassfish.jersey.server.ResourceConfig$ImmutableState.register(ResourceConfig.java:195)
    org.glassfish.jersey.server.ResourceConfig.register(ResourceConfig.java:428)
    org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:306)
    org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:154)
    org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:347)
    jakarta.servlet.GenericServlet.init(GenericServlet.java:158)
    org.apache.tomee.catalina.OpenEJBValve.invoke(OpenEJBValve.java:45)
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    org.apache.tomee.catalina.OpenEJBSecurityListener$RequestCapturer.invoke(OpenEJBSecurityListener.java:97)
    org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:353)
    org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:870)
    org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1696)
    org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    java.base/java.lang.Thread.run(Thread.java:829)

查看日志揭示了 jackson 的一些问题:

java.lang.NoClassDefFoundError: com/fasterxml/jackson/module/jaxb/JaxbAnnotationIntrospector

总结

看起来,与 jackson 结合使用的相关工具 (swagger) 尚未准备好与 jakarta 命名空间一起使用。如果您不介意使用 9,则可以使用工作工具简单地切换到 8(因为 9 仅包含名称空间更改)。