Archive for July, 2011

A StringTemplate Spring MVC integration that doesn’t suck

July 20, 2011 4 comments

I’ve been experimenting with all this web stuff that’s going on these days, and my platform of choice is java.  I have some experience with Spring MVC (2.0 sucks but 3 is ok), but I didn’t feel like using JSP.  StringTemplate is a template engine invented by the ANTLR folks to do code generation, and is a general purpose, fast template engine.  It enforces clean separation of code from presentation.  StringTemplate 4 is pretty recent, and that’s what I’m using for this experimentation.  Anyway, here’s the shiny bits:

Firstly, set up your build environment.  I use maven to pull in dependencies.  Add this to your pom.xml:

1 <dependency>
2 	<groupId>org.antlr</groupId>
3 	<artifactId>stringtemplate</artifactId>
4 	<version>4.0.2</version>
5 </dependency>

Now you need to inform Spring that you’ll be using StringTemplate instead of the standard JSTLView.  Here’s where things get hairy.  Spring uses the InternalResourceViewResolver class to resolve views, and it will instantiate a class that you configure it to use.  What this means is, spring calls new(), and this class doesn’t participate in bean lifecycle processes.  If you want dependency injection, this is one case where you’re hosed unless you find a more elaborate way to do things.  This is as far as I’ve cared to take it.

1 <bean id="viewresolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
2 	<property name="viewClass" value="com.garytrakhman.common.BeanView" />
3 	<property name="prefix" value="/templates/" />
4 	<property name="suffix" value=".st" />
5 	<property name="requestContextAttribute" value="rc" />
6 </bean>

The magic happens in my BeanView class. This class is configured to access a factory and get the real bean view resolver. This enables me to unit-test and configure the actual logic using dependency injection.

 1 public class BeanView extends InternalResourceView {
 2 	private IViewResolver viewResolver;
 3 	{
 4 		Config config = ConfigFactory.getInstance();
 5 		viewResolver = config.getViewResolver();
 6 	}
 8 	@SuppressWarnings({ "rawtypes", "unchecked" })
 9 	@Override
10 	protected void renderMergedOutputModel(Map model,
11 			HttpServletRequest request, HttpServletResponse response)
12 			throws Exception {
13 		viewResolver.renderMergedOutputModel(model, request, response,
14 				getBeanName());
15 	}
16 }

Now the delegation logic has been set-up. We now need the actual StringTemplate loading logic. Spring uses reflection to instantiate the InternalResourceView class, then we override that single method to take control of view rendering.

 1 import;
 2 import java.util.Map;
 4 import javax.servlet.http.HttpServletRequest;
 5 import javax.servlet.http.HttpServletResponse;
 7 import org.springframework.beans.factory.annotation.Autowired;
 8 import org.springframework.stereotype.Component;
 9 import org.stringtemplate.v4.AutoIndentWriter;
10 import org.stringtemplate.v4.ST;
11 import org.stringtemplate.v4.STGroup;
12 import org.stringtemplate.v4.STGroupFile;
14 import com.garytrakhman.common.IViewResolver;
15 import com.garytrakhman.common.impl.URLMap;
16 import;
18 @Component
19 public class STViewResolver implements IViewResolver {
21 	// assume thread safe modifying access to templates, adaptors, attributes,
22 	// imports, and renderer access on STGroup/ST (they are synchronized).
23 	//
25 	@Autowired
26 	private URLMap urlMap;
28 	private Map<String, STGroup> groups = Maps.newConcurrentMap();;
30 	@Override
31 	public void renderMergedOutputModel(Map<String, ?> model,
32 			HttpServletRequest request,
33 			HttpServletResponse response, String viewName) throws Exception {
35 		ST template = getView(viewName);
37 		template.add("model", model);
38 		template.add("url", urlMap.get());
40 		PrintWriter writer = response.getWriter();
42 		template.write(new AutoIndentWriter(writer));
44 		writer.flush();
45 		writer.close();
46 	}
48 	private STGroup getGroup(String key) {
49 		STGroup out = groups.get(key);
50 		if (out != null) {
51 			return out;
52 		}
54 		out = new STGroupFile("templates/" + key + ".stg");
55 		out.delimiterStartChar = '%';
56 		out.delimiterStopChar = '%';
57 		groups.put(key, out);
58 		return out;
59 	}
61 	private ST getView(String viewName) {
62 		STGroup group = getGroup(viewName);
63 		ST template = group.getInstanceOf(viewName);
64 		return template;
65 	}
66 }

What’s happening here is pretty simple.  The object sets up a concurrent cache of the template groups (each file is a group).  They are organized in my src/main/resources/templates directory, and it uses the view name as the key to load the file.  Once a template is requested a second time, it loads straight from the cache.  Since these templates are compiled and fast, it makes for some very speedy behavior.  In my testing with JMeter, I was able to perform ~9k requests/sec with 100 threads hammering on a simple view on a core i5 2600K, running on jetty.  I also set-up some default keys/values for the templates, mainly the model and url keys.  You could easily use the request objects or anything else.  StringTemplate provides flexibility by accessing your beans with reflection or letting you implement adapters to map template data to objects.  So far, I’m very happy with it.

Categories: Uncategorized