Inside Spring AOP: Full Lifecycle of Proxy Object Creation
The article provides a step‑by‑step analysis of Spring AOP’s proxy creation process, explaining why some beans are proxied, how @Order, @EnableAspectJAutoProxy, proxyTargetClass and exposeProxy affect proxying, the differences between JDK and CGLIB proxies, and why private, static or final methods cannot be intercepted.
1. Core Component Hierarchy
Spring AOP relies on a layered set of components:
Startup layer : @EnableAspectJAutoProxy imports AspectJAutoProxyRegistrar, which registers the core post‑processor AnnotationAwareAspectJAutoProxyCreator .
Aspect parsing layer : ReflectiveAspectJAdvisorFactory scans @Aspect classes, parses advice and pointcut expressions, and creates Advisor objects.
Bean post‑processor layer : AnnotationAwareAspectJAutoProxyCreator implements InstantiationAwareBeanPostProcessor. Its key methods are postProcessAfterInitialization and wrapIfNecessary.
Proxy factory layer : ProxyFactory decides the proxy type (JDK or CGLIB) and assembles the proxy.
Bytecode generation layer : JdkDynamicAopProxy (interface proxy) and CglibAopProxy (subclass proxy).
Invocation layer : DefaultAdvisorChainFactory builds the interceptor chain, and ReflectiveMethodInvocation drives the onion‑model execution.
2. @EnableAspectJAutoProxy Source Code
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
// Force CGLIB proxying
boolean proxyTargetClass() default false;
// Expose proxy to ThreadLocal for self‑invocation
boolean exposeProxy() default false;
}The registrar registers the auto‑proxy creator and parses the two attributes. If proxyTargetClass is true, it forces class‑based (CGLIB) proxies; if exposeProxy is true, it stores the current proxy in AopContext for later retrieval.
proxyTargetClass=true : forces all beans to use CGLIB, solving the inability of JDK proxies to intercept class‑specific methods.
exposeProxy=true : puts the proxy into AopContext (a ThreadLocal) so that AopContext.currentProxy() can be used to invoke methods on the proxy, eliminating internal‑call failures.
Spring Boot 2.x enables these settings automatically; no manual annotation is required.
3. Aspect Pre‑Parsing and Caching
During container refresh, Spring scans all @Aspect classes, parses them once, and caches the resulting Advisor objects. This avoids repeated parsing for each bean.
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory factory) {
Class<?> aspectClass = factory.getAspectMetadata().getAspectClass();
List<Advisor> advisors = new ArrayList<>();
// Find all advice methods (exclude pure @Pointcut methods)
List<Method> advisorMethods = getAdvisorMethods(aspectClass);
for (Method method : advisorMethods) {
Advisor advisor = getAdvisor(method, factory);
if (advisor != null) {
advisors.add(advisor);
}
}
return advisors;
}Parsing rules:
Recognize @Before, @After, @AfterReturning, @AfterThrowing, @Around.
Parse @Pointcut expressions into Pointcut matchers.
Wrap "pointcut + advice method + aspect instance" into InstantiationModelAwarePointcutAdvisor.
All advisors are globally cached and reused for every bean.
Note: @Order does not take effect at this stage; sorting happens later when each bean’s proxy is created.
4. Bean Lifecycle – Proxy Creation Point
The full bean lifecycle is:
Instantiation → Property population → Init methods → Bean post‑processors → Singleton registration
Proxy creation occurs in the last step, inside postProcessAfterInitialization:
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// Avoid early or duplicate proxying
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}wrapIfNecessary Core Logic
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 1. Skip infrastructure classes, the aspect itself, and beans that do not need proxying
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
return bean;
}
// 2. Find all advisors that match this bean and sort them by @Order
List<Advisor> eligibleAdvisors = findEligibleAdvisors(bean.getClass(), beanName);
// 3. If there are matching advisors, create a proxy
if (!eligibleAdvisors.isEmpty()) {
Object proxy = createProxy(bean.getClass(), beanName, eligibleAdvisors, bean);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
// No advisors → return original bean
return bean;
}Skipping Rules
isInfrastructureClassexcludes:
Classes annotated with @Aspect (they are infrastructure beans and are not proxied).
All internal AOP utility, callback, and processor classes.
Conclusion: Aspect beans themselves are never proxied, so an aspect cannot intercept its own advice.
5. Proxy Factory – JDK vs CGLIB Decision
protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {
Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());
boolean hasValidInterface = false;
for (Class<?> ifc : targetInterfaces) {
if (ifc.getDeclaredMethods().length > 0) {
hasValidInterface = true;
proxyFactory.addInterface(ifc);
}
}
// Core rule: valid interface && not forced CGLIB → JDK proxy; otherwise CGLIB subclass proxy
if (hasValidInterface && !proxyFactory.isProxyTargetClass()) {
proxyFactory.setInterfaces(targetInterfaces);
} else {
proxyFactory.setProxyTargetClass(true);
}
}JDK Dynamic Proxy (Interface Proxy)
Generates a class that implements the target interfaces; it does not extend the original class.
Only public methods declared in the interfaces can be overridden and intercepted.
Class‑specific public methods, as well as private, static, or final methods, cannot be intercepted.
Invocation entry point: InvocationHandler#invoke.
CGLIB Dynamic Proxy (Subclass Proxy)
Uses ASM to generate a subclass of the target class at runtime.
Overrides all non‑final, non‑static, non‑private instance methods that are accessible.
Final, static, private, or methods without bytecode generation are left untouched, which explains why those three categories cannot be advised.
Invocation entry point: MethodInterceptor#intercept.
6. Interceptor Chain Generation
public List<MethodInterceptor> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class<?> targetClass) {
List<MethodInterceptor> interceptors = new ArrayList<>();
// Iterate over already ordered advisors
for (Advisor advisor : config.getAdvisors()) {
PointcutAdvisor pcAdvisor = (PointcutAdvisor) advisor;
// Dynamically match the current method
if (pcAdvisor.getPointcut().getMethodMatcher().matches(method, targetClass)) {
MethodInterceptor interceptor = this.registry.getInterceptor(advisor);
interceptors.add(interceptor);
}
}
return interceptors;
}7. Onion‑Model Execution – ReflectiveMethodInvocation
The interceptor chain is executed recursively:
Before advice order : outer → inner.
After advice order : inner → outer.
Each interceptor calls proceed() to hand control to the next interceptor; the final call invokes the target method.
If the target method throws an exception, the chain unwinds:
Inner advice captures the exception first (executing @AfterThrowing).
If an inner advice swallows the exception, outer advice never sees it.
8. Replacing the Original Bean with the Proxy
After wrapIfNecessary creates the proxy, Spring stores it in the singleton pool ( singletonObjects). Subsequent injections, controller calls, and context lookups all receive the proxy instance.
Only vulnerability: a self‑call using this bypasses the proxy and executes the original method.
9. Solving Self‑Invocation Failure
Enable exposeProxy=true so that the proxy is stored in AopContext (a ThreadLocal). Business code can then obtain the proxy via AopContext.currentProxy() and invoke methods on it:
// Store proxy in ThreadLocal
AopContext.setCurrentProxy(proxy);
// Business code retrieves proxy
YourService proxy = (YourService) AopContext.currentProxy();
proxy.innerMethod();10. Three Method Types That Cannot Be Advised
private methods : CGLIB subclasses cannot access or override private members; the original method runs unchanged.
static methods : They belong to the class, not the bean instance; instance‑level proxies cannot intercept them.
final methods : CGLIB detects the final modifier during bytecode generation and skips overriding, leaving the method unenhanced.
These limitations are enforced by both Java language rules and bytecode generation; they can only be bypassed with compile‑time weaving (e.g., AspectJ).
Conclusion
Spring AOP is the foundation for transaction management, caching, rate limiting, logging, security, and monitoring. Mastering the complete proxy creation workflow—from annotation processing, advisor parsing, proxy factory decisions, to interceptor chain execution—is essential for any advanced backend engineer working with Spring.
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.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
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.
