Optimizing web resources with wro4j, Spring and ehcache

January 18, 2012 by Michael

I think that almost no website today can do without JavaScript. There are some incredible good JavaScript libraries like jQuery for which an enormous mass of plugins and extensions exits.

The downside of this is, that for example the JavaScript code of my daily picture project Daily Fratze is bigger than the whole startpage of my first “homepage” was.

With every problem there is a solution, namely JavaScript compressors and minifier. Those tools can compress the code by removing superfluous whitespaces, renaming variables and functions or even by optimizing the code.

So far i have used the YUI compressor maven mojo in my Spring based projects. This is a build time solution that compresses JavaScript and CSS files when creating a war file.

For me it had several disadvantages: I don’t see the effect of compressing when i develop my application and it could not concatenate multiple script files.

The later is important because every additional request a browser makes slows down the loading of a webpage. And manual hacking all JavaScript into one file? No way…

wro4j to the rescue:

Free and Open Source Java project which brings together almost all the modern web tools: JsHint, CssLint, JsMin, Google Closure compressor, YUI Compressor, UglifyJs, Dojo Shrinksafe, Css Variables Support, JSON Compression, Less, Sass, CoffeeScript and much more. In the same time, the aim is to keep it as simple as possible and as extensible as possible in order to be easily adapted to application specific needs.

My goal was to integrate wro4j with Spring and ehcache with a minimum number of additional configuration files.

If you’re interested in some of my ideas, read on:

Please note: This is not the complete solution. The wro.xml is missing as well as some of my infrastructure ideas. But you should get the idea on how to use wro4j with Spring, extend it with your own caching strategy and configure the processors.

First step: Creating a WroFilter implementation that doesn’t depend on wro.properties:

import java.util.Map;
import java.util.Properties;
 
import ro.isdc.wro.http.ConfigurableWroFilter;
import ro.isdc.wro.manager.factory.WroManagerFactory;
 
public class Wro4jFilter extends ConfigurableWroFilter {
	private final WroManagerFactory factory;
 
	public Wro4jFilter(final Map<String, String> properties, final WroManagerFactory wroManagerFactory, final boolean debug) {
		this.factory = wroManagerFactory;
 
		final Properties p = new Properties();
		p.putAll(properties);
		p.put("debug", Boolean.toString(debug));				
		super.setProperties(p);		
	}
 
	@Override
	protected WroManagerFactory newWroManagerFactory() {		
		return this.factory;
	}	
}

As you see this filter accepts a map that is converted to a properties instance from which the superclass is configured. I also inject the WroManagerFactory which will be my interface to ehcache. Finally i can overwrite the debug flag.

This bean is instantiated through an @Configuration class like so:

@Configuration
@Profile("prod")
public class WebConfigProd extends WebConfig {
	@Bean(name="wroFilter")
	@Override
	public Filter getWroFilter() {
		return new Wro4jFilter(super.defaultWroProperties, new Wro4jManagerFactory(super.cacheManager), false);
	}
}

The defaultWroProperties is a Map created from the Spring Environment so that i can keep my wro4j options in my application properties file. cacheManager is an instance of net.sf.ehcache.CacheManager. The CacheManager is needed for my own WroManagerFactory which you'll see later.

To use this filter you need the Spring DelegatinFilterProxy:

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

Having set the targetFilterLifecycle to true is actually essential because the original ro.isdc.wro.http.WroFilter relies on the init method.

So far i only have the filter. What's missing is the wro4j model. This is where groups of JavaScript and CSS files are defined and it can be - among others - an xml file. I want mine to reside with my other configuration files in an dedicated package. To accomplish that i've created a special WroManagerFactory:

import java.io.InputStream;
 
import net.sf.ehcache.CacheManager;
import ro.isdc.wro.cache.CacheEntry;
import ro.isdc.wro.cache.CacheStrategy;
import ro.isdc.wro.cache.ContentHashEntry;
import ro.isdc.wro.extensions.processor.css.YUICssCompressorProcessor;
import ro.isdc.wro.extensions.processor.js.GoogleClosureCompressorProcessor;
import ro.isdc.wro.manager.factory.BaseWroManagerFactory;
import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.factory.XmlModelFactory;
import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory;
import ro.isdc.wro.model.resource.processor.factory.SimpleProcessorsFactory;
import ro.isdc.wro.model.resource.processor.impl.css.CssUrlRewritingProcessor;
 
import com.google.javascript.jscomp.CompilationLevel;
 
public class Wro4jManagerFactory extends BaseWroManagerFactory {
	public final static String CACHE_NAME = "some_cache_name";
	private final CacheManager cacheManager;
 
