我能否从lib中的JAR文件内部提供JSP文件,或者有没有解决方法?

65

我有一个Web应用程序,部署为Tomcat 7中的WAR文件。该应用程序构建为多模块项目:

  • core - 打包为JAR,包含大部分后端代码
  • core-api - 打包为JAR,包含指向核心的接口
  • webapp - 打包为WAR,包含前端代码并依赖于core
  • customer-extensions - 可选模块,打包为JAR

通常,我们可以将JSP文件放在webapp项目中,并相对于上下文引用它们:

/WEB-INF/jsp/someMagicalPage.jsp

问题是,我们该如何处理那些特定于客户扩展项目的JSP文件,这些文件不应该总是包含在WAR中。不幸的是,我似乎无法引用JAR文件中的JSP文件。尝试使用classpath:jsp/customerMagicalPage.jsp会导致在JspServlet中找不到文件,因为它使用了ServletContext.getResource()

传统上,我们通过让maven解压缩customer-extensions JAR文件,找到JSP文件,并在构建WAR时将它们放入其中来“解决”这个问题。但理想的情况是,在Tomcat的已解压缩的WAR中只需放置一个JAR文件,即可发现扩展——这适用于除JSP之外的所有内容。

有没有办法解决这个问题?是否有一种标准方法、Tomcat特定的方法、hack或workaround?例如,我一直在考虑在应用程序启动时解压缩JSP文件……


应该包含JSP、JS和Servlet的war文件。 - Dead Programmer
相关:http://forum.springsource.org/showthread.php?t=58441 - Bozho
5个回答

74

Servlet 3.0可以将JSP打包在一个JAR文件中,Tomcat 7支持此功能。

您需要:

  • 将JSP文件放置在JAR文件的META-INF/resources目录中
  • 可在JAR文件的META-INF目录中包含一个web-fragment.xml文件
  • 将JAR文件放置在WAR文件的WEB-INF/lib目录中

然后您就能够在上下文中引用您的JSP文件。例如,如果您有一个JSP文件META-INF/resources/test.jsp,则可以在上下文根目录下使用test.jsp进行引用。


嗨,我不确定你是否知道如何在JAR中使用include指令来处理JSP文件?绝对路径到/META-INF/tags似乎无法正常工作,即使是taglib指令也会出错。 - shousper
1
有没有办法使用Servlet 2.5或Tomcat 6完成这个任务? - lucasvc
是的,使用Servlet 2.5或Tomcat 5.5/6是可能的吗? - MychaL
这是标准的吗?其他容器,比如Weblogic,是否支持这个? - Amir Pashazadeh
这个问题涉及到Tomcat 7。我的解决方案适用于实现Servlet 3.0规范的任何servlet容器。因此,适用于Tomcat 7+(但不适用于Tomcat 6及以下版本)。我已经有一段时间没有使用weblogic了,但是快速搜索表明从12c Release 1(12.1.1)开始支持。 - scarba05
显示剩余3条评论

6
作为一种解决方法,我创建了一个类,它可以打开一个jar文件,找到与特定模式匹配的文件,并将这些文件提取到相对于上下文路径的给定位置。
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;

import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.ServletContextAware;

/**
 * Allows extraction of contents of a JAR file. All files matching a given Ant path pattern will be extracted into a
 * specified path.
 */
public class JarFileResourcesExtractor implements ServletContextAware {

    private String resourcePathPattern;
    private String jarFile;
    private String destination;
    private ServletContext servletContext;
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * Creates a new instance of the JarFileResourcesExtractor
     * 
     * @param resourcePathPattern
     *            The Ant style path pattern (supports wildcards) of the resources files to extract
     * @param jarFile
     *            The jar file (located inside WEB-INF/lib) to search for resources
     * @param destination
     *            Target folder of the extracted resources. Relative to the context.
     */
    private JarFileResourcesExtractor(String resourcePathPattern, String jarFile, String destination) {
        this.resourcePathPattern = resourcePathPattern;
        this.jarFile = jarFile;
        this.destination = destination;
    }

    /** 
     * Extracts the resource files found in the specified jar file into the destination path
     * 
     * @throws IOException
     *             If an IO error occurs when reading the jar file
     * @throws FileNotFoundException
     *             If the jar file cannot be found
     */
    @PostConstruct
    public void extractFiles() throws IOException {
        try {
            String path = servletContext.getRealPath("/WEB-INF/lib/" + jarFile);
            JarFile jarFile = new JarFile(path);

            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (pathMatcher.match(resourcePathPattern, entry.getName())) {
                    String fileName = entry.getName().replaceFirst(".*\\/", "");
                    File destinationFolder = new File(servletContext.getRealPath(destination));
                    InputStream inputStream = jarFile.getInputStream(entry);
                    File materializedJsp = new File(destinationFolder, fileName);
                    FileOutputStream outputStream = new FileOutputStream(materializedJsp);
                    copyAndClose(inputStream, outputStream);
                }
            }

        }
        catch (MalformedURLException e) {
            throw new FileNotFoundException("Cannot find jar file in libs: " + jarFile);
        }
        catch (IOException e) {
            throw new IOException("IOException while moving resources.", e);
        }
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public static int IO_BUFFER_SIZE = 8192;

