Aspect-Oriented Programming is a programming paradigm that allows us to separate cross-cutting concerns from our main business logic.
@Before -> Run before method@After -> run after method@AfterReturning -> run after method returns successfully@AfterThrowing -> run if method throws exception@Around -> run before and after (we can even control execution)com.example.service")TimerAop:@Aspect
@Component
public class TimerAop {
@Pointcut(value = "within(com.example.filter.controller.UserApiController)")
public void timerPointCut(){}
@Around(value = "timerPointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("Before method execution");
Arrays.stream(joinPoint.getArgs()).forEach(
it -> {
if(it instanceof UserRequest){
var tempUser = (UserRequest)it;
tempUser.setPhoneNumber(tempUser.getPhoneNumber().replace("-",""));
}
}
);
var newObjs = Arrays.asList(
new UserRequest()
);
joinPoint.proceed(newObjs.toArray());
System.out.println("After method execution");
}
}
@Aspect: Marks this class as an Aspect, meaning it contains cross-cutting logic.@Component: Registers it as a Spring Bean, so Spring AOP can weave it into the app.@Pointcut(value = "within(com.example.filter.controller.UserApiController)")
public void timerPointCut(){}
UserApiController" and we can reuse timerPointCut() as a logical label for this pointcut.@Around: Advice that runs before and after the target method.ProceedingJointPoint: Gives us access to the method being intercepted:getArgs())proceed())Arrays.stream(joinPoint.getArgs()).forEach(it -> {
if (it instanceof UserRequest) {
var tempUser = (UserRequest) it;
tempUser.setPhoneNumber(tempUser.getPhoneNumber().replace("-", ""));
}
});
UserRequest object, it cleans up the phone number by removing dashes. -> This shows that AOP advice can pre-process and sanitize input arguments before the controller method executes.var newObjs = Arrays.asList(
new UserRequest()
);
joinPoint.proceed(newObjs.toArray());
Instead of passing the original arguments, we are constructing a new argument list (with a brand-new UserRequest)
proceed(newObjs.toArray()): Calls the target method but with our custom arguments. This demonstrates that not only can we intercept method calls, we can also inject completely different arguments into the target method. -> This shows that AOP isn't just about logging or validation - it can actually change the behaviour of the target method by controlling what arguments it receives.
Adding more aspects:
@Aspect
@Component
public class TimerAop {
@Pointcut(value = "within(com.example.filter.controller.UserApiController)")
public void timerPointCut(){}
@Before(value = "timerPointCut()")
public void before(JoinPoint joinPoint){
System.out.println("before");
}
@After(value = "timerPointCut()")
public void after(JoinPoint joinPoint){
System.out.println("after");
}
@AfterReturning(value = "timerPointCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result){
System.out.println("after returning");
}
@AfterThrowing(value = "timerPointCut()", throwing = "tx")
public void afterThrowing(JoinPoint joinPoint, Throwable tx){
System.out.println("After throwing");
}
@Around(value = "timerPointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
....
}
}
Before method execution
before
2025-08-19T02:02:19.403+08:00 INFO 30565 --- [filter] [nio-8080-exec-1] c.e.filter.controller.UserApiController : UserRequest(name=null, phoneNumber=null, email=null, age=null)
after returning
after
After method execution
execution(...): Matches method execution join points (method calls).@Pointcut("execution(* com.example.service.*.*(..))")com.example.service package.within(...): Limits matching to types (classes).@Pointcut("within(com.example.controller..*)")com.example.controller package.this(...): Matches join points where the proxy object is an instance of a given type.@Pointcut("this(com.example.service.MyService)")MyService.target(...): Matches join points where the target object (actual object, not proxy) is an instance of a given type.@Pointcut("target(com.example.service.MyService)")args(...): Matches methods with parameters of specific types (runtime binding)@Pointcut("args(java.lang.String, int)")@target(...): Matches join points where the target object has a given annoatation@Pointcut("@target(org.springframework.stereotype.Service)")@Service@within(...): Matches join points where the declaring type (class) has a given annotation.@Pointcut("@within(org.springframework.stereotype.Controller)")@annotation(...): Matches methods where the runtime types of arguments have certain annotations@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")@Transactional.@args(...): Matches methods where the runtime types of arguments have certain annotations.@Pointcut("@args(com.example.security.Secure)")@Secure ```java execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?) ```modifiers-pattern? (optional)
public matches only public methods, protected only matches protected methods etcret-type-pattern (required)
void matches methods returning nothing, int matches methods returning an int, * matches any return type.declaring-type-pattern (optional)
com.example.Service.UserService -> Matches only methods in that classcom.example.Service.* -> Matches methods in any class in that packagecom.example.* -> Matches methods in that package and all subpackages.method-name-pattern (required)
get* -> Matches any method starting with get*Service -> Matches any method ending with Service* -> Matches any method nameparam-pattern (Required but can be ..)
(String) -> Matches methods with exactly one String parameter(String, int) -> Matches methods with two parameters: first String, second int.(..) -> Matches any parameters (any number, any type)(String, ..) -> first parameter is String, rest can be anything.throws-pattern? (optional)
throws clause. throws java.io.IOException -> Only methods that declare IOEXceptionthrows * -> Matches methods with all types of exception@Pointcut("execution(public * com.example.service..*Service.get*(String, int) throws java.io.IOException)")
void myPointcut() {}
public -> Only public methods* -> Any return typecom.example.service..*Service -> class ends with Service in service package or subpackagesget* -> Method name starts with get(String, int) -> Exactly two parameters: String then intthrows java.io.IOEXception -> Methods declaring IOException.. means:com.example.service..*Service means will match any subpackage under service and will match any class whose name ends with Service. com.example.service.* and com.example.serivce..*: com.example.service.* matches all classes directly inside the com.example.service package only (does not include subpackages). com.example.service..* matches all classes in com.example.service and in any of its subpackages (at any depth) -> The .. is the sub-package wildcard, and * is the class-name wildcard.