On JEE 6 webapps startup time 25 Mar 2012
While working on Tomcat 7 embedded to automate my integration tests, I realized that my integration tests wasted much of the time in starting/stopping the server. Even if I do not start the integration tests as often as the unit tests, it becomes rapidly irritating. Furthermore, during development I tend to restart the server a couple of times per hour, especially at the beginning of the project. Sure hot deployment helps, but it is not always enough.
On my Mac Book Pro, a cold start took around 10s. Interestingly enough, an empty Tomcat startups in less than a second. The problem comes from the fact that Tomcat 7 scans the classpath to find out annotations that declare Servlets using the
@WebServlet. This, even if you do not use that feature. Don’t get me wrong, not having to configure XML is cool but I am not ready to pay such a high price for it. Especially as the only servlet I use, is the JSF one.
Where do we start from?
For these tests, I use a JEE6 application with JSF, Weld and JPA (no EJBs) that runs under Tomcat.
This is a demo application called JEE-6-Demo that I use to teach JEE6. A mentioned previously, a cold start (without tuning anything) requires around 10s on my Mac Book Pro Intel Core i7 with 8Gb RAM :
INFO: Server startup in 9992 ms
Step 1: Avoid looking for
@WebSerlet and co.
By default, Tomcat 7 (along with the Servlet 3.0 specification) scans the classpath to look for classes that are annotated
@InitParamJSF. It is a nice feature as you do not have to specify the faces servlet anymore.
However, it comes at a price: depending of the classpath this can be very long.
To solve this issues, simple add the
metadata-complete="true" to the
web-app element of our
WEB-INF/web-xml attribute to avoid scanning the classpath.
<web-app metadata-complete="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="MyWebApp" version="3.0">
Obviously, as it is no more automatically discovered, we have to manually add the faces servlet to the context:
<servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>
Using these modifications in
web.xml, the startup time came down to around 4.5 seconds:
INFO: Server startup in 4404 ms
Step 2: Avoid looking for
@ManagedBean and co.
Similarly, the is a similar feature in JSF 2.0. By default, the JSF implementation looks for classes annotated with
As I use Weld and its
@SessionScoped, and so on, I can disable this feature in JSF.
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" version="2.0" metadata-complete="true">
Using this modification in
faces-config.xml, the startup time came down to around 3.7 seconds:
INFO: Server startup in 3730 ms
Step 3: Limiting Weld’s scanning
Finally, I would like to keep Weld scanning to discover the
@Inject, and other Weld annotations but I would like to limit it to my a subset of the classes of the jar. To that end simply add
weld:scan directive and include a pattern with packages to scan.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:weld="http://jboss.org/schema/weld/beans" xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://jboss.org/schema/cdi/beans_1_0.xsd http://jboss.org/schema/weld/beans http://jboss.org/schema/weld/beans_1_1.xsd"> <weld:scan> <weld:include pattern="ch.demo.*"/> </weld:scan>
Using this modification in
beans.xml, the startup time came down to around 3.3 seconds:
INFO: Server startup in 3312 ms
To conclude, using these minor modifications I divided the startup time by three. This is very useful during development and integration tests when the server is started and stopped many times.