Wednesday, May 16, 2007

Generic Daos without all the xml

I was real happy when http://www-128.ibm.com/developerworks/java/library/j-genericdao.html came out. It seemed ideal until a few days and 50 daos later I had spent far too much time repeating the same xml over and over again. Finally with a little help from some new classes in spring 2.1 we can get rid of all the repetitous xml. First we need to scan for all the applicable dao interfaces. ClassPathScanningCandidateComponentProvider would be perfect except it specfically disallows returning interfaces. So the the first step was to create InterfaceComponentProvider which is essentially a copy of the original class except it will match and return interfaces.

WIthin the spring core there are TypeFilters that match on all sorts of things. The best choice seemed to be AbstractTypeHierarchyTraversingFilter. DaoFilter extends this and looks for any class which extends/implements the GenericDao interface.


import genericdao.GenericDao;

import org.springframework.core.typefilter.AbstractTypeHierarchyTraversingFilter;

/**
* TypeFilter used to match classes which implement the GenericDao interface
*
* @author Dan Thiffault
*
*/
public class DaoFilter extends AbstractTypeHierarchyTraversingFilter {
private Class daoInterfaceClass = GenericDao.class;

public DaoFilter() {
super(true, true);
}

@Override
protected boolean matchInterfaceName(String arg0) {
return daoInterfaceClass.getName().equals(arg0);
}

public Class getDaoInterfaceClass() {
return daoInterfaceClass;
}

public void setDaoInterfaceClass(Class daoInterfaceClass) {
this.daoInterfaceClass = daoInterfaceClass;
}

}


Now that we can discover all the appropriate beans we need a way to register them. First step is to extract the runtime type info from the discover beans.



public void scan() {
DaoFilter filter = new DaoFilter();
componentProvider.addIncludeFilter(filter);

Set candidateClasses = componentProvider.findCandidateComponents();

for(Class candidateDao : candidateClasses) {
for(Type interfaceType : candidateDao.getGenericInterfaces()) {
if(interfaceType instanceof ParameterizedType) {
ParameterizedType pInterfaceType = (ParameterizedType) interfaceType;
if(filter.getDaoInterfaceClass().equals(pInterfaceType.getRawType())) {
Type genericType = pInterfaceType.getActualTypeArguments()[0];
Assert.isInstanceOf(Class.class, genericType);
registerDao(candidateDao, (Class) genericType);
}
}
}
}

}


You may wonder what that registerDao method does. It creates the two required beans from the original Don't repeat the Dao article. Here's that method:


protected void registerDao(Class daoInterface, Class entityClass) {
//Create the target child bean
ConstructorArgumentValues cav = new ConstructorArgumentValues();
cav.addGenericArgumentValue(entityClass);
BeanDefinition targetBeanDefinition = new ChildBeanDefinition("abstractDaoTarget", cav, null);

//Create the proxy child bean
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("proxyInterfaces", daoInterface);
propertyValues.addPropertyValue("target", targetBeanDefinition);
BeanDefinition proxyBeanDefinition = new ChildBeanDefinition("abstractDao", propertyValues);

registry.registerBeanDefinition(daoInterface.getSimpleName(), proxyBeanDefinition);
}


For the last step our new class implements BeanFactoryPostProcessor so it automatically registers beans at startup. Now we just need one line of xml per application instead of many per dao. Get the full code from here:

http://www.gspiral.com/autoDao.tgz