How to precompile jsp in a spring boot application?

I use Spring boot, and before that we used Spring with Tomcat. When we used Spring and Tomcat two years ago, we used the maven plugin to precompile jsp. It was really helpful to avoid this compilation for every first visit after deployment.

However, all the maven plugins that we know unload the web.xml file, which lists all the jsps and the associated generated servlets. When loading Spring, it no longer uses web.xml, so this file is ignored.

We still have a compilation and a security belt, but there is a penalty for every first visit on every page.

Does anyone know if jsp can be precompiled in a Spring boot application?

+7
spring spring-boot spring-mvc maven jsp
source share
2 answers

I got a preliminary compilation to work either during server startup (no need to use JspC, it’s easier to build file) and during build (much faster server startup time). I register received servlets dynamically, so you do not need to manually modify any files if you add / remove JSPs.

When starting the server

Use ServletRegistration.Dynamic to register a JSP_SERVLET_CLASS Servlet for each JSP. Use initParameter jspFile to set the JSP file name ( ref )

eg. for SpringBoot in ServletContextInitializer ( ref ):

 @Bean public ServletContextInitializer preCompileJspsAtStartup() { return servletContext -> { getDeepResourcePaths(servletContext, "/WEB-INF/jsp/").forEach(jspPath -> { log.info("Registering JSP: {}", jspPath); ServletRegistration.Dynamic reg = servletContext.addServlet(jspPath, Constants.JSP_SERVLET_CLASS); reg.setInitParameter("jspFile", jspPath); reg.setLoadOnStartup(99); reg.addMapping(jspPath); }); }; } private static Stream<String> getDeepResourcePaths(ServletContext servletContext, String path) { return (path.endsWith("/")) ? servletContext.getResourcePaths(path).stream().flatMap(p -> getDeepResourcePaths(servletContext, p)) : Stream.of(path); } 

At build time

Create Java source files for each JSP and web.xml with their servlet mappings using JspC ( ref ).

Then register them using ServletContext (by parsing web.xml using Tomcat WebXmlParser , e.g. for SpringBoot:

 @Value("classpath:precompiled-jsp-web.xml") private Resource precompiledJspWebXml; @Bean public ServletContextInitializer registerPreCompiledJsps() { return servletContext -> { // Use Tomcat web.xml parser (assume complete XML file and validate). WebXmlParser parser = new WebXmlParser(false, true, true); try (InputStream is = precompiledJspWebXml.getInputStream()) { WebXml webXml = new WebXml(); boolean success = parser.parseWebXml(new InputSource(is), webXml, false); if (!success) { throw new RuntimeException("Error parsing Web XML " + precompiledJspWebXml); } for (ServletDef def : webXml.getServlets().values()) { log.info("Registering precompiled JSP: {} = {} -> {}", def.getServletName(), def.getServletClass()); ServletRegistration.Dynamic reg = servletContext.addServlet(def.getServletName(), def.getServletClass()); reg.setLoadOnStartup(99); } for (Map.Entry<String, String> mapping : webXml.getServletMappings().entrySet()) { log.info("Mapping servlet: {} -> {}", mapping.getValue(), mapping.getKey()); servletContext.getServletRegistration(mapping.getValue()).addMapping(mapping.getKey()); } } catch (IOException e) { throw new RuntimeException("Error registering precompiled JSPs", e); } }; } 

Example Maven configuration for creating and compiling JSP classes and creating precompiled-jsp-web.xml :

 <!-- Needed to get the jasper Ant task to work (putting it in the plugin dependencies didn't work) --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina-ant</artifactId> <version>8.0.32</version> <scope>provided</scope> </dependency> <!-- ... --> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.8</version> <executions> <execution> <id>precompile-jsp-generate-java</id> <!-- Can't be generate-sources because we need the compiled Henry taglib classes already! --> <phase>compile</phase> <goals> <goal>run</goal> </goals> <configuration> <tasks> <echo message="Precompiling JSPs"/> <property name="compile_classpath" refid="maven.compile.classpath"/> <property name="target_dir" value="${project.basedir}/generated-sources/jspc" /> <path id="jspc_classpath"> <path path="${compile_classpath}"/> </path> <typedef resource="org/apache/catalina/ant/catalina.tasks" classpathref="jspc_classpath"/> <mkdir dir="${target_dir}/java"/> <mkdir dir="${target_dir}/resources"/> <jasper validateXml="false" uriroot="${project.basedir}/src/main/webapp" compilertargetvm="1.8" compilersourcevm="1.8" failonerror="true" javaencoding="UTF-8" webXml="${target_dir}/resources/precompiled-jsp-web.xml" outputDir="${target_dir}/java/" > </jasper> <!-- Can't use Maven to compile the JSP classes because it has already compiled the app classes (needed to do that becuase JspC needs compiled app classes) --> <javac srcdir="${target_dir}/java" destdir="${project.build.outputDirectory}" classpathref="jspc_classpath" fork="true"/> <!-- Have to copy the web.xml because process-resources phase has already finished (before compile) --> <copy todir="${project.build.outputDirectory}"> <fileset dir="${target_dir}/resources"/> </copy> </tasks> </configuration> </execution> </executions> </plugin> <!-- Not strictly necessary, because Ant does the compilation, but at least attempts to keep it in sync with Maven --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <executions> <execution> <id>add-precompiled-jsp-java-sources</id> <phase>generate-sources</phase> <goals><goal>add-source</goal></goals> <configuration> <sources> <source>${project.basedir}/generated-sources/jspc/java</source> </sources> </configuration> </execution> <execution> <id>add-precompiled-jsp-resources</id> <phase>generate-resources</phase> <goals><goal>add-resource</goal></goals> <configuration> <resources> <resource> <directory>${project.basedir}/generated-sources/jspc/resources</directory> </resource> </resources> </configuration> </execution> </executions> </plugin> 
+12
source share

Comment for "At the initial stage of the server" described above: the created servlet will be in development mode by default if the application is packaged in an executable bank, so if you use it in working mode, you must also set development = false ++ so that compile jsps:

 reg.setInitParameter("genStringAsCharArray", "true"); reg.setInitParameter("trimSpaces", "true"); reg.setInitParameter("development", "false"); 
+1
source share

All Articles