如何让Jetty扫描class路径下的Maven依赖jar文件?

How can I get Jetty to scan the Maven dependency jar files in the class path?

Eclipse 自动将所有 Maven 依赖项添加到 class 路径。

从 Eclipse 启动时,如何让 Jetty 扫描 class 路径上的 jar 文件以查找 Web 片段?

打包后放入webapp/WEB-INF/lib/*.jar,扫描成功

Server server = new Server(8080);

WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/myapp");
webapp.setBaseResource(Resource.newResource(new File("webapp")));

// 
webapp.setConfigurations(new Configuration[] { //
    new AnnotationConfiguration(), // @WebServlet, @WebListener...
    new WebXmlConfiguration(), // webapp/WEB-INF/web.xml
    new WebInfConfiguration(), // ?
    new MetaInfConfiguration(), // ?
    new FragmentConfiguration(), // e.g. zkwebfragment-9.6.0.1.jar!/META-INF/web-fragment.xml
});

if(RUNNING_FROM_IDE)
{
  // add project classes to classpath (e.g. target/classes)
  webapp.getMetaData().setWebInfClassesDirs(Arrays.asList(Resource.newResource(MyAppMainClass.class.getProtectionDomain().getCodeSource().getLocation())));

  // how to add maven dependencies?
  // webapp.getMetaData().addWebInfJar(???);
}

server.setHandler(webapp);
server.start();

我让它以某种方式工作。仍然想知道是否有更标准的方法来实现这一点:

// launching from IDE: manually add WEB-INF/classes and WEB-INF/lib/*.jar entries
webapp.getMetaData().setWebInfClassesDirs(Arrays.asList(Resource.newResource(MyAppMainClass.class.getProtectionDomain().getCodeSource().getLocation())));
for(URL url : ((URLClassLoader) MyAppMainClass.class.getClassLoader()).getURLs()) {
  if(url.getPath().endsWith(".jar")) {
    webapp.getMetaData().addWebInfJar(Resource.newResource(url));
  }
}

我也找到了MavenWebInfConfiguration in the jetty-maven-plugin。我使用:

((WebAppClassLoader)  webapp.getClassLoader()).addClassPath(...)

不要手动设置配置,这是第一个错误。

你的努力基本上破坏了配置,因为你没有尊重原来的默认配置,或者微妙的订单要求。

这是错误的方式,直接设置 webapp 配置。

webapp.setConfigurations(new Configuration[] { //
    new AnnotationConfiguration(), // @WebServlet, @WebListener...
    new WebXmlConfiguration(), // webapp/WEB-INF/web.xml
    new WebInfConfiguration(), // ?
    new MetaInfConfiguration(), // ?
    new FragmentConfiguration(), // e.g. zkwebfragment-9.6.0.1.jar!/META-INF/web-fragment.xml
});

这是正确的方法,通过调整默认配置列表 在服务器级别。

Configuration.ClassList classlist = Configuration.ClassList
  .setServerDefault(server);
classlist.addBefore(
  "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
  "org.eclipse.jetty.annotations.AnnotationConfiguration");

这应该够了。

您可以在服务器转储上看到默认配置顺序。

server.setDumpAfterStart(true);
server.start();
// and then on the output (see your logger) ...
|  |  |  +> Configurations Servlet Dump WebApp@4c163e3 size=5
|  |  |  |  +> org.eclipse.jetty.webapp.WebInfConfiguration@5e403b4a
|  |  |  |  +> org.eclipse.jetty.webapp.WebXmlConfiguration@5117dd67
|  |  |  |  +> org.eclipse.jetty.webapp.MetaInfConfiguration@5be49b60
|  |  |  |  +> org.eclipse.jetty.webapp.FragmentConfiguration@2931522b
|  |  |  |  +> org.eclipse.jetty.webapp.JettyWebXmlConfiguration@7674b62c
|  |  |  +> Handler attributes Servlet Dump WebApp@4c163e3 size=3

此转储功能很有用,因为它可能会告诉您 $HOME/.m2/repository jar 文件不存在于 Web 应用程序自己的 class 路径中。

|  |  |  +> WebAppClassLoader{Servlet Dump WebApp}@5fb759d6
|  |  |  |  +> URLs size=1
|  |  |  |  |  +> file:/tmp/jetty-0_0_0_0-8080-ROOT_war-_-any-15598896298108484560/webapp/WEB-INF/classes/

如果您在此处没有看到您的 Maven 存储库文件,那么是时候配置您的 WebAppContext.setExtraClasspath(String) 以在启动您的 web 应用程序之前包含它们。

另一个需要注意的功能是用于识别扫描哪些内容(class 文件)的 2 个扫描正则表达式。一种用于网络应用程序 classes,一种用于服务器 classes。这两个都是相关的 用于发现 servlet 规范中的任何内容,无论是带注释的侦听器、带注释的 servlet、web 片段、servlet 容器初始化程序、jsp 级别详细信息、websocket 级别详细信息等)

如果您想配置它们,它们都被设置为 WebAppContext.setAttribute(String key, String value) 上的属性。

默认情况下,这些值为空,表示扫描所有 jar。

// This one controls server/container level discovery.

// An example of limited scanning would be
webappContext.setAttribute(
  "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern"
  ".*/[^/]*servlet-api-[^/]*\.jar$|.*[^/]*jstl-.*\.jar$|.*/[^/]*taglibs.*\.jar$");


