Friday, April 24, 2009

Self Injection for Fun and Profit

The Spring Framework (www.springsource.org) is an amazing piece of software. It has really changed the way that I code. It's dependancy injection features make decoupling and junit testing much easier than when using factories or service locators. The best thing about Spring I've found is declarative transaction management. Once configured, it's as easy as adding the @Transactional annotation to the method that you want be be transactional. When the method is called, it is intercepted by a proxy that wraps the method call in a transaction. On entry, a transaction is started. If your method completes normally, the transaction is commited. If your method throws a runtime exception, then the transaction is rolled back. Way easy.

This ease of use can be like a drug however. You still need to make sure that your transactions are short lived, to avoid contention on the database and resulting dropped locks. The other thing that you need to remember is that you can only apply @Transactional to public methods exposed using an Interface type. This is because Spring creates a dynamic proxy based on the Interface type. You aren't directly calling your implementation, your calling a proxy that calls your implementation. Errors associated with this can be hard to find and confusing.

@Transactional has been so easy to use that I had not dived into AOP itself. However, once I started running into problems, I found that it is really good to understand how Spring uses dynamic proxies and AOP to implement the @Transactional behavior. The Spring documentation is really amazingly good. Better than any software package I have ever used, open source or commercial. See chapter 6 of the Spring Reference for a discusion of AOP. Goto pg 158 for a discussion of how calls to the proxy and calls within the proxied bean are handled. This discussion is useful to understand the problems that I discuss below.

The most important thing you have to know is that your bean must implement an Interface type for any of this magic to work. Secondly, you must use Spring retrieve your bean, either through dependancy injection or using ApplicationContext.getBean(). Together, these two steps give Spring an opportunity to provide you with a dynamic proxy based on the Interface type. When you make calls to a bean created this way, you are really calling the proxy, which then calls your bean. The proxy does all the magic.

A common error is to forget to create an Iterface type and make your bean implement the interface. If your bean is not an implementation of an interface, Spring cannot create a proxy. Instead, it will simply new() a version of the class directly and will not create a proxy. In this case, any @Transactional annotation in your bean is non-funcation. Worse, you will not get an error or warning. Your method will simply not be transactional.

A similar issue happens if you mark a method as @Transactional, but that method is not part of the Interface type. Because it is not part of the declared interface, Spring does not proxy it. This case can be even more confusing because some @Transactional methods, those that are part of the Interface type, will work correctly. Those that are not part of the Interface type will not work and you will not get an error or warning.

This next thing may seem obvious, but legacy code may be involved so you may not even know this is happening. You must inject your bean using Spring or make a call to ApplicationContext.getBean() to retrieve the bean so it can be proxied by Spring. If you new() your bean rather than inject it using Spring, Spring will never be able to get in the way and create a proxy, so any @Transactional annotation in the bean is useless. Again, you will not be warned in any way. Your method will simply not be transactional.

This last error is a bit of a mind-blower. Calls to your bean from other classes will be proxied correctly. However, if you call your bean from within one of it's own methods, it will _not_ be proxied. This is because external calls are really calling through the proxy. However, internal calls are calling through the 'this' reference and so are 'inside' the proxy. So, if you call an @Transactional method from another method in the same bean, the call will not be tranacational because it does not go through the proxy. The same thing is true of calls down into the super() class because these are also made through the 'this' reference.

One way to fix this issue is to refactor and break out the @Transactional methods into their own bean which can be proxied. That can be a bunch of work and may mess up your well designed bean. The way I solved my problem is slightly icky, but it works well. Since my bean is a singleton, I can inject a proxied copy of the bean into itself using the spring configuration. I simply add a field that is based on the bean's own Interface type and inject it. I then make internal bean calls to transactional methods through the injected reference and all is well.
The example below uses self-injection so it can call proxied versions of it's own methods through the thisProxy field. Here is the bean definition:


   <bean id="foo" class="Foo" >
        <property name="foo" ref="foo" />
   </bean>



Here is how a intra-class proxied call would look:



public class FooImpl implements Foo
{
    private Foo thisProxy; // this get's injected by Spring
    public setFoo(Foo proxiedFoo)
    {
         thisProxy = proxiedFoo;
    }
    public Foo()
    {
    }
    public void someFoo()
    {
        thisProxy.someTransactionalFoo();
    }
    @Transactional
    public void someTransactionalFoo()
    {
        // fooing within a transaction....
    }
}


Don't try this if you use scope=prototype however. In that case, your injected thisProxy will be pointing to a different copy of Foo!

No comments:

Post a Comment