Can I serve JSPs from inside a JAR in lib, or is there a workaround?

I have a web application deployed as a WAR file in Tomcat 7. The application is build as a multi-module project:

  • core - packaged as JAR, contains most of the backend code
  • core-api - packaged as JAR, contains interfaces toward core
  • webapp - packaged as WAR, contains frontend code and depends on core
  • customer-extensions - optional module, packaged as JAR
  • Normally, we can put our JSP files in the webapp project, and reference them relative to the context:

    /WEB-INF/jsp/someMagicalPage.jsp
    

    The question is what we do about JSP files that are specific to the customer-extensions project, that should not always be included in the WAR. Unfortunately, I cannot refer to JSPs inside JAR files, it appears. Attempting classpath:jsp/customerMagicalPage.jsp results in a file not found in the JspServlet, since it uses ServletContext.getResource() .

    Traditionally, we "solved" this having maven unpack the customer-extensions JAR, locate the JSPs, and put them in the WAR when building it. But an ideal situation is where you just drop a JAR in the exploded WAR in Tomcat and the extension is discovered - which works for everything but the JSPs.

    Is there anyway to solve this? A standard way, a Tomcat-specific way, a hack, or a workaround? For example, I've been thinking of unpacking the JSPs on application startup...


    Servlet 3.0 which Tomcat 7 supports includes the ability to package jsps into a jar.

    You need to:

  • place your jsps in META-INF/resources directory of your jar
  • optionally include a web-fragment.xml in the META-INF directory of your jar
  • place the jar in WEB-INF/lib directory of your war
  • You should then be able to reference your jsps in your context. For example if you have a jsp META-INF/resources/test.jsp you should be able reference this at the root of your context as test.jsp


    As a workaround, I created a class that opens up a jar file, finds files matching a certain pattern, and extracts those files to a given location relative to the context path.

    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();
            }
        }
    }
    

    And then I configure it as a bean in my Spring XML:

    <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>
    

    It's not an optimal solution to a really annoying problem. The question now becomes, will the guy who maintains this code come and murder me while I sleep for doing this?


    There is such workaround - you can precompile your JSPs into servlets. So you'll get .class files you can put into JAR and map in web.xml to some URLs.

    链接地址: http://www.djcxy.com/p/74668.html

    上一篇: 无法找到http://java.sun.com/jsf/facelets的标记库描述符

    下一篇: 我可以从lib中的JAR内部提供JSP吗,还是有解决方法?