14

我正在打包一个 Web 应用程序存档 (.war),以便可以通过java -jar webapp.war在主类中使用以下代码启动 Jetty 9 的嵌入式副本在 shell 中启动它:

int port = Integer.parseInt(System.getProperty("port", "80")); // I know this has implications :)
String contextPath = System.getProperty("contextPath", "");
Server server = new Server(port);
ProtectionDomain domain = Deployer.class.getProtectionDomain();
URL location = domain.getCodeSource().getLocation();
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/" + contextPath);
webapp.setWar(location.toExternalForm());
server.setHandler(webapp);
server.start();
server.join();

但是,当包含 JSTL taglib 声明的第一个 JSP 被编译时,我遇到了这个错误:

org.apache.jasper.JasperException: /WEB-INF/html/user/login.jsp(2,62) PWC6188: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:92)
at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:378)
at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:172)
at org.apache.jasper.compiler.TagLibraryInfoImpl.generateTLDLocation(TagLibraryInfoImpl.java:431)
at org.apache.jasper.compiler.TagLibraryInfoImpl.<init>(TagLibraryInfoImpl.java:240)
at org.apache.jasper.compiler.Parser.parseTaglibDirective(Parser.java:502)
at org.apache.jasper.compiler.Parser.parseDirective(Parser.java:582)
at org.apache.jasper.compiler.Parser.parseElements(Parser.java:1652)
at org.apache.jasper.compiler.Parser.parse(Parser.java:185)
at org.apache.jasper.compiler.ParserController.doParse(ParserController.java:244)
at org.apache.jasper.compiler.ParserController.parse(ParserController.java:145)
at org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:212)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:451)
at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:625)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:492)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:378)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:698)
etc...

该 JSP 的前几行如下:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

