【问题标题】:How to get ZK webfragment working with embedded Jetty 9?如何让 ZK webfragment 与嵌入式 Jetty 9 一起工作?
【发布时间】:2022-01-22 08:09:06
【问题描述】:

这个最小的嵌入式 Jetty 项目正确启动,扫描注释并找到并映射带注释的 TestServlet。

项目结构:

|-src/main/java/test
|  |-Test.java
|-webapp/
|  |-test.zul
|-pom.xml

Test.java:

package test;

import java.io.File;
import java.io.IOException;
import java.net.URI;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;

public class Test {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/test");
        webapp.setBaseResource(Resource.newResource(new File("webapp").getCanonicalFile()));
        // https://www.eclipse.org/jetty/documentation/jetty-9/index.html#configuring-webapps
        // the order is important
        webapp.setConfigurations(new Configuration[] { //
            new WebInfConfiguration(), //
            new WebXmlConfiguration(), //
            new MetaInfConfiguration(), //
            new FragmentConfiguration(), //
            // new EnvConfiguration(), // not needed
            // new PlusConfiguration(), // not needed
            new AnnotationConfiguration(), //
            // new JettyWebXmlConfiguration(), // no jetty-web.xml
        });
        webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*");
        server.setHandler(webapp);
        server.setDumpAfterStart(true);
        server.start();
        java.awt.Desktop.getDesktop().browse(new URI("http://localhost:8080/test/TestServlet")) /* working */;
        java.awt.Desktop.getDesktop().browse(new URI("http://localhost:8080/test/test.zul")) /* not working */;
    }
    
    @WebServlet(urlPatterns = {"/TestServlet"})
    public static final class TestServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().write("Test 1");
        }
    }
}

pom.xml:

<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-annotations</artifactId>
            <version>9.4.30.v20200611</version>
        </dependency>
        <dependency>
            <groupId>org.zkoss.zk</groupId>
            <artifactId>zkbind</artifactId>
            <version>9.6.0.1</version>
        </dependency>
    </dependencies>
</project>

test.zul:

<zk><label value="hello"/></zk>

zk 网络片段似乎以某种方式被“注意到”:

|  +@ org.eclipse.jetty.webFragments.cache = java.util.concurrent.ConcurrentHashMap@bb35baa5{size=30}
|  |  +@ file:///C:/Users/r.hoehener/.m2/repository/org/zkoss/common/zcommon/9.6.0.1/zcommon-9.6.0.1.jar = org.eclipse.jetty.util.resource.EmptyResource@3fc39309
|  |  +@ file:///C:/Users/r.hoehener/.m2/repository/org/eclipse/jetty/jetty-annotations/9.4.30.v20200611/jetty-annotations-9.4.30.v20200611.jar = org.eclipse.jetty.util.resource.EmptyResource@3fc39309
|  |  +@ file:///C:/Users/r.hoehener/.m2/repository/org/eclipse/jetty/jetty-io/9.4.30.v20200611/jetty-io-9.4.30.v20200611.jar = org.eclipse.jetty.util.resource.EmptyResource@3fc39309
|  |  +@ file:///C:/Users/r.hoehener/.m2/repository/org/apache-extras/beanshell/bsh/2.0b6/bsh-2.0b6.jar = org.eclipse.jetty.util.resource.EmptyResource@3fc39309
|  |  +@ file:///C:/Users/r.hoehener/.m2/repository/org/zkoss/zk/zkwebfragment/9.6.0.1/zkwebfragment-9.6.0.1.jar = jar:file:///C:/Users/r.hoehener/.m2/repository/org/zkoss/zk/zkwebfragment/9.6.0.1/zkwebfragment-9.6.0.1.jar!/META-INF/web-fragment.xml
...

但是 test.zul 显示为纯文本。 ZK 引擎未初始化。

有什么想法吗?

