Spring Boot Web - Spring AOP

Seunghwan Choi·2025년 8월 18일

Java Backend

목록 보기
16/16

What is AOP?

Aspect-Oriented Programming is a programming paradigm that allows us to separate cross-cutting concerns from our main business logic.

  • In Spring, AOP allows us to define aspects (module of cross-cutting logic) and apply them declaratively to our code.

The problem AOP solves

  • In realworld applications, we often have cross-cutting concerns - logic that is needed in many places but doesn't belong to the main business logic. Examples:
    - Logging every request
    • Security checks
    • Transaction management
    • Performance monitoring
    • Error handling
  • Without AOP, we would have to repeat this code in multiple classes/methods -> messy, hard to maintain, and error-prone

Key concepts in AOP

  • Aspect: A module that encapsulates cross-cutting logic (e.g., logging)
  • Advice: The actual action taken by the aspect at a certain join point (like "log this before a method runs").
    - Types of advice:
    - @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)
  • Join point: A point in the execution of a program (like a method call) where extra behaviour (advice) can be inserted.
  • Pointcut: An expression that selects join points (e.g., "all methods in package com.example.service")
  • Weaving: The process of applying aspects to our code (Spring does this at runtime using proxies)

Example

  • Created another project based on filter project, disabled filters and interceptors for AOP demonstrations.
  • Added 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(){}
  • Defines a pointcut: "Any method inside 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:
    - Method Arguments (getArgs())
    • Method signature
    • Proceeding with execution (proceed())
Arrays.stream(joinPoint.getArgs()).forEach(it -> {
    if (it instanceof UserRequest) {
        var tempUser = (UserRequest) it;
        tempUser.setPhoneNumber(tempUser.getPhoneNumber().replace("-", ""));
    }
});
  • Modifying arguments
    - Iterates through method arguments and if the argument is a 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{
		....
    }
}
  • Output:
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
  • "After throwing" not printed because no exception was thrown

AOP PointCut Designators

Commonly used designators:

  1. execution(...): Matches method execution join points (method calls).
    @Pointcut("execution(* com.example.service.*.*(..))")
    • Matches all methods in com.example.service package.
  2. within(...): Limits matching to types (classes).
    @Pointcut("within(com.example.controller..*)")
    • Matches any method inside class under com.example.controller package.
  3. this(...): Matches join points where the proxy object is an instance of a given type.
    @Pointcut("this(com.example.service.MyService)")
    • Matches if the proxy object is MyService.
  4. 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)")
  5. args(...): Matches methods with parameters of specific types (runtime binding)
    @Pointcut("args(java.lang.String, int)")
    • Matches methods taking (String, int) as parameters
  6. @target(...): Matches join points where the target object has a given annoatation
    @Pointcut("@target(org.springframework.stereotype.Service)")
    • Matches all methods of beans annotated with @Service
  7. @within(...): Matches join points where the declaring type (class) has a given annotation.
    @Pointcut("@within(org.springframework.stereotype.Controller)")
  8. @annotation(...): Matches methods where the runtime types of arguments have certain annotations
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    • Matches methods annotated with @Transactional.
  9. @args(...): Matches methods where the runtime types of arguments have certain annotations.
    @Pointcut("@args(com.example.security.Secure)")
    • Matches methods whose arguments are annotated with @Secure

AOP PCD - Execution

  • Basic Syntax:
    	```java
    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
    	```
  1. modifiers-pattern? (optional)

    • Matches the access modifier of a method
    • public matches only public methods, protected only matches protected methods etc
    • It is optional - can be omitted if we're not concerned about access level.
  2. ret-type-pattern (required)

    • Matches the return type of the method.
    • void matches methods returning nothing, int matches methods returning an int, * matches any return type.
  3. declaring-type-pattern (optional)

    • Matches the class or interface that declares the method
    • Examples:
      - com.example.Service.UserService -> Matches only methods in that class
      • com.example.Service.* -> Matches methods in any class in that package
      • com.example.* -> Matches methods in that package and all subpackages.
    • If omitted, the pointcut applies to methods in *any class.
  4. method-name-pattern (required)

    • Matches the method name, supports wildcards:
      - get* -> Matches any method starting with get
      • *Service -> Matches any method ending with Service
      • * -> Matches any method name
  5. param-pattern (Required but can be ..)

    • Matches method parameters by type, number and order
    • Examples:
      - (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.
  6. throws-pattern? (optional)

    • Matches methods that declare specific exceptions in their throws clause.
    • Examples:
      - throws java.io.IOException -> Only methods that declare IOEXception
      • throws * -> Matches methods with all types of exception

Putting them together:

@Pointcut("execution(public * com.example.service..*Service.get*(String, int) throws java.io.IOException)")
void myPointcut() {}
  1. public -> Only public methods
  2. * -> Any return type
  3. com.example.service..*Service -> class ends with Service in service package or subpackages
  4. get* -> Method name starts with get
  5. (String, int) -> Exactly two parameters: String then int
  6. throws java.io.IOEXception -> Methods declaring IOException
  • What .. means:
    - Matches zero or more sub-packages.
    • com.example.service..*Service means will match any subpackage under service and will match any class whose name ends with Service.
  • Difference between 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.

0개의 댓글