我环顾四周(这似乎不是一个新问题)并尝试了以下解决方案:

  • 精简我的依赖项并寻找冲突(目前我只依赖jetty-server, jetty-webapp, and jetty-jsp, all version 9.0.4.v20130625
  • 在 webapp 的 web.xml 文件中指定一个<taglib>直接指向 JSTL 的显式映射(从阅读 JSP 规范中得到这个想法)
  • 根据此答案修改服务器类路径
  • 利用 WebAppContext 的方法,例如addServerClasssetParentLoaderPriority

根据Jetty 的文档,使用 JSTL 应该可以正常工作,但我认为嵌入式上下文可能会改变 JSTL 的加载方式并导致其失败。

将不胜感激任何想法或建议。此设置将替换旧设置,该设置在 Windows 上成功完成相同操作,但由于包含导致此错误的旧依赖项而在 Linux 上无法运行。不幸的是,我无法找到不引入上述 JSTL URI 堆栈跟踪的依赖项(groupId org.mortbay.jettyartifactId jsp-2.1-glassfishversion )的快速替换。2.1.v20100127

更新:我找到了一个次优的解决方案。受此线程启发,降级到 Jetty 7现在让我启动并运行。这是个好消息,但令人沮丧的是,如果我以后需要 Jetty 8 或 Jetty 9 独有的任何功能,我将不得不放弃这个部署基础设施。对 Jetty 9 中 JSTL taglib 问题的任何见解仍将不胜感激。

4

8 回答 8

8

所以这里还有另一种解决方案。我在非常相似的问题上苦苦挣扎,只是我有一个单独的war文件和一个简单的嵌入器类,它为我创建了一个Jetty服务器并启动它指向可能的任何war文件。这就是这一切的工作原理。

  1. war 文件中没有任何 tld 库,WEB-INF/lib并且与加载程序迷你应用程序完全分离。

  2. 启动服务器并将其指向任何 war 文件的 Loader 应用程序主类具有以下依赖项(maven):

        <!-- Jetty webapp -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-webapp</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
    
        <!-- With JSP support -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-jsp</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
    

  3. 加载类本身看起来像:

    Server server = new Server(cncPort);
    
    WebAppContext webApp = new WebAppContext();
    
    webApp.setContextPath(applicationContext);
    webApp.setWar(jettyHome + "/" + warFile);
    server.setHandler(webApp);
    
    try {
        server.start();
        server.join();
    } catch (Exception ex) {
        System.out.println("Failed to start server.");
        ex.printStackTrace();
    } 
    
  4. 在我的案例中,生成的包如下所示:

    + EmbedderApp
    |
    +-- lib
      - EmbeddedApp.jar <-- JAR with the embedder class
      - com.sun.el-2.2.0.v201303151357.jar
      - javax.el-2.2.0.v201303151357.jar
      - javax.servlet-3.0.0.v201112011016.jar
      - javax.servlet.jsp-2.2.0.v201112011158.jar
      - javax.servlet.jsp.jstl-1.2.0.v201105211821.jar
      - jetty-http-9.0.6.v20130930.jar
      - jetty-io-9.0.6.v20130930.jar
      - jetty-jsp-9.0.6.v20130930.jar
      - jetty-security-9.0.6.v20130930.jar
      - jetty-server-9.0.6.v20130930.jar
      - jetty-servlet-9.0.6.v20130930.jar
      - jetty-util-9.0.6.v20130930.jar
      - jetty-webapp-9.0.6.v20130930.jar
      - jetty-xml-9.0.6.v20130930.jar
      - org.apache.jasper.glassfish-2.2.2.v201112011158.jar
      - org.apache.taglibs.standard.glassfish-1.2.0.v201112081803.jar
      - org.eclipse.jdt.core-3.8.2.v20130121.jar
    
  5. 我的依赖项只是通过程序集插件添加为

    <dependencySet>
        <outputDirectory>lib</outputDirectory>
        <scope>runtime</scope>
    </dependencySet>
    
  6. 我有一个启动嵌入类的 shell 启动脚本,这让我花了很长时间才弄清楚。

    我使用了嵌入在 jar 中的类路径的清单,并设置我CLASSPATH=<PATH_TO_APP>\lib\EmbeddedApp.jar假设依赖项的重置是我通过清单的类路径的一部分。我遇到了同样无法解决的 URI 错误。

    一旦我添加了更改CLASSPATH脚本中的变量以明确包含所有罐子,它就开始工作了。

    for jar in ${APP_ROOT}/lib/*.jar; do CLASSPATH=$jar:${CLASSPATH}; done 
    

希望这可以节省某人的时间:-)

于 2013-11-21T14:48:42.687 回答
3

在摆弄 a1kmm 的解决方案并最终使用 NullPointers 之后,我注意到我没有在 WebAppContext 上设置类加载器。使用以下行,我不再需要自定义类加载/清单扫描设置。

webAppContext.setClassLoader(new WebAppClassLoader(getClass().getClassLoader(), webAppContext));
于 2013-08-13T15:11:01.630 回答
3

我从 Surefire 测试中启动 Jetty 时遇到了同样的问题;问题是 Jetty 9 不查看除 WEB-INF 之外的任何 jar 文件的清单,这与我编写测试的方式不兼容。

为了解决这个问题,我编写了一些代码来从清单中找到 jar 文件,并将它们放入一个新的中间 URLClassLoader。

这就是我的功能测试设置功能最终让它与 Jetty 9 一起工作的样子:

@Before @SuppressWarnings("unchecked")
public void setUp() throws Exception {

    WebAppContext ctx = new WebAppContext("src/main/webapp", "/");

    Server server = new Server(9000);

    ctx.setServer(server);
    server.setHandler(ctx);

    ctx.preConfigure();

    ctx.addOverrideDescriptor("src/main/webapp/WEB-INF/tests-web.xml");      

    // Replace classloader with a new classloader with all URLs in manifests 
    // from the parent loader bubbled up so Jasper looks at them.
    ClassLoader contextClassLoader = ctx.getClassLoader();
    ClassLoader parentLoader = contextClassLoader.getParent();
    if (contextClassLoader instanceof WebAppClassLoader &&
        parentLoader instanceof URLClassLoader) {
      LinkedList<URL> allURLs =
          new LinkedList<URL>(Arrays.asList(((URLClassLoader)parentLoader).getURLs()));
      for (URL url : ((LinkedList<URL>)allURLs.clone())) {
        try {
          URLConnection conn = new URL("jar:" + url.toString() + "!/").openConnection();
          if (!(conn instanceof JarURLConnection))
            continue;
          JarURLConnection jconn = (JarURLConnection)conn;
          Manifest jarManifest = jconn.getManifest();
          String[] classPath = ((String)jarManifest.getMainAttributes().getValue("Class-Path")).split(" ");

          for (String cpurl : classPath)
            allURLs.add(new URL(url, cpurl));
        } catch (IOException e) {} catch (NullPointerException e) {}
      }

      ctx.setClassLoader(
          new WebAppClassLoader(
              new URLClassLoader(allURLs.toArray(new URL[]{}), parentLoader),
              ((WebAppClassLoader)contextClassLoader).getContext()));
    }

    server.start();
}

我的代码示例位于公共域中 - 您可以在自己的代码中使用它(感谢注明出处,但不是必需的)。

于 2013-07-28T13:57:33.097 回答
2

我遇到了这个确切的问题。我以最不寻常的方式解决了它。

我使用 Maven 作为构建工具,但这就是我构建自我执行 WAR 的方式。

<profile>
        <id>Jetty_9</id>
        <properties>
            <jetty9.version>9.0.4.v20130625</jetty9.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-access</artifactId>
                <version>${logback.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>${logback.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>${slf4j.version}</version>
                <scope>provided</scope>
            </dependency>

            <dependency>
                <groupId>org.eclipse.jetty.orbit</groupId>
                <artifactId>javax.servlet</artifactId>
                <version>3.0.0.v201112011016</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-webapp</artifactId>
                <version>${jetty9.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-plus</artifactId>
                <version>${jetty9.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-jsp</artifactId>
                <version>${jetty9.version}</version>
            </dependency>            
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3.2</version>
                    <configuration>
                        <source>${compileSource}</source>
                        <target>${compileSource}</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.7</version>
                    <executions>
                        <execution>
                            <id>main-class-placement</id>
                            <phase>prepare-package</phase>
                            <configuration>
                                <target>
                                    <move todir="${project.build.directory}/${project.build.finalName}/">
                                        <fileset dir="${project.build.directory}/classes/">
                                            <include name="Main.class"/>
                                        </fileset>
                                    </move>
                                </target>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.6</version>
                    <executions>
                        <execution>
                            <id>jetty-classpath</id>
                            <phase>prepare-package</phase>
                            <goals>
                                <goal>unpack-dependencies</goal>
                            </goals>
                            <configuration>
                                <includeGroupIds>
                                    org.eclipse.jetty,org.slf4j,ch.qos
                                </includeGroupIds>                                    
                                <includeScope>provided</includeScope>
                                <excludes>META-INF/*.SF,META-INF/*.RSA,about.html, about_files/**, readme.txt,
                                    plugin.properties, jetty-dir.css
                                </excludes>
                                <outputDirectory>
                                    ${project.build.directory}/${project.build.finalName}
                                </outputDirectory>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>2.3</version>
                    <configuration>
                        <archive>
                            <manifest>
                                <mainClass>Main</mainClass>
                            </manifest>
                        </archive>
                    </configuration>
                    <executions>
                        <execution>
                            <id>default-war</id>
                            <phase>package</phase>
                            <goals>
                                <goal>war</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>

但就像你自己一样,我遇到了这个错误。在 2 天后谷歌搜索死亡后,我发现了这个 - http://internna.blogspot.co.uk/2011/08/step-by-step-executable-war-files.html

通过为这个切换出 jetty-jsp 依赖项:

 <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jsp-2.1-glassfish</artifactId>
            <version>2.1.v20100127</version>
 </dependency>

这一切都开始神奇地工作了!

截至目前,我无法解释为什么它确实有效。但我很想知道

于 2013-08-16T10:39:02.350 回答
1

可悲的是,到目前为止,没有一个答案对我有用。但我终于找到了解决我的问题的方法。这听起来像是一种黑客行为,而且绝对感觉像是一种黑客行为。

但是,如果我按照我在问题中描述的那样设置好所有东西,直到 JSTL 无法解决,我就可以采取一个步骤,让一切正常,几乎就像魔术一样。

令人畏缩的步骤是将.war文件的扩展名更改为.jar. 一旦我这样做了,JSTL 就可以很好地解决,并且一切正常。

所以现在我制作了一个.war你可以粘贴到 servlet 容器中或者你可以重命名.jar并独立运行的。它适用于 Unix 和 Windows,而我之前这样做的方式在 Unix 上不起作用,因为jsp-2.1-glassfishJames Cook 提到的库中存在一个错误。

我的 pom 中的相关位:

<properties>
    <jetty.version>9.0.5.v20130815</jetty.version>
    <war.class>com.domain.package.DeployWebapp</war.class>
    <war.class.path>com/domain/package</war.class.path>
</properties>    

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>${jetty.version}</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-webapp</artifactId>
    <version>${jetty.version}</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-jsp</artifactId>
    <version>${jetty.version}</version>
    <scope>provided</scope>
</dependency>

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <id>main-class-placement</id>
            <phase>prepare-package</phase>
            <configuration>
                <tasks>
                    <move todir="${project.build.directory}/${project.artifactId}-${project.version}/${war.class.path}">
                    <fileset dir="${project.build.directory}/classes/${war.class.path}">
                        <include name="DeployWebapp.class" />
                    </fileset>
                </move>
                        </tasks>
                </configuration>
                <goals>
                    <goal>run</goal>
                </goals>
            </execution>
    </executions>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.3</version>
    <executions>
        <execution>
            <id>jetty-classpath</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>unpack-dependencies</goal>
            </goals>
            <configuration>
                <includeGroupIds>org.eclipse.jetty,javax.servlet,javax.el,org.glassfish.web,org.eclipse.jetty.orbit,org.ow2.asm,javax.annotation</includeGroupIds>
                <outputDirectory>
                    ${project.build.directory}/${project.artifactId}-${project.version}
                </outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <attachClasses>true</attachClasses>
        <archiveClasses>true</archiveClasses>
        <archive>
            <manifest>
                <mainClass>${war.class}</mainClass>
            </manifest>
        </archive>
        <packagingExcludes>META-INF/*.SF</packagingExcludes>
        <packagingExcludes>META-INF/*.DSA</packagingExcludes>
        <packagingExcludes>META-INF/*.RSA</packagingExcludes>
    </configuration>
</plugin>
于 2014-06-10T19:07:54.080 回答
1

我刚刚在您使用时使用 ta taglib 创建了简单的 jsp。然后创建了服务器应用程序,它可以工作。所以,我认为导致问题的不是标签库。

public static void main(String[] args) throws Exception {
    Server server = new Server(8680);
    HandlerCollection handlers = new HandlerCollection();
    server.setHandler(handlers);
    ContextHandlerCollection chc = new ContextHandlerCollection();
    handlers.addHandler(chc);
    WebAppContext webapp = new WebAppContext();
    webapp.getInitParams().put("org.eclipse.jetty.servlet.Default.useFileMappedBuffer",    
    "false");
    webapp.setContextPath("/WebAppMng01");
    //URL url = ManagedServer.class.getResource("/WebAppMng01/build/web");
    URL url = ManagedServer.class.getResource("/WebAppMng01/dist/WebAppMng01.war");
    if (url != null) {
        webapp.setWar(url.toExternalForm());
        chc.addHandler(webapp);
    }
    server.start();
    server.join();
}   
于 2013-07-24T22:09:39.010 回答
0
WebAppContext context = new WebAppContext();      
final URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
if (url != null) {
    context.getMetaData().addWebInfJar(JarResource.newResource(url));
}

将 fat-jar 添加为 WEB-INF jar,让MetaInfConfiguration找到 *.tld 文件。

参考:

于 2015-04-28T15:44:51.750 回答
0

嵌入式 jetty 9 似乎不喜欢自动使用主可执行 jar 文件中的任何类路径条目。这包括 taglib 库。将类路径条目直接添加到 webappclassloader 似乎也不起作用。无论出于何种原因,类路径条目都必须添加到 webappclassloader 的父级。

简单的解决方案对我不起作用:

    webAppContext.setClassLoader(new WebAppClassLoader(getClass().getClassLoader(), webAppContext));

但是手动扫描清单确实如此。我重写了上面的扫描示例,只是我可以看到发生了什么,并尝试了我将在此处包含的不同内容。

//========== create WebAppClassloader with a new parent classloader with all URLs in manifests=================== 
    //search for any secondary class path entries
    Vector<URL> secondayClassPathURLVector = new Vector<>();
    ClassLoader parentClassLoader = this.getClass().getClassLoader();
    if(parentClassLoader instanceof URLClassLoader)
    {
        URL[] existingURLs = ((URLClassLoader)parentClassLoader).getURLs(); 
        for (URL parentURL : existingURLs)
        {
            //if it doesn't end in .jar, then it's probably not going to have Manifest with a Class-Path entry 
            if(parentURL.toString().endsWith(".jar"))
            {
                JarURLConnection jarURLConnection = (JarURLConnection) new URL("jar:" + parentURL.toString() + "!/").openConnection();
                Manifest jarManifest = jarURLConnection.getManifest();
                String classPath = jarManifest.getMainAttributes().getValue("Class-Path");
                if(classPath != null)
                {
                    //Iterate through all of the class path entries and create URLs out of them                                                
                    for (String part : classPath.split(" "))
                    {
                        //add our full path to the jar file to our classpath list
                        secondayClassPathURLVector.add(new URL(parentURL,part));                     
                    }
                }
            }                
        }
    }
    //use our class path entries to create a parent for the webappclassloader that knows how to use or referenced class paths
    URLClassLoader internalClassPathUrlClassLoader = new URLClassLoader(secondayClassPathURLVector.toArray(new URL[secondayClassPathURLVector.size()]), parentClassLoader);
    //create a new webapp class loader as a child of our  internalClassPathUrlClassLoader. For whatever reason Jetty needs to have a WebAppClassLoader be it's main class loader,
    //while all of our classpath entries need to be added to the parent class loader   
    WebAppClassLoader webAppClassLoader = new WebAppClassLoader(internalClassPathUrlClassLoader,context);
    context.setClassLoader(webAppClassLoader);

这应该替换 Embedded-jetty-jsp 示例,该示例由于任何原因都不起作用,但是应该,因为任何类路径条目都应该自动包含在类路径中。然而,它确实说 JSP 需要一个非系统类加载器。所以我只能假设 JSP 一旦到达系统类路径加载器就会停止扫描类路径。这就是为什么我们必须有效地更换它。

于 2015-03-13T16:30:04.387 回答