Wednesday, May 30, 2007

Making JDOM run faster

It's time for another cool AspectJ hack here on RMA, once again leveraging Codehaus' excellent Maven plugin. Still working on the same project as the last post, we've decided to move from standard JAXP to JDOM for programmatic XML handling (the JDOM api is so much nicer to work with it's not even funny). As the returning reader may recall, the model in the MVC part of our application basically consists of a number of XML documents, and the view rendering technology is Freemarker. Freemarker is supposed to be able to handle both JAXP, JDOM and dom4j, but a closer look will reveal that it's more or less only JAXP that's up to date, the other wrappers are deprecated and don't work correctly anymore, which is a shame.

Using JDOM, there is a simple workaround however: the DOMOutputter, which is capable of converting an org.jdom.Document to an org.w3c.dom.Document which can then be handed to Freemarker. There is a speed penalty of course, and when profiling we found that almost all the time is spent in this method, in JAXPDOMAdapter:

public Document createDocument() throws JDOMException {

try {
// We need DOM Level 2 and thus JAXP 1.1.
// If JAXP 1.0 is all that's available then we error out.
Class.forName("javax.xml.transform.Transformer");

// Try JAXP 1.1 calls to build the document
Class factoryClass =
Class.forName("javax.xml.parsers.DocumentBuilderFactory");

// factory = DocumentBuilderFactory.newInstance();
Method newParserInstance =
factoryClass.getMethod("newInstance", null);
Object factory = newParserInstance.invoke(null, null);

// jaxpParser = factory.newDocumentBuilder();
Method newDocBuilder =
factoryClass.getMethod("newDocumentBuilder", null);
Object jaxpParser = newDocBuilder.invoke(factory, null);

// domDoc = jaxpParser.newDocument();
Class parserClass = jaxpParser.getClass();
Method newDoc = parserClass.getMethod("newDocument", null);
org.w3c.dom.Document domDoc =
(org.w3c.dom.Document) newDoc.invoke(jaxpParser, null);

return domDoc;
} catch (Exception e) {
throw new JDOMException("Reflection failed while creating new JAXP document", e);

You're probably wondering why the hell they're using this much reflection just to create an empty Document (we did, anyway). It avoids compile-time dependencies on certain javax.xml classes, but it sure isn't designed with high performance in mind!

In addition to that, the call trace from DOMOuputter to JAXPDOMAdapter contains private methods, so you can't simply inherit and override with your own implementation. So, what now? AspectJ to the rescue, of course!

What we realy want to do is replace this implementation with one that performs the sam thing but statically, so we wrote this around advice (DocBuilder creation omitted for brevity):

@Aspect
public class FastDOMDocumentCreator {

@Around("execution (* org.jdom.output.DOMOutputter.createDOMDocument(..))")
public Object createDOMDocument(ProceedingJoinPoint pjp) throws Throwable {
return docBuilder.newDocument();
}

}

This is a lot faster than the default way. But how do we perform weaving of JDOM classes, that are in an external jar? Actually, it's really simple: just place this snippet in the AspectJ plugin section in the pom-xml (details here):

<configuration>
<weaveDependencies>
<weaveDependency>
<groupId>jdom</groupId>
<artifactId>jdom</artifactId>
</weaveDependency>
</configuration>

If you're using AJDT (which I highly recommend), make sure you add the JDOM jar to the inpath in the configuration dialog, that way this weaving runs on the fly just like regular compilation. On a sidenote, AJDT has some really cool features such as contextual information on "weaves" and "weaved by". It supports annotation-style aspects too.

The weaved jar is exploded under the output directory, with the new weaved classes instead of the vanilla ones. This trick pushed the JDOM-to-JAXP document converstion from the top way down on the hotspot list, with a fairly small amount of work.

Update: While examining the situation a bit closer, we found that in fact it's possible to accomplish the same thing without having to weave the JAXPDOMAdapter. Extending AbstractDOMAdapter using the same code as in the aspect, and passing the new class' name to the DOMOutputter constructor will work too. Nevertheless, the technique is still interesting and may be applicable (as the only solution) somwhere else.

1 comment:

dontcare said...

Have you looked at vtd-xml? it is a lot faster and memory efficient than DOM4J and JDOM

http://vtd-xml.sf.net