    private static void copyAndClose(InputStream in, OutputStream out) throws IOException {
        try {
            byte[] b = new byte[IO_BUFFER_SIZE];
            int read;
            while ((read = in.read(b)) != -1) {
                out.write(b, 0, read);
            }
        } finally {
            in.close();
            out.close();
        }
    }
}

然后我在我的Spring XML中将其配置为bean:

<bean id="jspSupport" class="se.waxwing.util.JarFileResourcesExtractor">
   <constructor-arg index="0" value="jsp/*.jsp"/>
   <constructor-arg index="1" value="myJarFile-1.1.0.jar"/>
   <constructor-arg index="2" value="WEB-INF/classes/jsp"/>
</bean>

这不是一个解决非常烦人问题的最佳方案。现在的问题是,维护这段代码的人会不会因为我做了这个而在我睡觉时杀了我?


2
虽然不太美观,但我见过更糟糕的。不过你可能需要解决一个问题:当包含 JSP 的 jar 被移除时,没有删除 JSP 的功能。 - Mike Baranczak

4

4
有一种解决方法 - 您可以将JSP预编译为servlet。这样,您将获得可放置在JAR文件中并映射到某些URL的.class文件。

有趣。不过我不确定这会如何与Tiles一起使用(可能应该提到我们使用它)。我不知道我会在我的tiles定义文件中放什么。 - waxwing
能否详细阐述一下这个主题? - usr-local-ΕΨΗΕΛΩΝ

1
这是对waxwing答案的补充,我使用它是因为我们使用的服务器无法处理高于servlet 2.5的内容。我添加了一个方法,在bean被销毁时删除添加的文件。
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;

import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.ServletContextAware;


import com.sap.tc.logging.Location;

/**
 * Allows extraction of contents of a JAR file. All files matching a given Ant path pattern will be extracted into a
 * specified path.
 * Copied from https://dev59.com/V2445IYBdhLWcg3wJ298
 */
public class JarFileResourcesExtractor implements ServletContextAware {

    private final transient Location logger = Location.getLocation(JarFileResourcesExtractor.class);

    private String resourcePathPattern;
    private String jarFile;
    private String destination;
    private ServletContext servletContext;
    private AntPathMatcher pathMatcher = new AntPathMatcher();
    private List<File> listOfCopiedFiles = new ArrayList<File>();

    /**
     * Creates a new instance of the JarFileResourcesExtractor
     * 
     * @param resourcePathPattern
     *            The Ant style path pattern (supports wildcards) of the resources files to extract
     * @param jarFile
     *            The jar file (located inside WEB-INF/lib) to search for resources
     * @param destination
     *            Target folder of the extracted resources. Relative to the context.
     */
    public JarFileResourcesExtractor(String resourcePathPattern, String jarFile, String destination) {
        this.resourcePathPattern = resourcePathPattern;
        this.jarFile = jarFile;
        this.destination = destination;
    }


    @PreDestroy
    public void removeAddedFiles() throws IOException{
        logger.debugT("I removeAddedFiles()");
        for (File fileToRemove : listOfCopiedFiles) {
            if(fileToRemove.delete()){
                logger.debugT("Tagit bort filen " + fileToRemove.getAbsolutePath());
            }
        }
    }


    /** 
     * Extracts the resource files found in the specified jar file into the destination path
     * 
     * @throws IOException
     *             If an IO error occurs when reading the jar file
     * @throws FileNotFoundException
     *             If the jar file cannot be found
     */
    @PostConstruct
    public void extractFiles() throws IOException {
        try {
            String path = servletContext.getRealPath("/WEB-INF/lib/" + jarFile);
            JarFile jarFile = new JarFile(path);

            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (pathMatcher.match(resourcePathPattern, entry.getName())) {
                    String fileName = entry.getName().replaceFirst(".*\\/", "");
                    File destinationFolder = new File(servletContext.getRealPath(destination));
                    InputStream inputStream = jarFile.getInputStream(entry);
                    File materializedJsp = new File(destinationFolder, fileName);
                    listOfCopiedFiles.add(materializedJsp);
                    FileOutputStream outputStream = new FileOutputStream(materializedJsp);
                    copyAndClose(inputStream, outputStream);
                }
            }

        }
        catch (MalformedURLException e) {
            throw new FileNotFoundException("Cannot find jar file in libs: " + jarFile);
        }
        catch (IOException e) {
            throw new IOException("IOException while moving resources.", e);
        }
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public static int IO_BUFFER_SIZE = 8192;

    private static void copyAndClose(InputStream in, OutputStream out) throws IOException {
        try {
            byte[] b = new byte[IO_BUFFER_SIZE];
            int read;
            while ((read = in.read(b)) != -1) {
                out.write(b, 0, read);
            }
        } finally {
            in.close();
            out.close();
        }
    }
}

然后我更改了构造函数,以便我可以使用所有的Java配置:

@Bean 
public JarFileResourcesExtractor jspSupport(){
    final JarFileResourcesExtractor extractor = new JarFileResourcesExtractor("WEB-INF/pages/*.jsp","myJarFile-1.1.0.jar","WEB-INF/pages" );
    return extractor;
}

我希望这能帮助到某个人!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接