Backend Development 32 min read

Unveiling Spring AOP: A Deep Dive into Source Code and Proxy Mechanics

This article provides a comprehensive analysis of Spring AOP, covering its core concepts, configuration parsing, BeanFactoryPostProcessor and BeanPostProcessor mechanisms, namespace handling, the creation of advisors and advice from @Aspect annotations, and the proxy generation process that enables method interception.

Architecture & Thinking
Architecture & Thinking
Architecture & Thinking
Unveiling Spring AOP: A Deep Dive into Source Code and Proxy Mechanics

1 Overview

Spring AOP (Aspect‑Oriented Programming) works on top of the IoC container. Its main feature is intercepting and enhancing specified methods without modifying business code, thus separating business logic from cross‑cutting concerns. In Spring, using transaction annotations automatically opens, commits, or rolls back transactions around business methods.

Match methods that satisfy a pointcut.

Enhance matched methods via JDK dynamic proxies or CGLIB proxies.

2 Case Study

Below is a simple Spring AOP example used for source‑code analysis.

<code>@Aspect
@Component
public class TracesRecordAdvisor {
    @Pointcut("execution(* spring.action.expend.aop.services.*.*(..))")
    public void expression() {}

    @Before("expression()")
    public void beforePrint() {
        System.out.println("Entering service, logging before execution...");
    }

    @AfterReturning("expression()")
    public void afterPrint() {
        System.out.println("Exiting service, logging after execution...");
    }
}
</code>

XML configuration to enable AOP:

<code>&lt;aop:aspectj-autoproxy/&gt;</code>

Service class (requires an interface for JDK proxy):

<code>@Service
public class PayServiceImpl implements PayService {
    public void payMoneyMethod() {
        System.out.println("Executing payment...");
    }
}
</code>

Test method:

<code>@Test
public void springAopTestService() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-aop.xml");
    PayService payService = (PayService) ctx.getBean("payServiceImpl");
    payService.payMoneyMethod();
}
</code>

Result:

<code>Entering service, logging before execution...
Executing payment...
Exiting service, logging after execution...
</code>

The output shows that payMoneyMethod has been enhanced.

3 BeanFactoryPostProcessor

When reading Spring source code, the BeanFactoryPostProcessor and BeanPostProcessor interfaces allow us to modify BeanDefinition objects after they are parsed from XML but before beans are instantiated.

<code>public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}
</code>

The postProcessBeanFactory method receives a ConfigurableListableBeanFactory , which can access all generated BeanDefinition objects. Implementations are registered in the container and invoked during the refresh process.

<code>protected void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try {
            postProcessBeanFactory(beanFactory);
            invokeBeanFactoryPostProcessors(beanFactory);
            registerBeanPostProcessors(beanFactory);
            // other initialization steps …
        } catch (BeansException ex) {
            throw ex;
        } finally {
            resetCommonCaches();
        }
    }
}
</code>

invokeBeanFactoryPostProcessors iterates over all registered BeanFactoryPostProcessor instances and calls their postProcessBeanFactory method.

4 BeanPostProcessor

Compared with BeanFactoryPostProcessor , BeanPostProcessor is more important because Spring AOP, annotations, etc., rely on it. It intercepts the bean lifecycle after instantiation and before/after initialization.

<code>public interface BeanPostProcessor {
    // Called after bean instantiation and property population, before init methods
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    // Called after init methods (e.g., @PostConstruct, afterPropertiesSet)
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
</code>

Bean creation sequence:

Instantiate bean (constructor or factory method).

Populate properties.

Invoke Aware interfaces.

postProcessBeforeInitialization .

Invoke init methods.

postProcessAfterInitialization .

The AnnotationAwareAspectJAutoProxyCreator implements both BeanPostProcessor and InstantiationAwareBeanPostProcessor , handling AOP logic during these callbacks.

5 NamespaceHandler

All Spring technologies, including AOP, are built on the IoC container. When the XML parser encounters a tag, it looks up the namespace URI, finds the corresponding NamespaceHandler , and delegates parsing to it.

For the &lt;aop:aspectj-autoproxy/&gt; tag, the handler is AopNamespaceHandler , which registers parsers for config and aspectj-autoproxy elements.

<code>public class AopNamespaceHandler implements NamespaceHandler {
    @Override
    public void init() {
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
    // other methods omitted …
}
</code>

The AspectJAutoProxyBeanDefinitionParser simply registers the AnnotationAwareAspectJAutoProxyCreator class.

6 Spring AOP Source Analysis

When Spring reads an XML file containing &lt;aop:aspectj-autoproxy/&gt; , it loads AopNamespaceHandler , which delegates to AspectJAutoProxyBeanDefinitionParser . That parser registers AnnotationAwareAspectJAutoProxyCreator , which implements BeanPostProcessor and InstantiationAwareBeanPostProcessor . The proxy creation flow is:

During bean instantiation, postProcessBeforeInstantiation runs.

After bean initialization, postProcessAfterInitialization runs.

These two callbacks contain the core AOP logic.

7 Annotation Proxy Class Analysis

The class AnnotationAwareAspectJAutoProxyCreator caches aspect classes and infrastructure classes, decides whether a bean should be proxied, and creates the proxy when needed.

<code>public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    Object cacheKey = getCacheKey(beanClass, beanName);
    if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null; // already processed as advice
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }
    if (beanName != null) {
        TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            this.targetSourcedBeans.add(beanName);
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
    }
    return null;
}
</code>

