我应该如何从带有嵌入式码头的 web.xml 加载 servlet?

Posted

技术标签:

【中文标题】我应该如何从带有嵌入式码头的 web.xml 加载 servlet?【英文标题】:How should I load servlets from web.xml with embedded jetty? 【发布时间】:2020-02-02 22:44:00 【问题描述】:

我正在尝试将我的应用程序从 tomcat 迁移到 embedded jetty。我按照一些指南(this、this、this 等)在 Web 模块中创建了一个入口点。生成的文件如下所示:

import org.eclipse.jetty.server.Server
import org.eclipse.jetty.webapp.WebAppContext

object EPILauncher extends App 
  val server: Server = new Server(8080)
  val coolWebApplication = new WebAppContext()
  coolWebApplication.setResourceBase("warehouse/src/main/webapp/")
  coolWebApplication.setContextPath("/api")
  coolWebApplication.setDescriptor("warehouse/src/main/webapp/WEB-INF/web.xml")
  coolWebApplication.setParentLoaderPriority(true)
  server.start()
  System.out.println("Started!")
  server.join()

我的web.xml 文件中有以下 servlet 声明

     <servlet>
        <servlet-name>CustomerApplication</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.xxxx.yyyy.warehouse.web.Root</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>
                io.swagger.jaxrs.listing,
                org.owasp.csrfguard.servlet,
                com.xxxx.yyyy.warehouse.resource
            </param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.scanning.recursive</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
            <param-value>.*(html|css|js|eot|svg|ttf|woff)</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>CustomerApplication</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

com.xxxx.yyyy.warehouse.resource 包含实现,例如:

@Singleton
@Path("/settings")
@Api("/settings")
class SettingsResource @Inject()(config: Config) 
  @GET
  @Path("/version")
  @Produces(Array(MediaType.APPLICATION_JSON))
  def getBackendVersion(@Context servletContext: ServletContext): Response = 
    val manifestStream = servletContext.getResourceAsStream("META-INF/MANIFEST.MF")
    val version: String = Option(manifestStream)
      .map(Utils.using(_)  is =>
        val attrs = new java.util.jar.Manifest(is).getMainAttributes
        val version = attrs.getOrDefault(new Attributes.Name("Specification-Version"), "local-version").toString
        val build = attrs.getOrDefault(new Attributes.Name("Implementation-Version"), "local-build").toString
        s"$version.$build"
      ).getOrElse("local-version.local-build")
    Response.ok(VersionInfo(version)).build()
  

所以,当我运行我的应用并导航到 localhost:8080/api/settings/version 时,我看到的只是:

URI:    /api/settings/version
STATUS: 404
MESSAGE:    Not Found
SERVLET:    -

所以,我认为我没有正确理解某些概念。我应该明确指出我的main 方法,我想使用哪些servlet?它们可以从web.xml 文件自动加载吗? 谢谢。

【问题讨论】:

【参考方案1】:

您可以从 WEB-INF/web.xml 或注解加载 servlet。但是,如果您要嵌入,使用注释通常不是使用嵌入式服务器的最理想方式。

对于 Jetty,要使用 WEB-INF/web.xml 或使用 WebAppContext 的注释。这是一个重量级的组件,具有完整的 Servlet 规则和类加载器行为(re: 类加载器隔离)。

这对于嵌入式服务器来说通常是多余的,许多以这种方式开始的项目最终都会放弃它。

如果您依赖 Annotations,那么您将需要对您拥有的类进行字节码扫描,以一种所有进行字节码扫描的库都可以找到它们的方式公开它们。 Servlet 以自己的方式进行一次字节码扫描,而 JAXRS 层将以自己独特的方式再次进行一次。两者都非常希望通过ServletContext 接口及其关联的描述符信息找到他们的资源,这些信息指向哪里可以找到类。

Jetty 上更传统​​的设置是使用ServletContextHandler。它本质上是 WebAppContext 的子集,没有类加载器隔离,允许您完全在代码中手动声明所有 Servlet 和过滤器及其映射。

这也使得启动非常快(适用于 docker 环境或微服务)。想想亚秒级启动。通过这种方式,只需很少的努力即可实现 100 毫秒的启动。

如果您必须坚持使用战争和 WebAppContext,请考虑对战争配置和资源进行构建时扫描,然后将 jetty-quickstart 元数据添加到您的战争中。那一小段 XML 加上快速启动运行时还可以让您在没有 web 应用发现步骤的情况下快速启动。

如果您想将嵌入式与 webapps 合并,并且可以选择在不重新打包的情况下使用传统意义上的应用程序,那么请考虑设置“实时战争”,这是可以通过java -jar /path/to/myapp.war 直接执行的战争。

过去有关于使用 Live WAR 的答案...

How do I configure embedded jetty to used war file in executable jar as resource https://github.com/jetty-project/embedded-jetty-live-war - 示例显示一个实时可执行的 war 文件,其中包含带有注释扫描的内置 Jetty 服务器。

【讨论】:

谢谢,@joakim-erdfelt!但是,由于其中定义了一些安全过滤器,我将保留web.xml WEB-INF/web.xml 中的所有概念都可以在嵌入式码头使用中针对ServletContextHandler 用代码表示。包括您的 servlet 约束、安全过滤器、mime 类型等。 @JoakimErdfelt Hej!好吧,我们有一个现有的 web.xml 在独立的 tomcat 上运行良好,理想情况下,我们本来希望并且仍然希望能够使用嵌入式码头(或首先工作的 tomcat),但实际上是 1 - 1 映射。理想情况下,我们希望保留保留战争部署替代方案的选项,而不是像现在那样,必须为每个部署重写和使用特定于容器的代码。理想情况下,您应该能够为 Jetty 提供 web.xml,它应该从那里继续找出并设置所需的配置。不像现在,我必须手动配置它.. .. 使用难以正确处理的特定码头代码。我们还必须设置注释扫描并让 Web 套接字正常工作。 web.xml 有一个 listener 标记,在 jetty 中没有对应的标记。有了 tomcat,我们几乎一路走好,虽然它不是 1-1 独立的 tomcat。到目前为止,使用 Jetty,我们甚至无法掌握基础知识。如果我们真的启动并运行它,我们仍然希望保留原始的 web.xml 和 war 选项,尽管我可以看到这可能很难实现。 另外,我们的测试现在只输入 web.xml 并继续启动 Web 服务器。我不明白为什么码头也不能这样做。我不想学习和被孤立在码头。 web.xml 应该是通用的。

以上是关于我应该如何从带有嵌入式码头的 web.xml 加载 servlet?的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式 Jetty web 应用程序上下文/持有者从两个资源库和一个 web.xml (spring secuity) 提供服务

开始在java代码中嵌入码头,如何让它自动重新加载*.jsp/*.css/*.js?

如何使用嵌入式码头添加 servlet 过滤器

如何让 WebApp 在嵌入式码头的类路径上找到应用程序属性?

嵌入式码头热部署

嵌入式 Jetty 中的 Web 应用程序出现错误 404 未找到