编辑:为了捍卫我进行配置的方式:这直接来自 9.x 文档,其中说“您有许多选项可以让 Jetty 使用不同的配置列表。”,包括“直接在 WebAppContext 上设置列表:

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  <Set name="war"><SystemProperty name="jetty.base" default="."/>/webapps/my-cool-webapp</Set>
  <Set name="configurationClasses">
    <Array type="java.lang.String">
      <Item>org.eclipse.jetty.webapp.WebInfConfiguration</Item>
      <Item>org.eclipse.jetty.webapp.WebXmlConfiguration</Item>
      <Item>org.eclipse.jetty.webapp.MetaInfConfiguration</Item>
      <Item>org.eclipse.jetty.webapp.FragmentConfiguration</Item>
      <Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
      <Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
      <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
      <Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>
    </Array>
  </Set>
</Configure>

【问题讨论】:

    标签: java jetty embedded-jetty zk


    【解决方案1】:

    添加配置的方式是:

    Configuration.ClassList classList = Configuration.ClassList.setServerDefault(server);
    classList.addAfter(
                    "org.eclipse.jetty.webapp.FragmentConfiguration",
                    "org.eclipse.jetty.annotations.AnnotationConfiguration");
    

    更新:

    我试过了

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-annotations</artifactId>
            <version>10.0.7</version>
        </dependency>
    

    zul 无需添加任何配置即可工作。

    【讨论】:

    • 谢谢,但根据文档,我这样做的方式也是正确的。对我来说,这种方式看起来更随机,因为最终的顺序不太明显。无论如何,您需要确切地知道在其他现有配置之后或之前添加 AnnotationConfiguration。例如,为什么要在 FragmentConfiguration 之后添加,而不是在 JettyWebXmlConfiguration 之前添加?文档说:“上面的配置类(AnnotationConfiguration)必须在配置列表中的 org.eclipse.jetty.webapp.JettyWebXmlConfiguration 类之前立即插入。”
    • 关于您的更新:我很快尝试了 10.0.7,但得到“无法加载类 org.eclipse.jetty.servlet.listener.ELContextCleaner”。无论如何,我现在标记了问题 jetty-9,因为我现在被困在 Java 8 上。对此感到抱歉。
    • @RetoHöhener 该订单是AnnotationConfiguration 定义的一部分,即:它必须在FragmentConfiguration 之后。根据默认定义,FragmentConfiguration 已经在正确的位置。
    • 就像我在评论中写的那样,文档中的措辞是:'上面的配置类(AnnotationConfiguration)必须立即插入到列表中的 org.eclipse.jetty.webapp.JettyWebXmlConfiguration 类之前配置。'。所以我觉得用addBefore(JettyWebXml..., Annotation...)会更自然
    【解决方案2】:

    首先,不要使用 Jetty 9.4.30,它现在受到一些安全建议的约束。

    见:https://www.eclipse.org/jetty/security_reports.php

    至少使用 Jetty 9.4.44.v20210927。

    接下来,检查您的 Jetty Server 转储中的 zk servlet ...

    • org.zkoss.zk.au.http.DHtmlUpdateServlet
    • org.zkoss.zk.ui.http.DHtmlLayoutServlet

    如果这些存在于您的WebAppContext 转储中,那么您的zkwebfragment-&lt;ver&gt;.jar 已被 Jetty 正确发现并加载。在这一点上,你剩下要做的就是如何使用 zk 技术正确配置你的 zk lib(你可以从这里忽略 Jetty 的具体细节)。

    如果它们不存在,那么首先确保您自己的 web 应用正在使用 Servlet 3.0(在您的 WEB-INF/web.xml 中声明)或更新版本以获得适当的 Web 片段支持(旧的 Servlet 规范不支持 Web 片段)。

    接下来,确保 zkwebfragment-&lt;ver&gt;.jar 出现在 WebApp 类加载器上,因为每个规范都不会从任何其他类加载器加载 Web 片段,甚至不会从应用程序/服务器/容器类加载器加载。

    如果您仍然看不到它们,请返回调整默认的Configuration 列表,而不是您在代码 sn-p 中的硬编码列表。 (您的列表缺少必需的配置,并且成功的顺序错误,请勿更改默认列表,请勿在 webapp 上设置列表,仅更改服务器级别的默认值)。

    问问自己,zk 需要什么? (例如:如果它需要 jndi,那么你也需要 jndi 特定的配置块)。

    如果你没有卡在 Java 8 上,请使用 Jetty 10,因为整个 Configuration 层被重新设计以不再允许错误的配置(实际上旧的 setConfiguration() 方法甚至不存在,只是存在support jars 足以标记您想要该支持并在正确的位置使用正确的父依赖项启用它)。

    【讨论】:

    • 再次,惊人的答案,非常感谢! WebApp 类加载器上不存在 zkwebfragment(什么都没有),所以这就是它当时不工作的原因。让我(仍然)想知道使用 ZK 的标准嵌入式 Jetty 部署应该是什么样子。
    • 感谢有关最新版本的指针。目前我必须使用 Java 8,但不知道那个特定的 9.4.x 版本来自哪里。
    • 在这个最小的例子中根本没有 web.xml。我假设在这种情况下,Jetty 库提供了一个支持 Servlet 3.1 的默认描述符?
    • 配置:我严格按照文档中的顺序,并与服务器转储中的默认配置顺序进行了交叉检查。我省略了 JettyWebXmlConfiguration,因为没有 jetty-web.xml。我还省略了 JNDI 配置(不需要)。如文档中所述,我在 JettyWebXmlConfiguration 之前添加了 AnnotationConfiguration。
    【解决方案3】:

    所以,正如 Joakim 指出的,Servlet Specification 3.1 说:

    如果一个框架希望它的 META-INF/web-fragment.xml 在这样的 增强 Web 应用程序的 web.xml 的方式,框架必须 捆绑在 Web 应用程序的 WEB-INF/lib 目录中。 [...] 换句话说,只有捆绑在网络中的 JAR 文件 应用程序的 WEB-INF/lib 目录,但不是那些更高的 类加载委托链,需要扫描 web-fragment.xml

    因此,我必须确保 zkwebfragments.jar 出现在 WebAppContext 的类路径中。

    我将部署策略更改为如下所示:

    /myapp/
      |-bin/
      |  |- project jar (including annotated servlets and listeners)
      |  |- all maven dependency jars (including jetty and zk libs)
      |-webapp/
      |  |-WEB-INF/
      |  |  |-lib/ (EMTPY!)
      |  |  |-web.xml
      |  |  |-zk.xml
      |  |-index.zul
      |  |-other static resources
      |-data/
         |-application data (docker mounted host volume)
    

    在主类中,将zkwebfragment.jar添加到WebAppContext类路径中:

    Server server = new Server(port);
    WebAppContext webapp = new WebAppContext();
    webapp.setContextPath("/myapp");
    webapp.setBaseResource(Resource.newResource(new File(isDocker ? "/myapp/webapp" : "./webapp").getCanonicalFile()));
    // order important: https://www.eclipse.org/jetty/documentation/jetty-9/index.html#configuring-webapps
    webapp.setConfigurations(new Configuration[] { //
        new WebInfConfiguration(), //
        new WebXmlConfiguration(), //
        new MetaInfConfiguration(), //
        new FragmentConfiguration(), //
        new AnnotationConfiguration(), //
    });
    // servlet spec: fragments are only loaded if they are bundled in WEB-INF/lib
    webapp.setExtraClasspath(Arrays.asList(((URLClassLoader) MyAppMain.class.getClassLoader()).getURLs()).stream().filter(
        u -> u.toString().contains("zkwebfragment")).findAny().get().toString());
    // scan for annotations in the container classpath (AppClassLoader)
    webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*");
    server.setHandler(webapp);
    server.setDumpAfterStart(true);
    server.start();
       
    

    从 IDE 运行主类时,所有项目类和所有 maven 依赖项都会自动添加到 AppClassLoader。

    当作为 docker 容器运行时,应用程序启动时:

    ENTRYPOINT ["java", "-cp", "/myapp/bin/*", "myapp.MyAppMain"]
    

    它还将项目 jar 和所有 maven 依赖 jar 添加到 AppClassLoader。

    通过将所有 jar 从 /myapp/webapp/META-INF/lib/* 移出到 /myapp/bin/*,所有 jar 在类加载器层次结构中只存在一次,这也解决了 ZK replicate resources warnings。除了存在两次的zkwebfragment.jar。码头转储摘录:

    | |  +> WebAppClassLoader{1509563803}@59fa1d9b
    | |  |  +> URLs size=1
    | |  |  |  +> file:/myapp/bin/zkwebfragment-9.6.0.1.jar
    | |  |  +> sun.misc.Launcher$AppClassLoader@2ff4acd0
    

    再往下:

    | +> sun.misc.Launcher$AppClassLoader@2ff4acd0
    |    +> URLs size=52
    |    |  +> file:/myapp/bin/myapp.jar
    |    |  +> file:/myapp/bin/jetty-annotations-9.4.44.v20210927.jar
    |    |  +> file:/myapp/bin/zkwebfragment-9.6.0.1.jar
    |    |  +> ...
    |    +> sun.misc.Launcher$ExtClassLoader@4ee285c6
    |       +> URLs size=9
    |          +> file:/opt/java/openjdk/lib/ext/sunpkcs11.jar
    |          +> ...
    

    如果我使用 Jetty 安装并删除捆绑为标准 WAR 的应用程序,则这些都不是必需的。但我真的很想尝试这种简单的 Java 方法(来自 Jetty 文档):

    Jetty 有一个口号,“不要在 Jetty 中部署你的应用程序,部署 Jetty 在您的应用程序中!”这意味着作为替代 将您的应用程序捆绑为要部署的标准 WAR Jetty,Jetty被设计成一个软件组件,可以 像任何 POJO 一样在 Java 程序中实例化和使用。

    【讨论】:

    • 使用嵌入式 Jetty 可以解决很多问题,但您通常不想将 WAR 和 Servlet 规范的所有规则(包括自动发现)与嵌入式 Jetty 混为一谈相当复杂。大多数嵌入式 Jetty 用户使用 ServletContextHandler 和以编程方式定义/分配的 Servlet,没有自动发现。
    • 帮自己一个忙,现在为您的项目决定一种打包技术,嵌入式或适当的战争。如果你使用嵌入式,请使用ServletContextHandler(跳过类加载器混乱、webapp 初始化混乱、自动发现混乱等)。如果你去打仗,那就全力以赴!不要让您的嵌入式码头服务器代码对 Jetty 以外的任何东西以及服务器本身所需的东西有构建时间依赖性(注意不要将战争作为其依赖项的依赖项包括在内,因为这会极大地弄乱您的类加载器)
    • 甚至可以使用嵌入式码头创建一个可执行的战争,您可以使用java -jar /path/to/my-app.war 来执行它。请参阅github.com/jetty-project/embedded-jetty-live-war/blob/…(注意:Jetty 9、Jetty 10 和 Jetty 11 有分支,因此请使用最适合您的示例)
    • 是的,我过去也(仅)在嵌入式 Jetty 中使用过(仅)以编程方式定义的 Servlet(非常好用且干净)。在这里,我接手了一个 10 多年的项目,我不能轻易摆脱 ZK 框架的依赖。实际上我很高兴结果如何 :) 将 ZK web 片段 jar 添加到 WebAppContext 类路径是唯一剩下的黑客。我可以忍受这一点。再次感谢您的帮助!
    • 我试图解决的另一个问题是人们经常不检查他们的本地测试服务器配置(Tomcat、Jetty...)。或者人们有不同的 IDE,具有不同的服务器支持。所以人们非常不愿意接触这个项目,因为仅仅让它启动所需的努力太大了。对于嵌入式 Jetty 项目,他们所要做的就是运行一个主类。无需额外的 Jetty 或 IDE 知识即可开始使用 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-22
    • 1970-01-01
    • 1970-01-01
    • 2015-05-08
    • 1970-01-01
    • 1970-01-01
    • 2015-07-17
    相关资源
    最近更新 更多