Search This Blog

Sunday, September 11, 2011

Creating JaxContext is slow

Recently, I need to create xml or json from a POJO.  I used JAXB and jettison to do the job.  At the beginning, I create the following class:

import java.io.Writer;

import javax.xml.bind.*;
import javax.xml.stream.XMLStreamWriter;

import org.codehaus.jettison.mapped.*;

public class BadJaxbPrinter {
    /**
     * To print an object to xml format
     * @param writer
     * @param jaxbAnnotedObj the object to be printed.  The class must be annoted with @XmlRootElement
     * @throws JAXBException
     */
    public void toXml(Writer writer, Object jaxbAnnotedObj) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(jaxbAnnotedObj.getClass());
        Marshaller marshaller = context.createMarshaller();
        marshaller.marshal(jaxbAnnotedObj, writer);
    }

    /**
     * To print an object to json format
     * @param writer
     * @param jaxbAnnotedObj the object to be printed.  The class must be annoted with @XmlRootElement
     * @throws JAXBException
     */
    public void toJson(Writer writer, Object jaxbAnnotedObj) throws JAXBException {
        Configuration config = new Configuration();
        MappedNamespaceConvention convention = new MappedNamespaceConvention(config);
        XMLStreamWriter xmlStreamWriter = new MappedXMLStreamWriter(convention, writer);
       
        JAXBContext context = JAXBContext.newInstance(jaxbAnnotedObj.getClass());
        Marshaller marshaller = context.createMarshaller();
        marshaller.marshal(jaxbAnnotedObj, xmlStreamWriter);
    }
}

However, the above implementation is really slow.  The reason is that it is slow to create JAXBContext because it uses reflection to parse the object's annotation.  Since JAXBContext is thread safe, it should be only created once and reuse it.  Here is the new version of implementation:

public class JaxbPrinter {
    private final MappedNamespaceConvention convention;
    //creating JaxbContext seems expensive, do NOT recreate it
    private final JAXBContext context;
   
    public JaxbPrinter(Class<?> clazz) throws JAXBException {
        Configuration config = new Configuration();
        convention = new MappedNamespaceConvention(config);
        context = JAXBContext.newInstance(clazz);
    }
    /**
     * To print an object to xml format
     * @param writer
     * @param jaxbAnnotedObj the object to be printed.  The class must be annoted with @XmlRootElement
     * @throws JAXBException
     */
    public void toXml(Writer writer, Object jaxbAnnotedObj) throws JAXBException {
        Marshaller marshaller = context.createMarshaller();
        marshaller.marshal(jaxbAnnotedObj, writer);
    }

    /**
     * To print an object to json format
     * @param writer
     * @param jaxbAnnotedObj the object to be printed.  The class must be annoted with @XmlRootElement
     * @throws JAXBException
     */
    public void toJson(Writer writer, Object jaxbAnnotedObj) throws JAXBException {
        XMLStreamWriter xmlStreamWriter = new MappedXMLStreamWriter(convention, writer);
        Marshaller marshaller = context.createMarshaller();
        marshaller.marshal(jaxbAnnotedObj, xmlStreamWriter);
    }
}
It is 25 times faster than the previous version with 10000 executions of  toXml.  Note that Marshaller and Unmarshaller are not thread safe and it is cheap to recreate them.

Also, toJson is 5 times slower than toXml.  Since the response time meets SLA, I didn't bother to use other JSON implementation (like jackson).