프로젝트/데브툰

[프로젝트] @Async를 알아보자(feat. Spring AOP)

Don't stop 훈 2024. 5. 6. 04:41

 

프로젝트에서 @Async를 적용 시켜보았다.

@Async는 Spring AOP를 기반으로 동작하게 되는데, Spring AOP를 알아보자.

 

🎯 관점 지향 프로그래밍

웹 개발은 보통 계층적인 구조로 나누어져 있다.

  • Web Layer - json변환, 뷰 반환
  • Business Layer - 비즈니스 로직
  • Data Layer - persistence 로직

 

이런 계층들에 공통적으로 적용되는 부분들이 있다. 바로 보안, 성능측정, 로깅과 같은 부분들이다.

각 계층에서 핵심적인 부분들은 아니지만 계층을 걸쳐 공통적으로 존재하여 공통관심사라고 불린다.

이러한 공통 관심사를 관리해주기 위해 AOP(관점 지향 프로그래밍)이 등장하였다.

 

 

AOP가 동작하는 흐름은 크게 아래와 같다.

  1. 공통 관심사항을 Aspect로 만든다.
    • 로깅, 보안, 성능 모두 하나의 Aspect이다.
  2. PointCut을 정의한다.
    • Aspect를 어디에 적용할 것인지를 명시하는 로직을 정의

Java진영에서 AOP를 위한 프레임워크에는 SpringAOP와 AspectJ가 있다.

 

 

🎯 스프링 AOP 용어 정리하기

컴파일 시점 용어

  • Advice : 실행 하려는 코드를 의미한다.
    • ex) logger.info("Before Aspect - Method is called - {}", joinPoint);
  • Pointcut : 인터셉터 하려는 메서드 호출에 대한 표현식
    • ex) execution(* org.example.service..(..))
  • Aspect : Advice와 Pointcut의 조합
    • Advice : 무엇을 할 것인가.
    • Pointcut : 언제 메서드 호출을 인터셉트 할 것인가.
  • Weaver : AOP를 구현한 프레임워크를 의미한다.
    • AspectJ나 Spring AOP도 위버이다.
      • 위의 프레임워크가 하는 AOP를 실행하는 과정을 통틀어 위빙이라 한다.

 

실제로 프로젝트에 로깅위해 간단히 적용해 보았다 👇 (자세한 사용법은 슉슉 찾아서 적용 해보자!)

더보기
더보기

 

package yjh.devtoon.common.aop;

import org.aspectj.lang.annotation.Pointcut;

public class PointcutConfig {

    /**
     * presentation 계층의 모든 메서드에 적용하는 포인트컷 경로.
     */
    @Pointcut("execution(* yjh.devtoon.*.presentation.*.*(..))")
    public void presentationPackageConfig() {
    }

}

 

package yjh.devtoon.common.aop;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Slf4j
@Aspect
@Configuration
public class LoggingAspect {

    @AfterReturning("yjh.devtoon.common.aop.PointcutConfig.presentationPackageConfig()")
    public void logMethodCall(final JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String className = signature.getDeclaringType().getSimpleName();
        String methodName = signature.getName();

        HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        log.info("Request: {} {} ({}), {}.class -> {}()", request.getMethod(), request.getRequestURI(), request.getRemoteHost(),
                className, methodName);
    }

}

 

런타임 시점 용어

  • Join Point : Pointcut 조건이 참이면 Advice가 실행되는데, Advice가 실행되는 인스턴스를 JoinPoint 라고 한다. 메서드 실행 관련 필요한 여러 정보를 담고있다.

 

유용한 어노테이션

  • @Before : 메서드가 호출되기 전 로그를 출력하거나 인증이 필요할 때 유용하다.
  • @After : 메서드가 실행되고 난 뒤 수행할 작업을 지정한다.
    • 메서드가 성공하든 실패든 익셉션을 던지든 상관없이 실행
  • @AfterReturning : 오직 메서드가 성공했을 경우에서 실행, 익셉션 발생하면 실행하지 않음
  • @Around : 메서드 실행 전과 후에 동작한다.

 

 

🎯 스프링 AOP 동작방식

스프링에서는 사용할 Bean(객체)를 생성하고, 컨테이너에 등록하는 방식으로 동작한다.

 

스프링에서 AOP를 동작시키는 원리는 아래와 같다.

1. Bean으로 등록된 객체를 생성한 뒤 후처리기에 전달한다.

2. 빈 후처리기는 포인트컷을 확인하여 전달받은 빈이 프록시 적용 대상이지 확인한다.

3. 프록시 생성 대상 Bean이라면 프록시 객체로 대체하여 빈 후처리기가 컨테이너에 등록한다.

 

즉, Bean을 생성하여 컨테이너에 등록하는 시점에 AOP로 활용할 객체인지를 판단하여, 일단 객체 대신 프록시 객체로 대체해서 컨테이너에 등록하는게 핵심이다.

 

앞서 사용했던 @Async도 스프링 AOP처럼 @Async가 붙은 메서드의 클래스는 일단 객체(Bean)가 아니라 프록시 객체로 스프링 컨테이너에 등록된 것이다.

이를 통해서 @Async가 붙은 메서드에는 스레드풀에서 스레드를 할당받아서 별도로 비동기 방식으로 동작시킬 수 있었다.

 

🎯 더 알아보기

스프링 AOP동작방식을 간단히 알아보았는데, 이번에는 빈 후처리기와 프록시 객체라는 새로 알게된 개념이 등장하였다.

몰랐던 개념이 등장했기 때문에 조금 더 짚고 넘어가야 될 것 같다.

 

<계속해서 학습할 내용>

- 프록시 객체

- 빈 후처리기