ChatGPT解决这个技术问题 Extra ChatGPT

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

war should contain jsp,js and servlets.

s
scarba05

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


Hi, I don't suppose you also know how to use the include directive for JSP files in a JAR? Absolute paths to /META-INF/tags doesn't seem to word and even an taglib directive throws errors..
And is there any way to be done with Servlet 2.5 or Tomcat 6?
Yeah, is it possible with Servlet 2.5 or Tomcat 5.5/6 ?
Is it standard? Do other containers such as Weblogic support this?
This question is about Tomcat 7. My solution works for any servlet container implementing the Servlet 3.0 specification. So Tomcat 7+ (but not Tomcat 6 and below). I've not used weblogic for a while but a quick google suggests support from 12c Release 1 (12.1.1).
w
waxwing

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?


It ain't pretty, but I've seen much worse. One problem you might want to address, though: there's no functionality for removing a JSP when the jar containing it is removed.
P
Paul Lysak

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.


Interesting. I am not sure how this will work with Tiles though (probably should have mentioned that we use that). I'm not sure what I would put in my tiles definition file.
Is it possible to expand on the subject?
A
AJPerez

Struts 2 team added a plugin for embedded JSP. Maybe it may be used ad a base.

https://struts.apache.org/plugins/embedded-jsp/


B
Björn Bergenheim

This is a follup to waxwing answer, which I've used becuase we used a server that could not do anything higher then servlet 2.5.

I added a method that removes the files added when the bean is destroyed.

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 http://stackoverflow.com/questions/5013917/can-i-serve-jsps-from-inside-a-jar-in-lib-or-is-there-a-workaround
 */
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();
        }
    }
}

Then I did change the constructor so I could use all java configuration:

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

I hope someone this helps someone!