AOP (Aspect-Oriented Programming)

2024. 5. 18. 18:09Back-End/Spring

애플리케이션 개발에서는 종종 로깅, 보안, 트랜잭션 관리와 같은 공통된 기능을 여러 모듈에서 사용해야 할 때가 있다. 이러한 기능을 각 모듈에 직접 코딩하면 코드의 중복이 발생하고 유지보수가 어려워진다. 이를 해결하기 위해 등장한 개념이 AOP(Aspect-Oriented Programming)이다.

 

AOP란?

AOP는 관점 지향 프로그래밍(Aspect-Oriented Programming)의 약자로, 프로그램의 주요 비즈니스 로직과는 별개로 존재하는 부가적인 기능(Aspect)을 모듈화하여 관리하는 프로그래밍 패러다임이다. 핵심 비즈니스 로직을 침해하지 않으면서, 횡단 관심사(Cross-Cutting Concerns)를 분리하여 코드의 재사용성과 유지보수성을 향상시킨다.

 

AOP의 주요 개념

  • Aspect: 횡단 관심사를 모듈화한 것. 예를 들어, 로깅이나 트랜잭션 관리가 Aspect가 될 수 있다.
  • Join Point: Aspect가 적용될 수 있는 애플리케이션의 특정 지점. 메서드 호출, 객체 생성 등이 Join Point가 될 수 있다.
  • Advice: 특정 Join Point에서 수행될 동작을 정의한 것. 언제 실행될지를 정의할 수 있다.
  • Pointcut: 특정 Join Point를 선택하는 표현식. Aspect가 어디에 적용될지를 정의.
  • Introduction: 기존 클래스에 새로운 메서드나 필드를 추가하는 것.
  • Weaving: Aspect와 다른 객체를 연결하여, Advice가 적절한 Join Point에서 실행되도록 하는 과정.

스프링에서 AOP 사용하기

스프링은 @AspectJ 애노테이션 스타일과 XML 설정 스타일, 두 가지 방식으로 AOP를 지원한다. 여기서는 @AspectJ 애노테이션 스타일을 사용하여 AOP를 적용하는 방법을 살펴보겠다.

 

1. 의존성 추가

스프링 AOP를 사용하려면 spring-boot-starter-aop 의존성을 추가해야 한다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 

2. Aspect 클래스 정의

Aspect를 정의하기 위해 @Aspect 애노테이션을 사용한다. 여러 종류의 Advice를 포함하는 Aspect를 만들어 보자.

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    // Pointcut 표현식: com.example.service 패키지 내의 모든 메서드 실행
    private static final String POINTCUT_EXPRESSION = "execution(* com.example.service.*.*(..))";

    @Before(POINTCUT_EXPRESSION)
    public void logBefore() {
        System.out.println("Before method execution");
    }

    @After(POINTCUT_EXPRESSION)
    public void logAfter() {
        System.out.println("After method execution");
    }

    @Around(POINTCUT_EXPRESSION)
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        
        Object proceed = joinPoint.proceed();  // 메서드 실행
        
        long executionTime = System.currentTimeMillis() - start;
        
        System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
        return proceed;
    }

    @AfterReturning(pointcut = POINTCUT_EXPRESSION, returning = "result")
    public void logAfterReturning(Object result) {
        System.out.println("After returning: " + result);
    }

    @AfterThrowing(pointcut = POINTCUT_EXPRESSION, throwing = "exception")
    public void logAfterThrowing(Exception exception) {
        System.out.println("After throwing: " + exception.getMessage());
    }
}

 

3. AOP 설정 활성화

스프링 부트 애플리케이션 클래스에 @EnableAspectJAutoProxy 애노테이션을 추가하여 AOP 설정을 활성화한다.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
public class AopExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(AopExampleApplication.class, args);
    }
}

 

Advice 종류와 사용 예시

1. @Before

메서드 실행 전에 로직을 수행한다.

@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
    System.out.println("Before method execution");
}

 

2. @After

메서드 실행 후에 로직을 수행한다.

@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
    System.out.println("After method execution");
}

 

3. @Around

메서드 실행 전후에 로직을 수행한다.

@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    
    Object proceed = joinPoint.proceed();  // 메서드 실행
    
    long executionTime = System.currentTimeMillis() - start;
    
    System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
    return proceed;
}

 

4. @AfterReturning

메서드가 정상적으로 반환된 후에 로직을 수행한다.

@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(Object result) {
    System.out.println("After returning: " + result);
}

 

5. @AfterThrowing

메서드가 예외를 던진 후에 로직을 수행한다.

@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception")
public void logAfterThrowing(Exception exception) {
    System.out.println("After throwing: " + exception.getMessage());
}

 

결론

AOP 스프링 프레임워크에서 중요한 프로그래밍 기법으로, 코드의 재사용성과 유지보수성을 향상시킬 있다. @AspectJ 애노테이션 스타일을 사용하면 간단하게 AOP 적용할 있으며, 공통 기능을 모듈화하여 코드의 복잡성을 줄일 있다.

'Back-End > Spring' 카테고리의 다른 글

Thymeleaf  (0) 2024.05.21
Spring MVC  (0) 2024.05.18
ResultSetExtractor  (0) 2024.05.17
@Transactional과 TransactionTemplate  (0) 2024.05.17
배치 업데이트(Batch Update)  (0) 2024.05.17