BE/Spring Boot

[Spring Boot] AOP란? / AOP 간단 구현 (로깅)

셰욘 2025. 4. 12. 15:58
728x90

AOP란?

AOP는 관점 지향 프로그래밍이라고 불리며, 핵심 로직과는 별개로 반복되는 공통 관심사를 분리해서 관리할 수 있게 해주는 프로그래밍 패러다임

 

대표적인 공통 관심사 예시 : 로깅, 트랜잭션 처리, 보안 체크, 성능 측정

 

 

❓ 왜 AOP를 쓸까?

기존 코드에 이런 공통 기능들을 일일이 추가하면 코드가 지저분해지고, 유지보수도 어려워진다.

AOP를 쓰면 이런 기능들을 한 곳에서 정의하고, 필요한 곳에 자동으로 적용할 수 있어서 코드가 더 깔끔하고 재사용성도 높아진다.

=> 여러 클래스에 공통적으로 작업을 적용할 때 사용한다. 

 

 

✅ 핵심 개념 간단 정리

  • Aspect: 공통 관심사를 모듈화한 것 (예: 로깅 기능)
  • Join Point: Aspect를 적용할 수 있는 지점 (예: 메소드 호출 시점)
  • Advice: 언제, 어떤 방식으로 공통 기능을 적용할지 정의 (예: 메소드 실행 전/후)
  • Pointcut: Advice가 적용될 Join Point들을 지정
  • Weaving: 실제 코드에 Aspect를 적용하는 과정

 


AOP 간단 구현 (로깅)

build.gradle

AOP 라이브러리 추가

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

 


@Aspect 어노테이션

Aspect 어노테이션은 해당 클래스에 공통 관심사가 들어있다는 걸 스프링에게 알려주는 역할

-> 흩어진 관심사(로그 체크, 시간 체크 등)를 하나로 묶은 것 (모듈화)

 

 

Pointcut

  • AOP를 적용할 대상을 정의
  • "execution(* com.example.logging..*.*(..))"  : com.example.logging 패키지 및 하위 패키지의 모든 클래스의 모든 메소드를 대상으로 함

 

Before

  • 대상 메소드가 실행되기 전에 이 메소드(before)가 실행됨
  • 비즈니스 메소드 전 공통 동작을 삽입하는 것
  • @Before("pointcut()")  :  pointcut 메소드 실행 전에 이 메소드 실행

 

Join Point

  • 특정 기능이 실행될 위치나 시점을 의미
  • 현재 실행되는 메소드, 인자, 클래스 등의 정보를 담고 있음
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect  // 흩어진 관심사(부가 기능)를 하나로 묶은 것 (모듈화)
@Component
public class SimpleAop {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @Pointcut("execution(* com.example.logging..*.*(..))")
    private void pointcut() {
        log.info("pointcut");
    }
    
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {  // Join Point : 특정 기능이 실행될 위치나 시점을 의미
        log.info("before");
    }
}

 


AOP 사용 (Service & Controller)

위에서 설정한대로 before 메소드가 실행되려면 com.example.logging 패키지 안에 Service 파일과 Controller 파일이 포함되어 있어야 한다. 

 

Service

@Service
public class AbcService {
    private final Logger log = LoggerFactory.getLogger(getClass());

    public void test() {
        log.info("서비스 실행됨");
    }
    
}

 

 

Controller

@RequiredArgsConstructor
@RestController
@RequestMapping("/abc")
public class AbcController {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private final AbcService abcService;

    @GetMapping("/test")
    public ResponseEntity<String> test() {
        log.info("정상");  // 정상일 때
        log.trace("trace : 동작 상황을 모두 출력(메소드 실행, 종료, 반복문 실행 등)");
        log.debug("debug : 동작 상황을 모두 출력(로직, 변수 값 등)");
        log.warn("warn : 잘못이 될 수도 있고 아닐 수도 있는 것");
        log.error("에러");  // 에러일 때

        abcService.test();

        return ResponseEntity.ok("test");
    }
}

 

 

실행 흐름

1. 브라우저에서 /abc/test 요청

2. AbcController.test() 실행

3. 이 안에서 abcService.test() 호출

4. abcService.test() 실행 직전에 → AOP의 @Before 메서드 실행됨

5. 그 다음에 실제 abcService.test() 실행됨

 

 

서비스의 test 메소드가 실행되기 전에 before가 실행된다.

 


before에서 메소드 받아오기

클래스 이름과 메소드 이름 가져오기

@Before("pointcut()")
public void before(JoinPoint joinPoint) {
    String className = joinPoint.getSignature().getDeclaringTypeName();
    String methodName = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();

    log.info("{} - {} - 실행되기 전", className, methodName);
}

 

 


 

실행 시간 체크

@Around("pointcut()")  // 실행되기 전, 후 다 포함
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    StopWatch stopWatch = new StopWatch();
    String className = joinPoint.getSignature().getDeclaringTypeName();
    String methodName = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();

    try {
        stopWatch.start();
        return joinPoint.proceed();
    } finally {
        stopWatch.stop();
        log.info("{} - {} - {}ms", className, methodName, stopWatch.getTotalTimeMillis());
    }
}

 

728x90