Master Spring AOP: From Manual Logging to Seamless Aspect‑Oriented Programming
This article walks through the fundamentals of Spring AOP, showing how to replace repetitive logging code with AspectJ‑driven aspects, explains core AOP concepts, pointcut expressions, proxy creation mechanisms, advice types, caching behavior, and common pitfalls for Java backend developers.
Introduction
Spring AOP is one of the core technologies in the Spring framework. The article demonstrates a practical way to introduce AOP by replacing manual logging code with a reusable aspect.
Manual Logging Before AOP
@Service
public class TestService {
public void doSomething1() {
beforeLog();
System.out.println("==doSomething1==");
afterLog();
}
// ... other methods ...
public void beforeLog() {
System.out.println("打印请求日志");
}
public void afterLog() {
System.out.println("打印响应日志");
}
}Every new business method requires adding beforeLog and afterLog, leading to duplicated code.
Introducing Spring AOP with AspectJ
@Service
public class TestService {
public void doSomething1() {
System.out.println("==doSomething1==");
}
// ... other methods ...
}
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(public * com.sue.cache.service.*.*(..))")
public void pointcut() {}
@Before("pointcut()")
public void beforeLog() {
System.out.println("打印请求日志");
}
@After("pointcut()")
public void afterLog() {
System.out.println("打印响应日志");
}
}After refactoring, business methods stay unchanged while logging is automatically applied to all methods matched by the pointcut.
Key AOP Concepts
Joinpoint – a specific point in program execution (method call, exception, etc.).
Pointcut – a predicate that selects joinpoints.
Advice – the code to be executed at a selected joinpoint.
Aspect – a combination of pointcut and advice.
Target – the original business object.
Proxy – the object created after weaving the aspect.
Weaving – the process of linking advice to the target’s joinpoints.
Pointcut Expression
The @Pointcut expression execution(public * com.sue.cache.service.*.*(..)) matches any public method in any class under com.sue.cache.service. A more specific expression can target a single class, e.g., execution(public * com.sue.cache.service.TestService.*(..)).
@Pointcut("execution(public * com.sue.cache.service.TestService.*(..))")
public void pointcut() {}Spring AOP Entry Points
Spring creates proxies at three main places:
Bean creation – AbstractAutowireCapableBeanFactory.createBean invokes BeanPostProcessor to allow proxy generation.
Early bean reference – during circular‑dependency resolution, getEarlyBeanReference creates a proxy and stores it in the second‑level cache.
Bean initialization – AbstractAutowireCapableBeanFactory.initializeBean calls AbstractAutoProxyCreator.postProcessAfterInitialization, which finally wraps the bean with a proxy.
JDK Dynamic Proxy vs CGLIB
JDK Dynamic Proxy
public interface IUser { void add(); }
public class User implements IUser { public void add() { System.out.println("===add==="); } }
public class JdkProxy implements InvocationHandler {
private Object target;
public Object getProxy(Object target) { this.target = target; return Proxy.newProxyInstance(getClass().getClassLoader(), target.getClass().getInterfaces(), this); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(); Object result = method.invoke(target, args); after(); return result; }
private void before() { System.out.println("===before==="); }
private void after() { System.out.println("===after==="); }
}
public class Test { public static void main(String[] args) { User user = new User(); JdkProxy jdkProxy = new JdkProxy(); IUser proxy = (IUser) jdkProxy.getProxy(user); proxy.add(); } }Requires an interface; proxy creation is fast after the initial overhead.
CGLIB Proxy
public class User { public void add() { System.out.println("===add==="); } }
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getProxy(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
@Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before(); Object result = method.invoke(target, args); after(); return result; }
private void before() { System.out.println("===before==="); }
private void after() { System.out.println("===after==="); }
}
public class Test { public static void main(String[] args) { User user = new User(); CglibProxy cglibProxy = new CglibProxy(); User proxy = (User) cglibProxy.getProxy(user); proxy.add(); } }No interface needed; works with classes but cannot proxy final methods or classes.
Spring chooses JDK proxy when the target implements an interface; otherwise it falls back to CGLIB. The default proxy mode can be forced with spring.aop.proxy-target-class=true or @EnableAspectJAutoProxy(proxyTargetClass = true).
Five Types of Advice
@Before("pointcut()")
public void beforeLog(JoinPoint jp) { System.out.println("打印请求日志"); }
@After("pointcut()")
public void afterLog(JoinPoint jp) { System.out.println("打印响应日志"); }
@Around("pointcut()")
public Object around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("打印请求日志");
Object result = jp.proceed();
System.out.println("打印响应日志");
return result;
}
@AfterReturning(pointcut = "pointcut()", returning = "retVal")
public void afterReturning(JoinPoint jp, Object retVal) {
System.out.println("获取结果:" + retVal);
}
@AfterThrowing(pointcut = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint jp, Throwable e) {
System.out.println("异常:" + e);
}The execution order of a single aspect is:
@Before → @Around (before) → target method → @Around (after) → @After → @AfterReturning / @AfterThrowing</p><p>When multiple aspects are present, the order can be controlled with <code>@Order(lower value executes first).
Chain Invocation Mechanism
Spring uses a recursive chain (responsibility‑chain pattern) in ReflectiveMethodInvocation.proceed() to invoke each advice in order, eliminating complex if…else logic and adhering to the open‑closed principle.
Spring Bean Caching (Original vs Proxy)
Spring uses a three‑level cache:
singletonObjects (level‑1) – stores fully initialized beans (usually proxies).
earlySingletonObjects (level‑2) – stores early references, which are proxies created during circular‑dependency resolution.
singletonFactories (level‑3) – stores factories that produce the original bean instance.
Common Pitfalls
Direct method call – calling a @Transactional method from within the same bean bypasses the proxy, causing the transaction to be ignored.
Private methods – Spring cannot create proxies for private methods; they will never be advised.
Final classes or methods – CGLIB cannot subclass final elements, so advice is not applied.
Circular dependencies with proxies – early bean references may expose raw beans, leading to BeanCurrentlyInCreationException. Use lazy injection or redesign dependencies.
Conclusion
Spring AOP provides a powerful way to separate cross‑cutting concerns such as logging, transaction management, and security from business logic. Understanding pointcut syntax, proxy creation, advice types, caching, and the common pitfalls enables developers to use AOP effectively in Java backend projects.
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.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
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.
