Web Resource Optimisation with WRO4J

Its been a while since I've posted anything so I'm going to ease myself back in with a post on web resource optimisation, and in particular my recent experience optimising a Java web application using WRO4J.

What is Web Resource Optimisation?

Rich internet applications are now common place, delivering a slick, responsive user experience that in the past was only possible with thick client applications. Modern web applications are typically composed of many JavaScript and CSS resources, so we need to consider ways of optimising how theses resources are served to the client. The objective of web resource optimisation like any other type of optimisation, is to increase application performance and ultimately improve user experience.

In this post I'll create a very simple Spring MVC app with 2 JSP views that import some CSS and JavaScript resources. Both views will import the same resources, the difference being that one will manage resources using a traditional approach, while the other will use WRO4J. We'll take a look at how these resources are configured and the performance implications of both approaches. The sample application is available to download at the bottom of this post.

Standard Web Resource Configuration

We'll start off by creating a very simple JSP view that imports 11 JavaScript and 6 CSS files. The JSP is defined as follows.
 <%@taglib uri="http://www.springframework.org/tags" prefix="spring" %>  
 <!DOCTYPE html>  
 <html lang="en">  
 <head>  
      <!-- JavaScript Resources -->  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/jquery/jquery-1.8.3.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/bootstrap.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="//resources/js/lib/data-tables/1.9.4/media/js/jquery.dataTables.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/underscore/underscore.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/underscore/underscore.string.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/jquery.bootstrap-growl.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/datepicker/js/bootstrap-datepicker.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/jquery/plugins/jquery.ui.widget.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/jquery/plugins/jquery.iframe-transport.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/jquery/plugins/jquery.fileupload.js"/>"></script>  
      <script type="text/javascript" src="<spring:url value="/resources/js/lib/jquery.slimscroll.js"/>"></script>    
      <!-- CSS resources -->       
      <link href="<spring:url value="/resources/css/lib/bootstrap/3.0.0/css/bootstrap.min.css"/>" rel="stylesheet" media="screen">  
      <link href="<spring:url value="/resources/css/lib/bootstrap/3.0.0/css/bootstrap-theme.min.css"/>" rel="stylesheet" media="screen">  
      <link href="<spring:url value="/resources/css/lib/font-awesome/4.0.0/css/font-awesome.css"/>" rel="stylesheet" media="screen">       
      <link href="<spring:url value="/resources/js/lib/data-tables/1.9.4/media/css/demo_table.css"/>" rel="stylesheet" media="screen">  
      <link href="<spring:url value="/resources/js/lib/datepicker/css/datepicker.css"/>" rel="stylesheet" media="screen">        
      <link href="<spring:url value="/resources/css/lib/jquery-ui-1.10.3.custom.min.css"/>" rel="stylesheet" media="screen">  
 </head>  
 <body>                  
      <div class="container">  
           <H1>Not much to see here, just a test page with a bunch of JavaScript imports. No optimisation here!</H1>       
   </div>        
 </body>  
 </html>  
We can load this view by navigating to http://localhost:8080/wro4j-sample/standardView in the sample application. Before loading this view open chrome dev tools (CTRL+SHFT +I) and switch to the Network tab (you can of course use Firebug or any other dev tool for this).


When the view loads we’ll see that there are 18 individual HTTP requests to pull resources from the server (17 for the JavaScript/CSS and 1 for the HTML view itself). This results in 1.1MB of resources loading in 1.01 seconds (these metrics can be seen in the foot of the chrome dev tools window in the screenshot above). Note that individual page load times will vary depending on your machine spec, Tomcat memory settings etc.

Options for Web Resource Optimisation 

Now we’re going to look at how we can optimise these resources. In particular we’ll be looking at
  • Resource Minification
  • Resource Merging
  • Resource Compression
  • Resource Caching

Resource Minification
Resource minification is a process where by all non-essential characters are removed from a resource without impacting its functionality. Non-essential characters typically include white space, comments and new line characters. Resource minification can substantially reduce the size of a file and therefore the amount of time taken to retrieve that resource from the server. In the optimised example later you’ll see minification being applied to both JavaScript and CSS resources.

Resource Merging
Resource Merging is a process where by multiple distinct resources are merged into a single file. In our example above we have 11 separate JavaScript files - merging these resources into a single JavaScript file will reduce the number of HTTP requests from 11 to just 1. Resource merging reduces the load on the server and in conjunction with resource minification (described above) will reduce the amount of time taken to load the page resources. In the optimised example later you’ll see how WRO4J provides granular control over what resources are merged together.