The method first checks whether the class is an aspect or infrastructure class, then whether it should be skipped, and finally creates a proxy if custom TargetSource is present.

8 Aspect Annotation Class Analysis

The postProcessBeforeInstantiation method uses isInfrastructureClass to filter out classes that implement Advice , Advisor , or AopInfrastructureBean . It also checks for the @Aspect annotation.

<code>@Override
protected boolean isInfrastructureClass(Class<?> beanClass) {
    return super.isInfrastructureClass(beanClass) || this.aspectJAdvisorFactory.isAspect(beanClass);
}
</code>

The parent implementation checks assignability to Advice , Advisor , or AopInfrastructureBean .

9 Getting Advisors for an Aspect

The method getAdvisor builds an Advisor for a given aspect method.

<code>@Override
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
        int declarationOrderInAspect, String aspectName) {
    AspectJExpressionPointcut pointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    if (pointcut == null) return null;
    return new InstantiationModelAwarePointcutAdvisorImpl(pointcut, candidateAdviceMethod,
            this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
</code>

getPointcut extracts the pointcut expression from annotations such as @Before , @After , etc.

10 Creating Advice for an Aspect Method

Inside ReflectiveAspectJAdvisorFactory.getAdvice , the annotation type determines which concrete Advice implementation is instantiated.

<code>public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    AspectJAnnotation<?> ajAnnotation = findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (ajAnnotation == null) return null;
    AbstractAspectJAdvice springAdvice;
    switch (ajAnnotation.getAnnotationType()) {
        case AtBefore:
            springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfter:
            springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfterReturning:
            springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning ar = (AfterReturning) ajAnnotation.getAnnotation();
            if (StringUtils.hasText(ar.returning())) {
                springAdvice.setReturningName(ar.returning());
            }
            break;
        case AtAfterThrowing:
            springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing ath = (AfterThrowing) ajAnnotation.getAnnotation();
            if (StringUtils.hasText(ath.throwing())) {
                springAdvice.setThrowingName(ath.throwing());
            }
            break;
        case AtAround:
            springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtPointcut:
            // pointcut methods are ignored here
            return null;
        default:
            throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);
    }
    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrder);
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) {
        springAdvice.setArgumentNamesFromStringArray(argNames);
    }
    springAdvice.calculateArgumentBindings();
    return springAdvice;
}
</code>

Each concrete Advice class implements MethodInvocation and defines when the aspect method is invoked (before, after, around, etc.).

11 AOP Proxy Overview

After all advisors are collected, postProcessAfterInitialization decides whether to create a proxy.

<code>protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}
</code>

If matching advisors are found, a JDK dynamic proxy or a CGLIB proxy is created (depending on configuration), and the bean is replaced with the proxy instance.

12 Summary

Spring AOP performs two main tasks during container startup:

Configuration parsing: The XML tag &lt;aop:aspectj-autoproxy/&gt; triggers AopNamespaceHandler , which registers AnnotationAwareAspectJAutoProxyCreator .

Bean creation: postProcessBeforeInstantiation filters out infrastructure and aspect classes. All beans annotated with @Aspect are scanned; their advice methods are turned into Advisor objects and cached. postProcessAfterInitialization checks whether a bean matches any advisor and, if so, creates a proxy that weaves the aspect logic.

The proxy delegates method calls through a chain of MethodInvocation objects, each representing an advice (before, after, around, etc.), thereby achieving transparent method interception.

Spring AOP class diagram
Spring AOP class diagram
JavaAOPbackend developmentSpringAspectJ
Architecture & Thinking
Written by

Architecture & Thinking

🍭 Frontline tech director and chief architect at top-tier companies 🥝 Years of deep experience in internet, e‑commerce, social, and finance sectors 🌾 Committed to publishing high‑quality articles covering core technologies of leading internet firms, application architecture, and AI breakthroughs.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.