Spring pitfalls: proxying
Join the DZone community and get the full member experience.
Join For FreeBeing a Spring framework user and enthusiast for many years I came across several misunderstandings and problems with this stack. Also there are places where abstractions leak terribly and to effectively and safely take advantage of all the features developers need to be aware of them. That is why I am starting a Spring pitfalls series. In the first part we will take a closer look at how proxying works.
Java dynamic proxies
CGLIB generated classes
The downside of Java dynamic proxies is a requirement on the bean to implement at least one interface. CGLIB works around this limitation by dynamically subclassing the original bean and adding interception logic directly by overriding every possible method. Think of it as subclassing the original class and calling super version amongst other things:class DAO {
def findBy(id: Int) = //...
}
class DAO$EnhancerByCGLIB extends DAO {
override def findBy(id: Int) = {
startTransaction
try {
val result = super.findBy(id)
commitTransaction()
result
} catch {
case e =>
rollbackTransaction()
throw e
}
}
}
However, this pseudocode does not
illustrate how
it works in reality – which introduces yet another problem, stay
tuned. BTW all examples will be in Scala, live with that and get used
to it.
AspectJ weaving
This is the most invasive but also the most reliable and intuitive solution from the developer perspective. In this mode interception is applied directly to your class bytecode which means the class your JVM runs is not the same as the one you wrote. AspectJ weaver adds interception logic by directly modifying your bytecode of your class, either during build – compile time weaving (CTW) or when loading a class – load time weaving (LTW).If you are curious how AspectJ magic is implemented under the hood, here is a decompiled and simplified .class file compiled with AspectJ weaving beforehand:
public void inInterfaceTransactional()
{
try
{
AnnotationTransactionAspect.aspectOf().ajc$before$1$2a73e96c(this, ajc$tjp_2);
throwIfNotInTransaction();
}
catch(Throwable throwable)
{
AnnotationTransactionAspect.aspectOf().ajc$afterThrowing$2$2a73e96c(this, throwable);
throw throwable;
}
AnnotationTransactionAspect.aspectOf().ajc$afterReturning$3$2a73e96c(this);
}
trait FooService {
def inInterfaceTransactional()
def inInterfaceNotTransactional();
}
@Service
class DefaultFooService extends FooService {
private def throwIfNotInTransaction() {
assume(TransactionSynchronizationManager.isActualTransactionActive)
}
def publicNotInInterfaceAndNotTransactional() {
inInterfaceTransactional()
publicNotInInterfaceButTransactional()
privateMethod();
}
@Transactional
def publicNotInInterfaceButTransactional() {
throwIfNotInTransaction()
}
@Transactional
private def privateMethod() {
throwIfNotInTransaction()
}
@Transactional
override def inInterfaceTransactional() {
throwIfNotInTransaction()
}
override def inInterfaceNotTransactional() {
inInterfaceTransactional()
publicNotInInterfaceButTransactional()
privateMethod();
}
}
trait SpringRule extends AbstractSuite { this: Suite =>
abstract override def run(testName: Option[String], reporter: Reporter, stopper: Stopper, filter: Filter, configMap: Map[String, Any], distributor: Option[Distributor], tracker: Tracker) {
new TestContextManager(this.getClass).prepareTestInstance(this)
super.run(testName, reporter, stopper, filter, configMap, distributor, tracker)
}
}
@RunWith(classOf[JUnitRunner])
@ContextConfiguration
class DefaultFooServiceTest extends FunSuite with ShouldMatchers with SpringRule{
@Resource
private val fooService: FooService = null
test("calling method from interface should apply transactional aspect") {
fooService.inInterfaceTransactional()
}
test("calling non-transactional method from interface should start transaction for all called methods") {
fooService.inInterfaceNotTransactional()
}
}
class DAO$EnhancerByCGLIB extends DAO {
val target: DAO = ...
override def findBy(id: Int) = {
startTransaction
try {
val result = target.findBy(id)
commitTransaction()
result
} catch {
case e =>
rollbackTransaction()
throw e
}
}
}
@RunWith(classOf[JUnitRunner])
@ContextConfiguration
class DefaultFooServiceTest extends FunSuite with ShouldMatchers with SpringRule {
@Resource
private val fooService: DefaultFooService = null
test("calling method from interface should apply transactional aspect") {
fooService.inInterfaceTransactional()
}
test("calling non-transactional method from interface should start transaction for all called methods") {
fooService.inInterfaceNotTransactional()
}
test("calling transactional method not belonging to interface should start transaction for all called methods") {
fooService.publicNotInInterfaceButTransactional()
}
test("calling non-transactional method not belonging to interface should start transaction for all called methods") {
fooService.publicNotInInterfaceAndNotTransactional()
}
}
From http://nurkiewicz.blogspot.com/2011/10/spring-pitfalls-proxying.html
Opinions expressed by DZone contributors are their own.
Comments