	public Wro4jManagerFactory(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}
 
	@Override
	protected ProcessorsFactory newProcessorsFactory() {
		final SimpleProcessorsFactory rv = new SimpleProcessorsFactory();
                // URLs in CSS needs to be rewritten as it is served from a different location than the original files. I'm not using @import statements, otherwise the appropriate processor should be added for rewriting them as well
		rv.addPreProcessor(new CssUrlRewritingProcessor());	
                // JavaScript compression by the Google Closure compressor	
		rv.addPreProcessor(new GoogleClosureCompressorProcessor(CompilationLevel.SIMPLE_OPTIMIZATIONS));
                // And css by YUI Css Compressor
		rv.addPreProcessor(new YUICssCompressorProcessor());
		return rv;
	}			
 
	@Override
	protected WroModelFactory newModelFactory() {
		return new XmlModelFactory() {
			@Override
			protected InputStream getModelResourceAsStream() {
				return this.getClass().getResourceAsStream("/foo/bar/config/wro.xml");
			}
		};
	}	
 
	@Override
	protected CacheStrategy<CacheEntry, ContentHashEntry> newCacheStrategy() {
		return new Wro4jCacheStrategy<CacheEntry, ContentHashEntry>(this.cacheManager.getCache(CACHE_NAME));
	}	
}

The location of my wro model is handled in newModelFactory. Nothing special. What's more interesting is newCacheStrategy. It get's an ehcache net.sf.ehcache.Cache from the CacheManager and passes it to my caching strategy:

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import ro.isdc.wro.cache.CacheStrategy;
 
public class Wro4jCacheStrategy<K, V> implements CacheStrategy<K, V> {	
	private final Cache cache;
 
	public Wro4jCacheStrategy(Cache cache) {
		this.cache = cache;
	}
 
	@Override
	public synchronized void put(K key, V value) {
		this.cache.put(new Element(key, value, null, null, null));
	}
 
	@SuppressWarnings("unchecked")
	@Override
	public synchronized V get(K key) {
		final Element element = this.cache.get(key);		
		return (V) (element == null ? null : element.getValue());
	}
 
	@Override
	public synchronized void clear() {
		this.cache.removeAll();
	}
 
	@Override
	public synchronized void destroy() {
		this.clear();
	}
}

So far i have:

  • No need for wro.properties
  • The wro.xml model along with my other configuration files
  • The WroFilter initialized by Spring
  • Having ehcache cache my compressed and concatenated resources

As you might have guessed there is also a WebConfigDev. The development configurations sets wro4j to development mode which means i can turn of minimisation at runtime through an url parameter like so "?minimize=false".

I didn't want to change my jsps all the time so i defined a bean that holds a property minimizeResources that is changable via JMX. I include my JavaScript like so:

<jsp:element name="script">
	<jsp:attribute name="src">
		<c:url value="/wro/project.js">
			<c:param name="minimize">${globalOptions.minimizeResources}</c:param>
		</c:url>
	</jsp:attribute>
	<jsp:body />
</jsp:element>

CSS is analog. I use the jsp:element syntax because all my jsps are actually jspx files.

With this solution i can easily turn off minimization in development mode.

In production mode i now have one compressed JavaScript file instead of 15 files and one CSS file instead of four. The first request takes a second or so but the compressed (and gzipped) content is cached. The Google Closure is actually pretty awesome and reduces my accumulated JavaScript code by over 50%.

Reducing the number of requests necessary had - for me - the biggest impact on my smartphone.

I really can recommend wro4j. It's very extensible and actually pretty easy to use. I cannot only be used to compress files but also can allow you to use CoffeeScript or SASS in Spring or JEE application.

No comments yet

One Trackback/Pingback
  1. […] more than 4 years now in a “classic” Spring application, see my post from January 2012 here. The tool has been immensely useful and stable. With my setup at dailyfratze.de, i can disable […]

Post a Comment

Your email is never published. We need your name and email address only for verifying a legitimate comment. For more information, a copy of your saved data or a request to delete any data under this address, please send a short notice to michael@simons.ac from the address you used to comment on this entry.
By entering and submitting a comment, wether with or without name or email address, you'll agree that all data you have entered including your IP address will be checked and stored for a limited time by Automattic Inc., 60 29th Street #343, San Francisco, CA 94110-4929, USA. only for the purpose of avoiding spam. You can deny further storage of your data by sending an email to support@wordpress.com, with subject “Deletion of Data stored by Akismet”.
Required fields are marked *