Spring Boot

Spring Boot AOP

iksadnorth 2023. 7. 29. 18:21

👣 개요

특정 클래스들에 공통적으로 수행해야 하는 Logic들을
해당 클래스 코드를 건드리지 않고 하나의 코드로 일괄 적용할 수 있는 도구.

예를 들면, 로깅, 보안 처리, 예외 처리 등등의 경우엔 다수의 클래스에서 공통적으로 적용되어야 하는데
만약 모든 코드에 개별적으로 코드를 부여하면 동일한 기능을 가짐에도 불구하고 코드들이 다원화 되기 때문에
수정, 유지보수가 매우 어려워진다.

아래는 AOP 적용이 필요한 예시 코드다.

더보기
public class UserService {
    
    public User getUserById(long id) {
        // 공통 코드
        long startTime = System.currentTimeMillis();

        // 비즈니스 로직
        User user = userRepository.findById(id);
        
        // 공통 코드
        long endTime = System.currentTimeMillis();
        long executionTime = endTime - startTime;

        System.out.println("getUserById() 실행 시간: " + executionTime + "ms");

        return user;
    }

    public void createUser(User user) {
        // 공통 코드
        long startTime = System.currentTimeMillis();

        // 비즈니스 로직
        userRepository.save(user);

        // 공통 코드
        long endTime = System.currentTimeMillis();
        long executionTime = endTime - startTime;

        System.out.println("createUser() 실행 시간: " + executionTime + "ms");
    }
}

👣 기본 용어 정리

Aspect

AOP에서의 모듈화 단위를 의미합니다.
애플리케이션 전체에 걸쳐 적용되는 횡단 관심사(공통 기능)를 구현한 모듈.
예를 들어, 로깅 기능, 트랜잭션 처리, 보안 등이 Aspect로 구현됨.

Advice

Aspect의 구체적인 동작을 정의한 것으로, 횡단 관심사가 실제로 어떻게 동작해야 하는지를 명시함.
Advice는 Before, After, After-Returning, After-Throwing, Around 등 다양한 유형이 있다

Join Point

Advice가 적용될 수 있는 지점. 일반적으로 메서드 호출, 필드 접근 등이 Join Point가 될 수 있다.

Pointcut

Join Point의 부분 집합으로, 해당 Aspect를 적용할 대상을 뽑을 조건식.

Target Object

Aspect가 적용될 메서드가 포함된 객체.

Weaving

Aspect를 핵심 애플리케이션 코드에 실제로 적용하는 과정.
컴파일 시점, 클래스 로딩 시점, 실행 시점 등에 Weaving이 가능하다.

 

👣 Advice 관련 Annotation

아래 Annotation은 대상 메서드를 기준으로
실행 전 or 실행 후 or 에러 발생 후 등등의 Advice 실행 시점과 관련된 것이다.

  • @Around
    • 메서드 호출 전후 작업 명시 가능
  • @Before
    • 조인 포인트 실행 이전에 실행
  • @AfterReturning
    • 조인 포인트가 정상 완료 후 실행.
  • @AfterThrowing
    • 메서드가 예외를 던지는 경우 실행.
  • @After
    • 조인 포인트의 정상, 예외 동작과 무관하계 실행.

 

👣 Pointcut 문법

Pointcut은 위에서 나열한 Annotation의 파라미터로 전달되며, 이것을 위한 문법을 가지고 있다.

1. execution

참고로 [] 내부에 있는 형식은 Optional하다.

execution([접근제어자] 반환타입 [선언타입].메서드이름(파리미터) [예외])

특정 타입만 부여하는 것이 아니라 모든 타입에 대해 가능성을 열어야 하는 경우가 있다.
그런 등등의 사항들을 설정하는 문법은 다음과 같다.

  표기법 설명 예시
모든 타입 허용 * 말그대로 모든 타입을 허용 execution(* *.*(*))
모든 타입 허용 및 
파라미터 갯수 상관 X
.. 파라미터를 표기할 때,
여러 개의 파라미터를 삽입해야 할 때, 해당 표기법을 사용.
execution(public *.run(..))
클래스 혹은 메서드명 
일부 일치 시, 적용
{문자열}* or *{문자열} 정규표현식은 아니지만
일부 문자열이 일치하면
적용되는 형식.
execution(public *.run*(String))
특정 패키지의 하위 디렉토리
일괄 적용
{패지키}.. 타입에 적용되는 문법.
특정 패키지 아래의 모든 
디렉토리의 클래스를 매칭
execution(* com.example.project..*.run(String))

아래는 적용 예시다.

@Aspect
@Component
public class LoggingAspect {

    @Around("execution(* com.example.UserService.*(..))")
    public void userServiceMethods() {
    }
}

 

2. @annotation

주어진 애노테이션을 갖고 있는 경우 적용됨.

@annotation(적용할 어노테이션 Fullname)

아래는 적용 예시다.

// 부착할 애노테이션
package com.example.mvc.aop.member.annotation;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
    String value();
}

public class MemberServiceImpl {
    // 대상 메서드에 부착
    @MethodAop("test value")
    public String hello(String param) {
        return "ok";
    }
}

// 적용
@Around("@annotation(com.example.mvc.aop.member.annotation.MethodAop)")
public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
    // 생략
}

 

3. bean

스프링 빈의 이름으로 AOP 적용 여부를 지정함.
스프링에서만 사용할 수 있는 특별한 지시자.

// 빈 이름이 orderService이거나 Repository로 끝나는 빈에 적용
@Around("bean(orderService) || bean(*Repository)")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
    // 생략
}

 

👣 실제 적용방법

1. Aop 의존성 추가.

implementation 'org.springframework.boot:spring-boot-starter-aop'

2. Advice 코드 작성.
ProceedingJoinPoint 객체의 proceed() 메서드는 실제로 실행할 메서드를 실행하는 메서드다.
해당 코드 라인을 기준으로 특정 Logic을 실행시킬 수 있다.

@Slf4j
@Aspect
public class AspectV6Advice {

    @Around("execution(* com.example.mvc.order..*(..))")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            // @Before 수행
            log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
            // @Before 종료

            // Target 메서드 호출
            Object result = joinPoint.proceed();
            // Target 메서드 종료

            // @AfterReturning 수행
            log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
            // @AfterReturning 종료

            // 값 반환
            return result;
        } catch (Exception e) {
            // @AfterThrowing 수행
            log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
            throw e;
            // @AfterThrowing 종료
        } finally {
            //@ After 수행
            log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
            //@ After 종료
        }
    }
}

 

'Spring Boot' 카테고리의 다른 글

Lombok  (0) 2023.07.29
SpEL - Spring Expression Language  (0) 2023.07.29
Spring Boot Bean 등록 방법 5가지  (0) 2023.07.29
Spring Logging System  (0) 2023.07.29
Spring Boot 외부 설정법  (0) 2023.07.28