// This one controls webapp specific discovery.
webappContext.setAttribute(
  "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern"
  ".*/[^/]*foo-[^/]*\.jar$|.*/[^/]*bar.*\.jar$");

这方面的一个例子是...

您有一个网络应用...

WEB-INF/lib/
           foo-api-1.2.3.jar
           foo-1.2.3.jar
           bar-0.9.jar
           boo-1.0.jar

并定义了 webapp 扫描...

webappContext.setAttribute(
    "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern",
    ".*/.*foo-api-[^/]\.jar$|./.*bar-[^/]\.jar$|./.*wibble[^/]*\.jar$");

然后将匹配并扫描以下文件:

WEB-INF/lib/
           foo-api-1.2.3.jar
           bar-0.9.jar

根据 Joakim 的回答,我现在有:

Server server = new Server(port);
Configuration.ClassList classlist = Configuration.ClassList.setServerDefault(server);
classlist.addAfter(JettyWebXmlConfiguration.class.getName(), AnnotationConfiguration.class.getName());

WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/myapp");
webapp.setBaseResource(Resource.newResource(new File(isRunningFromIde ? "./webapp" : "/path/to/webapp").getCanonicalFile()));    
webapp.setWelcomeFiles(new String[] { "index.html" });
//    webapp.setInitParameter(..., ...));
//    webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", "");
//    webapp.setAttribute("org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern", "");

if(isRunningFromIde)
{
  List<String> extraClassPath = new ArrayList<>();
  // target/classes
  extraClassPath.add(Paths.get(MyAppMain.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toString());
  // maven dependencies
  for(URL url : ((URLClassLoader) MyAppMain.class.getClassLoader()).getURLs()) {
    if(url.getPath().endsWith(".jar")) {
      extraClassPath.add(Paths.get(url.toURI()).toString());
    }
  }
  webapp.setExtraClasspath(extraClassPath.stream().collect(Collectors.joining(";")));
}

server.setHandler(webapp);
server.setDumpAfterStart(true);
server.start();

多亏了服务器转储提示,我想我现在也明白了为什么我得到 。 WebAppClassLoader 和 AppClassLoader 都包含相同的条目。 IDE 由于上面的额外类路径而启动。 docker 容器可能是因为我用 java -cp <webapp>/WEB-INF/lib/* MyAppMain.class.

启动它
|  +> WebAppClassLoader{403716510}@1810399e
|  |  +> URLs size=41
|  |  |  +> file:<maven-repo-or-webapp-dir>/WEB-INF/lib/zk-9.6.0.1.jar
|  |  |  ...
|  |  +> sun.misc.Launcher$AppClassLoader@4e0e2f2a
|  |  |  +> URLs size=41
|  |  |  |  +> file:<maven-repo-or-webapp-dir>/WEB-INF/lib/zk-9.6.0.1.jar
|  |  |  |  ...

尝试 3(也基于 Joakim 的回答):设置 ContainerIncludeJarPattern 以扫描已经在 class 路径上的所有内容。无需任何额外的 class 路径修改。

    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
    // order apparently important
    webapp.setConfigurations(new Configuration[] { //
        new WebInfConfiguration(), //
        new WebXmlConfiguration(), //
        new MetaInfConfiguration(), //
        new FragmentConfiguration(), //
        new AnnotationConfiguration(), //
    });
    webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*");
    server.setHandler(webapp);
    server.setDumpAfterStart(true);
    server.start();

我的其他问题似乎与 ZK 相关,所以我为此开始