views:

582

answers:

3

I want to know what actually happens when you annotate a method with @Transactional? Of course, I know that Spring will wrap that method in a Transaction.

But, I have the following doubts:

  1. I heard that Spring creates a proxy class? Can someone explain this in more depth. What actually resides in that proxy class? What happens to the actual class? And how can I see Spring's created proxied class
  2. I also read in Spring docs that:

Note: Since this mechanism is based on proxies, only 'external' method calls coming in through the proxy will be intercepted. This means that 'self-invocation', i.e. a method within the target object calling some other method of the target object, won't lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!

Source: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Why only external method calls will be under Transaction and not the self-invocation methods. Please explain.

[Yes, I am a newbie and I don't mind accepting the fact that I don't know this]

Cheers !!

+8  A: 

This is a big topic. The Spring reference doc devotes multiple chapters to it. I recommend reading the ones on Aspect-Oriented Programming and Transactions, as Spring's declarative transaction support uses AOP at its foundation.

But at a very high level, Spring creates proxies for classes that declare @Transactional on the class itself or on members. The proxy is mostly invisible at runtime. It provides a way for Spring to inject behaviors before, after, or around method calls into the object being proxied. Transaction management is just one example of the behaviors that can be hooked in. Security checks are another. And you can provide your own, too, for things like logging. So when you annotate a method with @Transactional, Spring dynamically creates a proxy that implements the same interface(s) as the class you're annotating. And when clients make calls into your object, the calls are intercepted and the behaviors injected via the proxy mechanism.

Transactions in EJB work similarly, by the way.

As you observed, through, the proxy mechanism only works when calls come in from some external object. When you make an internal call within the object, you're really making a call through the "this" reference, which bypasses the proxy. There are ways of working around that problem, however. I explain one approach in this forum post in which I use a BeanFactoryPostProcessor to inject an instance of the proxy into "self-referencing" classes at runtime. I save this reference to a member variable called "me". Then if I need to make internal calls that require a change in the transaction status of the thread, I direct the call through the proxy (e.g. "me.someMethod()".) The forum post explains in more detail. Note that the BeanFactoryPostProcessor code would be a little different now, as it was written back in the Spring 1.x timeframe. But hopefully it gives you an idea. I have an updated version that I could probably make available.

Rob H
>> The proxy is mostly invisible at runtimeOh !! I am curious to see them :) Rest.. your answer was very comprehensive. This is the second time you are helping me..Thanks for all the help.
peakit
No problem. You can see the proxy code if you step through with a debugger. That's probably the easiest way. There's no magic; they're just classes within the Spring packages.
Rob H
And if the method which has the @Transaction annotation is implementing an interface the spring will use the dynamic proxy API to inject the transactionalisation and _not_ use proxies. I prefer to have my transactionalised classes implement interfaces in any case.
Michael Wiles
+2  A: 

When Spring loads your bean definitions, and has been configured to look for @Transactional annotations, it will create these proxy objects around your actual bean. These proxy objects are instances of classes that are auto-generated at runtime. The default behaviour of these proxy objects when a method is invoked is just to invoke the same method on the "target" bean (i.e. your bean).

However, the proxies can also be supplied with interceptors, and when present these interceptors will be invoked by the proxy before it invokes your target bean's method. For target beans annotated with @Transactional, Spring will create a TransactionInterceptor, and pass it to the generated proxy object. So when you call the method from client code, you're calling the method on the proxy object, which first invokes the TransactionInterceptor (which begins a transaction), which in turn invokes the method on your target bean. When the invocation finishes, the TransactionInterceptor commits/rolls back the transaction. It's transparent to the client code.

As for the "external method" thing, if your bean invokes one of its own methods, then it will not be doing so via the proxy. Remember, Spring wraps your bean in the proxy, your bean has no knowledge of it. Only calls from "outside" your bean go through the proxy.

Does that help?

skaffman
>Remember, Spring wraps your bean in the proxy, your bean has no knowledge of it**This said it all. What a great answer. Thanks for helping.**
peakit
A: 

Well, the "me" solution is against the idea of transparency of proxy. To me, it is more a workaround than a solution. I want something that if I call a method annotated with Transaction, I want the transaction, calling from external or not.

So I write my own version of CglibSubclassingInstantiationStrategy and proxy creator so that it will use CGLIB to generate a real subclass which delegates call to its super rather than another instance, which Spring is doing now. So I can freely annotate on any methods(as long as it is not private), and from wherever I call these methods, they will be taken care of. Well, I still have price to pay: 1. I must list all annotations that I want to enable the new CGLIB sub class creation. 2. I can not annotate on a final method since I am now generating subclass, so a final method can not be intercepted.

If you are interested, I can post my code. ping me at houyunf at hotmail dot com. (it only works for a specific spring version, my original code breaks when migrate from spring 2.5.2 to 2.5.4, and I can see it will also break at Spring 3.0 because my implementation depends on a lot of Spring internal implementation knowledge, and since the Spring impl changes every release, my code might not work anymore).

Yunfeng Hou