/ blog / java / compiled-el.html

Root Beer Logo Root Beer

Chris Nokleberg's Fizzy Weblog

February 2006
Su M Tu W Th F Sa
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 16 17 18
19 20 21 22 23 24 25
26 27 28
Previous  |  Next  |  More...
#  Compiling the JSP EL to bytecode

I recently came across Matthias Ernst's blog entry on compiling the JSP expression language. Back in the day this was something that I spent a bit of time on myself, but the code has been languishing ever since we decided to focus on PowerPoint solutions, so Matthias has inspired me to dust it off and release it as an open source project.

This zip file includes the entire project: Ant build file, JUnit tests, and all the necessary libraries. It uses ASM 3.0 (beta) for bytecode generation, and Jar Jar Links to embed the ASM libraries when building the library Jar file. The source uses the same license as GNU Classpath (GPL with library exception).

The library needs some more documentation, but essentially it is just an implementation of the javax.servlet.jsp.el.ExpressionEvaluator interface:

ExpressionEvaluator eval = new com.tonicsystems.el.Evaluator();
Expression e = eval.parseExpression("${8 * 8}", String.class, null);
assertEquals("64", e.evaluate(null));

The main benefit of byte-compiling is of course performance. In my very simple tests the performance ranges from about 5-10x faster than the Apache Commons EL, which uses an interpreter. However there are some areas of the EL spec which cause big problems for byte-compiling. In particular, the resulting type when dereferencing variables, Maps, and Lists cannot be known until runtime.

The general solution to the unknown type issue is to compile as much of an expression as you can, and lazily compile the rest once you have enough information (i.e. when the variables have been defined). The problem with this is it is possible to design some degenerate expressions which have to be split up into many different subexpressions, all compiled separately. To help counter this, I've added an extension to the parsing interface whereby you can optionally specify a prototype for your variables. This is basically just a Map which "resembles" the variables you will actually end up using. The compiler ignores the actual values, but uses the types of the objects to generate more efficient bytecode:

Map prototypes = new HashMap();
prototypes.put("a", new Integer(0));
Expression e = eval.parseExpression("${a + 3}", String.class, null, prototypes);

Another bytecode-specific issue is that the JSP EL interfaces only provide a way to parse one expression at a time, but you really do not want to compile each expression into a new class. For this I've added a Precompiler class which can take multiple expressions and ensure that they are all compiled into a single underlying class:

Precompiler.process(new Expression[]{ e1, e2, e3 });

BTW, I recently changed the code to use a custom ClassLoader instead of injecting the classes into the parent, in order to facilitate garbage collection compiled expressions. However, this seems to have a negative effect on the runtime performance--the expressions run about twice as slow. Does the use of custom ClassLoaders affect HotSpot somehow?

Because the code is relatively old and was never completely finished, there are a few things on the TODO list:

  • Support for JSP 2.1
  • Use generic type information to improve the bytecode, when available
  • Support for the implicit objects defined by JSP
  • More tests (or get access to the official tests)
  • Documentation

If people are interested in working on the code, let me know and I'll register a project somewhere like SourceForge.

[Powered By FreeMarker]  [Valid Atom 1.0]  [Weblog Commenting by]