Moving from Spring's XML to Annotations in AppFuse

Last night, I did a spike on AppFuse to change XML to Spring annotations (@Repository, @Service and @Autowired) in its service and data modules. While I was able to accomplish everything in a few hours (including converting tests), I did run into a couple issues.

AbstractTransactionalJUnit4..Tests vs. AbstractTransactionalDataSource..Tests
I've switched from my favorite Spring class to the annotation-happy AbstractTransactionalJUnit4SpringContextTests. However, this has presented an issue: when using ATDSSCT, I was able to call endTransaction() and startNewTransaction(). With ATJ4SCT, this doesn't seem possible. Below is a screenshot of the diff on a test method in the JPA implementation of UserDaoTest:

AbstractTransactionalJUnit4SpringContextTests vs. AbstractTransactionalDataSourceSpringContextTests

On the right, you'll notice that I had to comment out @ExpectedException to get the test to pass. This concerns me since this exception should be thrown. Is there a way to call endTransaction() and startNewTransaction() when subclassing AbstractTransactionalJUnit4SpringContextTests?

Instantiating GenericDao Implementations Programmatically
The second feature I tried to add is the ability to instantiate a GenericDao programatically rather than requiring a XML bean definition. In current versions of AppFuse, you can use the following bean definition to create a GenericDao for a model object.

<bean id="personDao" class="org.appfuse.dao.hibernate.GenericDaoHibernate">
    <constructor-arg value="org.appfuse.tutorial.model.Person"/> 
    <property name="sessionFactory" ref="sessionFactory"/>
</bean> 

When moving to a no-XML required architecture, it'd be nice to allow users to create GenericDao's programmatically. Below is the easiest way I've found to do this in a test:

GenericDao<User, Long> genericDao;
@Autowired
SessionFactory sessionFactory;

@Before
public void setUp() {
    genericDao = new GenericDaoHibernate<User, Long>(User.class);
    genericDao.setSessionFactory(sessionFactory);
}

However, there's a couple problems with this. First of all, mixing constructor injection and setter injection probably isn't a good idea. Changing the constructor to take a SessionFactory solves this problem, but now all subclasses need to have a more verbose constructor:

@Autowired
public UserDaoHibernate(SessionFactory sessionFactory) {
    super(User.class, sessionFactory);
}

Whereas before they had:

public UserDaoHibernate() {
    super(User.class);
}

In an ideal world, I could call new GenericDaoHibernate<User, Long>(User.class) and the SessionFactory would be wired in auto-magically. Is this possible with Spring 2.5?

The 2nd problem this presents is your client code will now be dependent on an implementation rather than the interface. I don't know how to solve that one, but I'd love to figure out a way to create GenericDaos with no XML and no implementation details in the client. Any ideas are most welcome.

If you'd like to see all the changes I made in converting from XML to Annotations, please see this patch.

Posted in Java at Nov 04 2008, 11:39:54 AM MST 14 Comments
Comments:

hi Matt,

did you try the Spring testing framework? I've used in my last project and it seems to me that it really eases the process of performing integration testing... this is the url if you haven't seen it:

http://static.springframework.org/spring/docs/2.5.x/reference/testing.html#testcontext-tx

Posted by Patria on November 04, 2008 at 01:44 PM MST #

hi!!!
i'm in the same case i have an abstractDao:

@Repository
public class AbstractPersistenceDaoGenericHibernate<T, PK extends Serializable>
        extends HibernateDaoSupport implements PersistenceDao<T, PK> {
    
    private Class<T> type = null;

    @Autowired
    public AbstractPersistenceDaoGenericHibernate(HibernateTemplate hibernateTemplate){
        super.setHibernateTemplate(hibernateTemplate);
    }
    
    @Transactional
    public void create(T newInstance) {
        this.getHibernateTemplate().save(newInstance);
    }

and my custom impl are like:

@Repository("clienteDao")
public class ClienteDaoHibernateImpl extends AbstractPersistenceDaoGenericHibernate<Cliente, Long> implements ClienteDao {

    @Autowired
    public ClienteDaoHibernateImpl(HibernateTemplate hibernateTemplate) {
        super(hibernateTemplate);
    }

    @Transactional(readOnly=true)
    public List<Cliente> getClientesByCriteria(Cliente clienteCriteria) {
        Criteria criteria = getSession().createCriteria(Cliente.class);
        if(clienteCriteria.getNombre()!=null)
            criteria.add(Restrictions.like("nombre", "%"+clienteCriteria.getNombre()+"%"));
        if(clienteCriteria.getDireccion()!=null)
            criteria.add(Restrictions.like("direccion", "%"+clienteCriteria.getDireccion()+"%"));
        if(clienteCriteria.getRFC()!=null)
            criteria.add(Restrictions.like("RFC", "%"+clienteCriteria.getRFC()+"%"));
        return criteria.list();
    }    

and finally in the test case:

public class ClienteDaoTest extends
        AbstractDependencyInjectionSpringContextTests {

    @Autowired
    ClienteDao clienteDao;
    
    @Override
    protected String[] getConfigLocations() {
        return new String[]{"PersistenceAppCtx.xml"};
    }
    
    public void testClienteDao1(){
        List<Cliente> clientes = clienteDao.findAll();
        for(Cliente cliente:clientes){
            System.out.println(ToStringBuilder.reflectionToString(cliente));
        }
    }

I have some services that depends of this Dao and I have with JUnit4 like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:PersistenceAppCtx.xml","classpath:ServicesAppCtx.xml","classpath:TxAppCtx.xml"})
@TransactionConfiguration(transactionManager="transactionManager")
@Transactional
public class TestClienteService {

    @Autowired
    ClienteService clienteService;
    
    @BeforeTransaction
    public void verificarEstadoInicial(){
        System.out.println("<!------------------------Antes de la transacción------------------------>");
    }
    
    @AfterTransaction
    public void verificarEstadoFinal(){
        System.out.println("<!***********************Despues de la transacción************************>");
    }
    
    @Test
    public void pruebaClienteService1(){
        try {
            clienteService.getAllClientes();
        } catch (BusinessException e) {
            System.err.println(e.getMessage());
        }
    }

Maybe this be useful for you, i'll hope so...

I don't have inconvenient with this way...

Regards
neodevelop

Posted by neodevelop on November 04, 2008 at 01:51 PM MST #

What's the name of that diff tool for OSX?

Posted by Jordi Bunster on November 04, 2008 at 03:25 PM MST #

@Jordi - it's IntelliJ IDEA. ;-)

Posted by Matt Raible on November 04, 2008 at 05:13 PM MST #

Matt,

For programmatic creation and autowiring in combination, you can use @Configurable along with @Autowired, such as:

@Configurable
public class GenericDaoHibernate {

   @Autowired
   private volatile SessionFactory sessionFactory;
   ...
}

In the Application Context configuration, you'll need to add the 'load-time-weaver' element in addition to 'annotation-config', and you'll need to provide a prototype-scoped bean whose type matches the programmatically created objects:

<context:annotation-config/>
<context:load-time-weaver/>

<bean class="appfuse.GenericDaoHibernate" scope="prototype"/>

There are a couple other requirements (AspectJ JARs, etc.) as described in the Spring 2.5.x Reference Documentation.

Regards,
Mark

Posted by Mark Fisher on November 04, 2008 at 05:43 PM MST #

It is possible to use TransactionTemplate to test the transactions. See http://blog.krecan.net/2007/12/12/testing-with-spring-25 for more details.

Posted by Lukas Krecan on November 05, 2008 at 03:18 AM MST #

Hi Matt,

I've been meaning to try this, but haven't gotten around to it. To address the issue of client code depending on the implementation class, I think one possibility would be to eliminate the constructor params from the generic DAO. If Spring's autowiring goes as far as inferring the type parameters (see Spring's GenericTypeResolver) then you might be able to obtain the params like this:

//Default constructor with no args
public GenericDaoHibernate() {
   persistentClass = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}

Do you think this would work? I'd be interested to know if anybody has tried it.

Chris.

Posted by Chris Herron on November 05, 2008 at 10:06 AM MST #

Any idea when Appfuse 2.0.3 will be released?

Posted by Marc on November 29, 2008 at 10:14 AM MST #

@Marc - I'm not planning on releasing 2.0.3. 2.1 is next on the roadmap. I hope to release it sometime in January 2009.

Posted by Matt Raible on December 10, 2008 at 10:38 AM MST #

Looking forward to it! Meanwhile, I'll play with the Snapshots.

Posted by Marc on December 10, 2008 at 10:44 AM MST #

Matt

Do you have any plans to switch client side validation to something annotation based?

Here is a good example in case you are curious.
http://wheelersoftware.com/articles/spring-bean-validation-framework.html

Posted by Alex on January 12, 2009 at 06:39 PM MST #

@Alex - I don't mind switching to a annotation-based validation solution, but I'm not too keen on putting framework-specific annotations in model objects. I'd prefer to use standard annotations like those provided with JSR 303.

Also, your solution doesn't seem to provide client-side validation whereas AppFuse's current solution (Commons Validator for Spring MVC) provides both client and server-side validation.

Posted by Matt Raible on January 12, 2009 at 07:01 PM MST #

I'm trying to secure the managers and found that Spring Security has an annotation for this (@Secured http://acegisecurity.org/acegi-security-tiger/apidocs/org/acegisecurity/annotation/Secured.html) only problem is that I can't get it to work. My guess is that I need some more configuration for the proxy beans to be generated with security interception. Any idea on how to achieve this?

Posted by Christian Decker on January 14, 2009 at 02:09 PM MST #

@Alex - I don't mind switching to a annotation-based validation solution, but I'm not too keen on putting framework-specific annotations in model objects. I'd prefer to use standard annotations like those provided with JSR 303. Also, your solution doesn't seem to provide client-side validation whereas AppFuse's current solution (Commons Validator for Spring MVC) provides both client and server-side validation. i think there is clientside validation with annotations. atleast that is what we have now.

Posted by tibi on August 06, 2009 at 02:03 PM MDT #

Post a Comment:
  • HTML Syntax: Allowed