When I must pre-compile JSP with tomcat, it’s usually pain because it takes a lot of time. Let’s see How compile this faster.
How to compile JSP
If I must compile for tomcat, I usually take this Maven plugin : https://github.com/leonardehrenfried/jspc-maven-plugin I take this plugin because I think it’s pretty simple, and it give availabilty to add includes/excludes of jsp. This is the Maven config to do the job in my War project (we see that I have a tomcat 8 project) :
<plugin>
<groupId>de.mytoys.maven.plugins</groupId>
<artifactId>jspc-maven-plugin</artifactId>
<version>1.1.0</version>
<configuration>
<webAppSourceDirectory>${project.build.directory}/${project.artifactId}-${project.version}</webAppSourceDirectory>
<webXml>${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/web.xml</webXml>
<generatedClasses>${project.build.directory}/jspc</generatedClasses>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.5.4</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.6.1</version>
</dependency>
</dependencies>
</plugin>
Some problems of this plugin
If we have many jsp (> 500) the plugin can take lot of time to compile all files. (see the benchmark after) The other problem is this plugin will stop and fail at the first jsp who contains error. And even at the first error in the jsp! If a jsp contains 5 errors, we must run 5 times the plugin to discover all. So if a big project contains multiples errors, we must run may times the plugin and so many times the full compilation of all jsp.
First fix : performance
In order to compile a big project faster, I propose different things
Give all jsp to Jasper JSPC
In version 1.1.0 of the plugin, it takes all of the jsp and call execute JSPC for each of them. This is the code :
for (String fileName : jspFiles) {
jspc.setJspFiles(fileName);
getLog().info("Compiling " + fileName);
jspc.execute();
}
Maybe it’s an optimization for Tomcat 6.X, but I think with Tomcat 8.X it’s slower than giving all JSP directly to JSPC.execute(); See pull request https://github.com/leonardehrenfried/jspc-maven-plugin/pull/1 to fix this.
Execute compilation in parallel
To compile faster we need to compile in parallel. Indeed when we launch JavaMissionControl for example, we see the CPU is 20%-30% usage when compiling. To increase CPU usage, we need to execute jspc in different thread. See pull request https://github.com/leonardehrenfried/jspc-maven-plugin/pull/2 to add this feature.
Benchmark
I made a little benchmark with two projects : one with 400 jsp and one with 2700 jsp. I test this on HP ZBook i7 Windows 7.
-
400 jsp
Time | Java Heap | CPU | |
---|---|---|---|
out of the box 1.1.0 |
50s |
1.3Gb |
20%-30% |
Giving all jsp to jspc.execute |
30sec |
1.1Gb |
20%-30% |
With 2 threads |
21s |
1.35Gb |
60% |
With 4 threads |
17s |
1.35Gb |
90% |
-
2700 jsp
Time | Java Heap | CPU | |
---|---|---|---|
out of the box 1.1.0 |
18min18s |
1.35Gb |
20% |
Giving all jsp to jspc.execute |
3min43s |
1.35Gb |
20% |
With 2 threads |
2min5s |
1.35Gb |
40%-60% |
With 4 threads |
1min25 |
1.35Gb |
80% |
Second fix : stop at first error
To avoid this behavior, I add new parameter : stopAtFirstError (true by default for retro-compatibility). Indeed Jasper JSPC provide a parameter "failOnError" wich allow to continue even if error were raised. This feature works only if we gave all the jsp to jspc. (see first fix). But we need to fail maven build if we have jsp error, but when we call jspc with "failOnError=false", it raise no exception. So the only way I found right now is to add a Log handler who tell the Mojo if jspc raise a SEVERE error in his log (this is a little be tricky…). If we take multiple threads, this parameter works only if it’s "false". If it’s true, each thread with stop at first error. See pull request https://github.com/leonardehrenfried/jspc-maven-plugin/pull/2 to add this feature.
Comparing to other Maven plugins
During this case I search other Maven plugins who compile JSP with Tomcat. It’s not easy because JSP it’s very fashionable… I found this major ones :
-
https://github.com/leonardehrenfried/jspc-maven-plugin
-
no recent upgrade
-
provide includes/excludes parameters
-
trace all jsp in log when compiling
-
fork of jetty-jspc-maven-plugin
-
-
https://github.com/Jasig/jspc-maven-plugin
-
1 recent upgrade
-
allow different version of tomcat compiler (by design)
-
fork of the Codehaus jspc-maven-plugin
-
does not provide stopAtFirstError=false for now (but there are current PR for that)
-
does not provide includes/excludes parameters
-
trace all jsp in log when compiling
-
-
jspc-maven-plugin from codehaus
-
no more maintained
-
does not provide stopAtFirstError=false
-
does not provide tomcat 8 compiler (only tomcat 6)
-
-
https://www.eclipse.org/jetty/documentation/current/jetty-jspc-maven-plugin.html
-
design by and for jetty
-
if we give jasper dependency we can use this for tomcat
-
does not provide stopAtFirstError=false for now
-
trace all jsp in log at startup
-
And this is a little benchmark of this plugins with my project (2700 jsp).
Time | Java Heap | CPU | |
---|---|---|---|
leonardehrenfried/jspc-maven-plugin 1.1.0 |
18min18s |
1.35Gb |
20% |
leonardehrenfried/jspc-maven-plugin with my PR (4 threads) |
1min25 |
1.35Gb |
80% |
Jasig/jspc-maven-plugin |
3min22s |
1.35Gb |
20% |
jspc-maven-plugin from codehaus |
not possible because not support tomcat 8 |
||
jetty-jspc-maven-plugin |
3min50s |
1.37Gb |
20% |
Conclusion
If the wholes PR are accepted, the plugin will be much faster and will permit to not stop at the first error. I think this maybe start a "version 2.X" of this plugin because one PR need java 7. I think this plugin can give Tomcat 8 by default now (especially if it start 2.X version). If you want to use this plugin with this fix and features see this : https://github.com/leonardehrenfried/jspc-maven-plugin/pull/2
Annex
Jasig JSPC maven plugin config
<plugin>
<groupId>org.jasig.mojo.jspc</groupId>
<artifactId>jspc-maven-plugin</artifactId>
<version>2.0.2</version>
<executions>
<execution>
<id>jspc</id>
<phase>package</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<inputWebXml>${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/web.xml</inputWebXml>
<defaultSourcesDirectory>${project.build.directory}/${project.artifactId}-${project.version}</defaultSourcesDirectory>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jasig.mojo.jspc</groupId>
<artifactId>jspc-compiler-tomcat8</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
</plugin>
jetty JSPC maven plugin config
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jspc-maven-plugin</artifactId>
<version>9.3.14.v20161028</version>
<executions>
<execution>
<id>jspc</id>
<phase>package</phase>
<goals>
<goal>jspc</goal>
</goals>
<configuration>
<webXml>${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/web.xml</webXml>
<webAppSourceDirectory>${project.build.directory}/${project.artifactId}-${project.version}</webAppSourceDirectory>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>${tomcatVersion}</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>${eclipse.jdt.core.compiler.ecj.version}</version>
</dependency>
</dependencies>
</plugin>