Resource Compression
Resource compression is a process whereby resources are compressed on the server prior to being sent to the browser. GZIP compression is such an approach and substantially reduces the resource size, resulting in faster page loads on the browser. In order for GZIP compression to work the browser must tell the server that it would like a compressed resource using a HTTP header like Accept-encoding:gzip. If the server supports GZIP compression it will compress whatever resources were requested and return them to the client. The client must then decompress the resources on the client side before using them. If the server doesn't support GZIP compression it will return the request resources in their standard format. In the optimised example later you’ll see how GZIP compression can be enabled in WRO4J.

Resource Caching
Resource caching is a process where by resources are served from memory as opposed to being retrieved from the file system. When a resource request reaches the server it will first check its cache for that resource and if its available it will return the resource to the client. If the requested resource isn't available in the cache the server will retrieve the resource from disk, add it to the cache and then return it to the client. Retrieving resources from the cache is preferable as it reduces load on the server and reduces the response time for the client. Resource caching is particularly desirable when used in conjunction with the optimisation methods discussed above. Resource merging, minification and compression can be performed on the first request for a resource before it is added to the cache. Subsequent requests for the same resource will return the merged, minified and compressed resource straight from the cache.

Configuring Resource Optimisation with WRO4J

Below is a step by step guide to configuring WRO4J in a Spring web application.

POM Configuration

Add the following entry to your POM so that the required WRO4J resources are available to the application.
 <dependency>  
    <groupId>ro.isdc.wro4j</groupId>  
    <artifactId>wro4j-core</artifactId>  
    <version>1.6.3</version>  
 </dependency>  

Filter Definition

