Spring in Practice

Willie Wheeler's Spring blog

ClasspathScanningJaxb2Marshaller

| Comments

By default, Spring’s <oxm:jaxb2-marshaller> tag uses the Jaxb2Marshaller class. It’s painful to use, though, if you have a lot of classes, because you have to enumerate them all individually. There are a couple of options here:

  • You can use one of the JAXB2 mechanisms (an ObjectFactory or an jaxb.index file; see JAXBContext).
  • You can use the marshaller’s classesToBeBound property.

Both of these options require more work than they ought to. Here, for example, is what the classesToBeBound approach looks like:

<oxm:jaxb2-marshaller id="marshaller">
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Database" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.DataCenter" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Environment" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Farm" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Instance" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Package" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Package$MyListWrapper" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Person" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Project" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.Region" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.model.relationship.ProjectMembership" />
    <oxm:class-to-be-bound name="org.skydingo.skybase.web.jit.JitNode" />

    ... and so forth ...

</oxm:jaxb2-marshaller>

Every time we add a new entity to the system, we have to go back to the list and add a corresponding entry. Blech.

About a year back I discovered a gem by Jarno Walgemoed called ClasspathScanningJaxb2Marshaller. Actually, I don’t remember if that’s its exact name, because his blog post no longer exists, but that’s pretty close if not exactly it. At any rate, it uses Spring’s classpath scanning mechanism (you know, the one that finds @Component and @Repository and so forth) to find classes annotated with @XmlRootElement, and then JAXB-binds them.

[EDIT: I found the original blog post after all. — WLW]

Here’s a slightly modified version of Jarno’s class, reposted with his kind permission. (Thanks Jarno!) I’ve used it on multiple projects and it works great.

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.xml.bind.annotation.XmlRootElement;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

public class ClasspathScanningJaxb2Marshaller extends Jaxb2Marshaller {
    private static final Logger log = LoggerFactory.getLogger(ClasspathScanningJaxb2Marshaller.class);
    
    private List basePackages;
    
    public List getBasePackages() { return basePackages; }
    
    public void setBasePackages(List basePackages) { this.basePackages = basePackages; }
    
    @PostConstruct
    public void init() throws Exception {
        setClassesToBeBound(getXmlRootElementClasses());
    }
    
    private Class[] getXmlRootElementClasses() throws Exception {
        ClassPathScanningCandidateComponentProvider scanner =
            new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(XmlRootElement.class));
        
        List<Class> classes = new ArrayList<Class>();
        for (String basePackage : basePackages) {
            Set definitions = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition definition : definitions) {
                String className = definition.getBeanClassName();
                log.info("Found class: {}", className);
                classes.add(Class.forName(className));
            }
        }
        
        return classes.toArray(new Class[0]);
    }
}

Comments