Spring AOP riddle
Spring support for aspect oriented programming is very wide, but sometimes you may shoot yourself in the foot if you are not careful enough. Consider the following service interface:
public interface FoobarService {
void foo();
void bar();
}
…and its implementation:
public class DefaultFoobarService implements FoobarService {
@Override
@Transactional
public void foo() {
//some code requiring active transaction
}
@Override
public void bar() {
foo();
}
}
To keep things simple, assume that
foo()
throws exception if not run in
context of active transaction. Since
main()
is so old-school, we’re going
to test both methods through test case:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DefaultFoobarServiceTest {
@Autowired
private FoobarService foobarService;
@Test
public void testFoo() {
foobarService.foo();
}
@Test
public void testBar() {
foobarService.bar();
}
}
Assuming testFoo()
succeeded, will
testBar()
succeed as well? The trick
p
t is transaction propagation…
…
The problem with bar()
method is
that it is not marked as transactional, but since it calls transactional
method foo()
, you may expect that
foo()
is called within a
transaction… but it’s not! And since
foo()
called from
bar()
isn’t wrapped in a
transaction, foo()
will throw a
exception and the test will fail.
To explain this odd behavior, you must first understand how aspects
(including declarative transactions) are applied to Spring beans. When
we mark any of beans’ methods with
\@Transactional
annotation, Spring automatically wraps the bean in Java dynamic proxy.
This proxy intercepts all method calls from other beans and if it
encounters method marked as transactional, it performs special,
transaction related routines (linking to existing or creating new
transaction, rolling back on exception, etc.) Of course, the advice
calls original method of wrapped object during its execution (like
foo()
). So as long as you are
running foo(), Spring handles transactions transparently and smoothly.
But think what happens when you call
bar()
method. Method is not marked
as transactional, so even though transactional proxy will intercept the
call, it will soon discover that method does not require transaction and
simply delegate to original method
bar()
. Then the method invokes
foo()
. This is the place, where we
expect the transaction to be started, but wait! – we are invoking
foo()
method on this reference,
which points directly to the bean, not transactional aspect proxy.
Spring does not know anything about this call, since you are calling
method on Java object (POJO), not on a proxy instance wrapping this
object. Declarative transaction management won’t be applied and
foo()
will throw unexpected
exception.
The answer is pretty obvious if you understand the mechanics of Spring
AOP, because the same problem will occur in any code using aspects, not
only transactions and not only via annotations. In fact, also the same
problem has been solved in EJB by using
SessionContext.getBusinessObject()
method – passing this is forbidden by the specification. In Spring, the
easiest way to avoid this particular bug in our code is to simply
a
otate bar()
method as well.
- If our bean does not implement any business interface, CGLIB will be
used instead. Also, you must use
<tx:annotation-driven />
to make this magic happen.
UPDATE:
- More details: Spring AOP riddle demystified