Define a filter and filter mapping in your web.xml file for WRO4J. These are required to tell WRO4J to handle incoming HTTP requests for resources.
 <filter>  
    <filter-name>WebResourceOptimizer</filter-name>  
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
    <init-param>  
       <param-name>targetBeanName</param-name>  
       <param-value>wroFilter</param-value>  
    </init-param>  
    <init-param>  
       <param-name>targetFilterLifecycle</param-name>  
       <param-value>true</param-value>  
    </init-param>  
 </filter>  
 <filter-mapping>  
    <filter-name>WebResourceOptimizer</filter-name>  
    <url-pattern>/wro/*</url-pattern>  
 </filter-mapping>  
The delegating filter proxy delegates off to a Spring managed bean that we'll define later. This allows us to configure WRO4J using standard Spring property management. Note that the target bean name must match the Spring managed bean that is defined in the Spring configuration file. Also, the URL pattern defined in the filter mapping tells WRO4J to handle all incoming requests for URLs matching the pattern /wro/*

WRO.xml Configuration

In the webapp/WEB-INF directory create a wro.xml file. This file will contain grouping definitions that tell WRO4J what resources you want to merge together and how they should be named.
 <?xml version="1.0" encoding="UTF-8"?>  
 <groups xmlns="http://www.isdc.ro/wro"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd">  
   
   <!-- CSS resources -->  
   <group name="bundledCss">  
     <css>/resources/css/lib/bootstrap/3.0.0/css/bootstrap.css</css>  
     <css>/resources/css/lib/bootstrap/3.0.0/css/bootstrap-theme.css</css>  
     <css>/resources/css/lib/font-awesome/4.0.0/css/font-awesome.css</css>  
     <css>/resources/js/lib/data-tables/1.9.4/media/css/demo_table.css</css>  
     <css>/resources/js/lib/datepicker/css/datepicker.css</css>    
     <css>/resources/css/lib/jquery/jquery.fileupload.css</css>    
     <css>/resources/css/lib/jquery-ui-1.10.3.custom.css</css>           
   </group>  
   
   <!-- JS resources -->  
   <group name="bundledJs">  
     <js>/resources/js/lib/jquery/jquery-1.8.3.js</js>  
     <js>/resources/css/lib/bootstrap.js</js>  
     <js>/resources/js/lib/data-tables/1.9.4/media/js/jquery.dataTables.js</js>  
     <js>/resources/js/lib/underscore/underscore.js</js>  
     <js>/resources/js/lib/underscore/underscore.string.js</js>    
     <js>/resources/js/lib/jquery.blockUI.js</js>  
     <js>/resources/js/lib/jquery.bootstrap-growl.js</js>    
     <js>/resources/js/lib/datepicker/js/bootstrap-datepicker.js</js>     
     <js>/resources/js/lib/jquery/plugins/jquery.ui.widget.js</js>  
     <js>/resources/js/lib/jquery/plugins/jquery.iframe-transport.js</js>  
     <js>/resources/js/lib/jquery/plugins/jquery.fileupload.js</js>   
     <js>/resources/js/lib/jquery.slimscroll.js</js>   
     <js>/resources/js/lib/jquery-ui-1.10.3.custom.js</js>   
   </group>  
   
 </groups>  
You'll can see from the configuration above that we've merged
  • 11 JavaScript resources into a single group that can be referenced by the name bundledJS. 
  • 7 CSS resources into a single group that can be referenced by the name bundledCss 

WRO4J Spring configuration

 <?xml version="1.0" encoding="UTF-8" standalone="no"?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
     xmlns:context="http://www.springframework.org/schema/context"  
     xmlns:mvc="http://www.springframework.org/schema/mvc"  
     xmlns:p="http://www.springframework.org/schema/p"  
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
     xmlns:util="http://www.springframework.org/schema/util"  
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
                         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd  
                         http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd  
                         http://www.springframework.org/schema/util  
                         http://www.springframework.org/schema/util/spring-util-3.0.xsd">  
   
        
      <mvc:annotation-driven/>  
   
      <!-- Setup spring to pull in @Controller, @RequestMapping, etc -->  
      <context:component-scan base-package="com.blog.samples.wro4j" />  
   
      <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->  
      <mvc:resources mapping="/resources/**" location="/resources/" />  
   
      <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->  
      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
           <property name="prefix" value="/WEB-INF/views/" />  
           <property name="suffix" value=".jsp" />  
      </bean>  
        
      <mvc:view-controller path="/" view-name="/testViewNoOptimisation"/>       
   
      <!-- WRO config -->  
      <bean id="wroFilter" class="ro.isdc.wro.http.ConfigurableWroFilter">  
       <property name="properties" ref="wroProperties"/>  
      </bean>  
        
      <bean id="wroProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">   
        <property name="location" value="classpath:wro.properties" />   
      </bean>   
             
 </beans>  
   
You'll notice that only 2 pieces of WRO4J configuration are required.
  • A configurable filter that will be invoked by the DelegatingFilterProxy we defined earlier in web.xml. Make sure that the bean name matches the targetBeanName attribute define in the filter definition in web.xml. 
  • A PropertiesFactoryBean for loading the wro4j properties file and making it available to the ConfigurableWroFilter mentioned above.

WRO4J Property Configuration 

WRO4J has the following configuration options. These are explained below.
 debug=false  
 gzipEnabled=true  
 cacheGzippedContent=true  
 jmxEnabled=false  
 mbeanName=wro  
 cacheUpdatePeriod=100  
 modelUpdatePeriod=0  
 disableCache=false  
 encoding=UTF-8  
 managerFactoryClassName=ro.isdc.wro.manager.factory.ConfigurableWroManagerFactory  
 preProcessors=cssUrlRewriting,semicolonAppender,jsMin  
 uriLocators=servletContext,uri,classpath  
  • debug - debug set to true allows you to add ?minimise=false to the request URL. This is pretty handy during development when debugging JavaScript.   
  • gzipEnabled - flag enables or disables gzip compression
  • cacheGzippedContent - when this is enabled resources will be compressed only once and added to the cache. Subsequent requests for the same resource will be taken from the cache. If this flag is disabled every resource requested will be compressed. Note that disabling this setting will result in slower response times
  • jmxEnabled - defines whether or not WRO4J is exposed to JMX console
  • mbeanName - Name given to mBean object exposed to JMX console
  • cacheUpdatePeriod - defines how often (in seconds) the cache is flushed
  • modelUpdatePeriod - defines how often (in seconds) the bundle definitions are flushed in wro.xml
  • disableCache - option for use in debug mode only results in every request being processed from scratch. This is useful when debugging.
  • encoding - type of encoding used. Default is UTF8
  • managerFactoryClassName - required in order to configure pre and post processors (see below)
  • preProcessors - a comma separated list of processors to run during resource pre processing. The configuration above uses the following pre processors
    • cssUrlRewriting - when CSS resources are merged the relative URLs are broken (links to images for example). This pre processor rewrites URLs so that they refer to the appropriate resources.
    • semiColonAppender - adds a semi colon to the end of each JavaScript file if its missing. This avoids issue when multiple JavaScript files are merged.
    • cssMin - runs cssMinification to remove non essential characters such as comments and white space
    • jsMin - runs JSMin utility to remove non essential characters such as comments and white space

Using WRO4J in the JSP

Now that we've configured WRO4J lets take a look at how it's used in our sample application.  We'll create another simple JSP view that imports the same resources (11 JavaScript and 6 CSS) as before. This time however, we'll use WRO4J to serve the resources. The JSP is defined as follows.
 <%@taglib uri="http://www.springframework.org/tags" prefix="spring" %>  
 <!DOCTYPE html>  
 <html lang="en">  
   <head>  
     <link rel="stylesheet" type="text/css" href="/wro4j-sample/wro/bundledCss.css" />  
     <script type="text/javascript" src="<spring:url value="/wro/bundledJs.js"/>"></script>         
   </head>  
   
   <body>                       
      <div class="container">  
           <H1>Not much to see here, just a test page with a bunch of JavaScript imports. Now with resource optimisation!</H1>            
      </div>     
   </body>  
   
 </html>  
A couple of things to note here
  • 11 JavaScript references from the original view (defined earlier) have been replaced with a single reference to /wro/bundledJs.js. 
  • 6 CSS references from the original view (defined earlier) have been replaced with a single reference to /wro/bundledCss.js.
  • All JavaScript and CSS requests now have the pattern /wro/* so that they are serviced by the WRO filter we defined earlier. 
  • The name of the JavaScript and CSS bundles (bundledJs.js & bundledCss.css) are taken from the group names we defined in wro.xml

Testing the Optimised View

We can load this view by navigating to http://localhost:8080/wro4j-sample/optimisedView in the sample application. Before loading this view open chrome dev tools (CTRL+SHFT +I) and switch to the Network tab.
When the view loads we’ll see that there are now 3 HTTP requests to pull resources from the server, 1 for the bundled JavaScript, 1 for the bundled CSS and 1 for the HTML view itself. 
This results in 245KB of resources loading in 316 ms (these metrics can be seen in the foot of the chrome dev tools window in the screenshot above). Note that individual page load times will vary depending on your machine spec, Tomcat memory settings etc.

Simple View vs WRO4J Optimised View

So what are the results when we compare the standard JSP view with the WRO4J optimised view? Remember that the resources referenced in both views are exactly the same, the only difference being the way they are served. The results are impressive
  • fewer HTTP requests (reduced from 18 to 3)
  • smaller message payload returned from the server (reduced from 1.1MB to 245KB)
  • reduced load time for resources (reduced from 1.01 seconds to 316ms)
The examples used in this post are primitive but they do demonstrate the fundamentals of web resource optimisation and the associated benefits. Ultimately, web resource optimisation should result in faster loading, more responsive applications and a better user experience.

Sample Code

Sample code for this post is available at https://github.com/briansjavablog/wro4j-sample. Feel free to experiment with the code and as always, comments/questions are welcome.   

Comments

  1. Getting error with above configuration like :

    Error creating bean with name 'wroFilter' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [ro.isdc.wro.http.ConfigurableWroFilter]: Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/lang3/Validate

    all jar files in classpath.

    ReplyDelete
  2. This is very nice information and thanks u so munch for sharing all detail.

    website design services

    ReplyDelete
  3. Hi Brian,

    I have used the solution and it works find except when I want to use bootstrap. There certain files in bootstrap like bootstrap.css.map and all the .less files referred by bootstrap.css.map are not getting included. And some of the bootstrap styles fail. Any workaround or solution to this?

    ReplyDelete
  4. Awesome tutorial, good job bro

    ReplyDelete

Post a Comment

Popular posts from this blog

Spring Web Services Tutorial

Spring Boot & Amazon Web Services (EC2, RDS & S3)

Spring JMS Tutorial with ActiveMQ

An Introduction to Wiremock

Axis2 Web Service Client Tutorial

Health Checks, Metrics & More with Spring Boot Actuator

Spring Batch Tutorial

Externalising Spring Configuration

Spring Quartz Tutorial