Understanding Spring's @Transactional Annotation: Implementation and AOP Mechanics
This article explains the purpose and inner workings of Spring's @Transactional annotation, detailing how AOP and dynamic proxies create transactional proxies, the role of BeanPostProcessor, advisors, TransactionInterceptor, and the transaction lifecycle, accompanied by code examples and diagrams for deeper insight.
@Transactionalis a Spring annotation for declarative transaction management, allowing automatic handling of transaction begin, commit, and rollback via AOP.
When a method is annotated with @Transactional, Spring defines a pointcut to intercept the method, creates a proxy during bean initialization, and injects the transaction advice.
The proxy creation involves the BeanPostProcessor implementation AnnotationAwareAspectJAutoProxyCreator and advisors such as BeanFactoryTransactionAttributeSourceAdvisor, which determine whether a bean requires a transactional proxy.
During method invocation, the proxy's intercept method ( DynamicAdvisedInterceptor#intercept) builds an interceptor chain that includes TransactionInterceptor, which ultimately calls TransactionInterceptor#invoke.
@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
// Analyze whether the method is marked with @Transactional
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
} else {
return null;
}
} @Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = getTarget();
if (target != null) {
targetClass = target.getClass();
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
} else {
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
} finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
} @Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// Determine target class
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Delegate to TransactionAspectSupport
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
} protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
} else {
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
} catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
} else {
throw new ThrowableHolderException(ex);
}
} else {
return new ThrowableHolder(ex);
}
} finally {
cleanupTransactionInfo(txInfo);
}
}
});
if (result instanceof ThrowableHolder) {
throw ((ThrowableHolder) result).getThrowable();
} else {
return result;
}
} catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}The TransactionInterceptor manages transaction boundaries: it obtains the TransactionAttribute, starts a transaction via the PlatformTransactionManager, proceeds with the target method, and commits or rolls back based on whether an exception occurs.
Overall, the article walks through the source code to illustrate how Spring implements @Transactional using AOP, dynamic proxies, and transaction interceptors, providing a clear mapping from high‑level annotation to low‑level execution flow.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
