Aop관련한 자세한 설명은 다음 자료를 참고하자

https://harmony-raccoon.tistory.com/22

 

Spring에서 AOP로 Log 기능 구현하기

서버에서 Log 기능은 매우 중요합니다. 예상치 못한 에러로 인해 서버가 다운되거나, 유저 요청이 특정 로직을 통과하지 못했을 때 Log를 통해 데이터를 복구하고 원인을 분석할 수 있습니다.그렇

harmony-raccoon.tistory.com

AOP에서 지원하고 있는 기능 중 하나는 MultiThread 구현이다.

MultiThread를 구현하는 어노테이션은 @Async이다.


@Async

Async는 Spring에서 MultiThread를 도입하기 위해서 필요한 어노테이션이다.

Async를 이용해서 MultiThread를 도입하기 위한 방법은 크게 어노테이션만 달기, Configuration 작성 2가지로 구분된다.

Annotation만 달기

1. Application.java 클래스에 @EnableAsync라는 어노테이션을 달아준다.

@EnableAsync
@SpringBootApplication
public class ExampleApplication {
	public static void main(String[] args) {
		SpringApplication.run(ExampleApplication.class, args);
	}
}

2. 그 후, MultiThread기능을 달고 싶은 메서드에 @Async라는 어노테이션을 달아준다.

@Service
@Slf4j
public class AsyncService {
    @Async
    public void log() throws InterruptedException {
        log.info("{} 쓰레드 작업 시작", Thread.currentThread().getId());
        Thread.sleep(100);
        log.info("{} 쓰레드 작업 종료", Thread.currentThread().getId());
    }
}

이렇게만 하면 끝이다.

그럼 위 log()라는 메서드가 실행될 때, MultiThread가 생성되어 실행된다.

테스트코드는 다음과 같다.

@SpringBootTest
class AsyncServiceTest {
    @Autowired
    AsyncService asyncService;

    @Test
    @DisplayName("멀티쓰레드 확인")
    void testMultiThread() throws InterruptedException {
        for(int i = 0; i < 20; i++) {
            asyncService.log();
        }
    }
}

위의 테스트 코드를 실행하면 결과는 다음과 같다.

테스트 결과

이렇게 쓰레드가 OS 자체의 Scheduling에 따라서 실행되고 있다는 것을 알 수 있다.


Configuration 작성

1. Configuration 클래스 생성 및 @Configuration, @EnableAsync 어노테이션 달아준다. (이때, 위에서 Application.java 파일에 달았던 @EnableAsync는 삭제해도 똑같이 작동한다.)

@EnableAsync
@Configuration
public class AsyncEx {
}

2. 그 후, MultiThread기능을 달고 싶은 메서드에 @Async라는 어노테이션을 달아준다.

 

@Service
@Slf4j
public class AsyncService {
    @Async
    public void log() throws InterruptedException {
        log.info("{} 쓰레드 작업 시작", Thread.currentThread().getId());
        Thread.sleep(100);
        log.info("{} 쓰레드 작업 종료", Thread.currentThread().getId());
    }
}

이렇게 하면 Configuration을 이용한 MultiThread 기능 사용은 끝이다.

내가 작성한 테스트코드는 위에서 알아보았던 코드와 일치한다. 위의 테스트 코드를 다시 작동하면 결과는 다음과 같다.

Configuration을 활용한 테스트 결과


ThreadPool 사용

위에서 작성하였던 Configuration에 ThreadPool을 도입해서 개발자 직접 Thread의 개수, Task Queue의 크기 지정 등 Thread 설정을 할 수 있다.

Configuration 코드를 다시 보면 다음과 같다.

@EnableAsync
@Configuration
public class AsyncEx {
    @Bean(name = "multiThread Ex") // 이름을 설정하면, 해당하는 이름을 가진 @Async 어노테이션에만 이 쓰레드 설정이 적용.
    public Executor multiThreadExecutor() {
        ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
        poolExecutor.setCorePoolSize(10); // 최소 쓰레드 수
        poolExecutor.setMaxPoolSize(100); // 최대 쓰레드 수
        poolExecutor.setQueueCapacity(100); // 쓰레드 이상의 작업 요청이 있다면, 이 Queue 에 저장한 후 쓰레드가 종료되면 여기서 작업을 꺼내서 다시 실행.
                                            // 이 Queue 크기를 설정
        poolExecutor.setThreadNamePrefix("MultiThread -"); // 쓰레드 실행시 이름을 설정
        poolExecutor.initialize();

        return poolExecutor;
    }
}

이렇게 설정한 후, 다시 테스트 코드를 돌리면 결과는 다음과 같다.

ThreadPool를 활용한 테스트 결과

위의 이미지와 다른 점은 Spring 자체에서 Thread로그를 찍을 때, Task가 아닌 우리가 설정한 MultiThread로 시작한다는 점이다.

즉, 위의 설정이 제대로 작동하고 있다.

참고 자료

https://cano721.tistory.com/208

 

[Spring] @Async 비동기 멀티스레드 사용법

수정사항 2022-08-27 async 사용 시 비동기 스레드 exception 처리 CompletableFuture 사용법 추가 Async 사용계기 현재 마이다스 AI 역검 백엔드팀에 들어오게 되었는데, 과제 중 원활한 검증작업을 위한 응시

cano721.tistory.com

https://320hwany.tistory.com/107

 

Spring에서 멀티 쓰레드 비동기 프로그래밍 해보기

Spring은 기본적으로 멀티 쓰레드, 동기 방식으로 작동합니다. 하지만 성능 향상을 위해서 비동기 방식으로 작동을 하도록 할 수 있습니다. 이번 글에서는 싱글 쓰레드/멀티 쓰레드, 동기/비동기

320hwany.tistory.com

https://hseong.tistory.com/88

 

스프링 @Async를 이용한 비동기 실행 적용하기

Spring Async@Async비동기 실행을 위한 @Async를 사용하기 위해서는 먼저 적당한 @Configuration 클래스에 @EnableAsync를 추가해주어야 합니다.@EnableAsync @Configuration public class AsyncConfig {}그런 다음 비동기적으

hseong.tistory.com

혹시라도 틀린 내용이 있다면 댓글로 알려주시면 감사하겠습니다!!

'Spring > AOP' 카테고리의 다른 글

Spring에서 AOP로 Log 기능 구현하기  (0) 2024.12.13

서버에서 Log 기능은 매우 중요합니다. 예상치 못한 에러로 인해 서버가 다운되거나, 유저 요청이 특정 로직을 통과하지 못했을 때 Log를 통해 데이터를 복구하고 원인을 분석할 수 있습니다.

그렇다면 모든 로직마다 Log 기능을 추가해야 할까요?

매번 동일한 코드를 구현한다면 개발자가 실수할 가능성이 높아지고, 코드를 이해하기도 어려워질 것입니다. 이를 해결하기 위한 패러다임이 바로 AOP(Aspect-Oriented Programming)입니다.


AOP(Aspect-Oriented Programming)란?

AOP관심사 분리(Separation of Concerns)를 통해 코드의 유지보수성을 높이는 프로그래밍 패러다임입니다.

관심사 분리란 무엇인가?

코드를 설계할 때는 보통 두 가지로 구분하여 고민합니다:

  1. 핵심 로직: 요구사항을 처리하는 주요 로직.
  2. 부가 로직: 서비스의 정확한 동작을 지원하기 위한 부가적인 로직.

핵심 로직은 요구사항마다 달라지므로 개발자가 집중해서 개발해야 합니다. 반면, 부가 로직은 여러 요구사항에서 공통적으로 사용되는 기능입니다. 부가 로직을 매번 구현하기보다는 한 번 구현하여 핵심 로직에 적용할 수 있다면, 개발의 효율성과 유지보수성이 높아질 것입니다.

이러한 아이디어에서 출발한 패러다임이 바로 AOP입니다.


Spring AOP 설치하기

Gradle를 통해서 간단하게 라이브러리를 설치할 수 있습니다.

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

PointCut: 부가 로직 적용 대상 지정

AOP에서는 PointCut을 사용하여 부가 로직이 적용될 대상을 명시합니다. 쉽게 말해, 부가 로직이 실행될 객체와 메서드를 지정하는 것입니다.

예제 코드

@Slf4j
@Aspect
@Component
public class LogPointcut {
    @Pointcut("execution(* eumsun.backend.controller.NotTokenController.*(..))")
    public void notToken(){
    }
}

 

코드 분석

  • @Aspect: 해당 클래스가 AOP 관련 코드를 포함하고 있음을 명시합니다. 이 어노테이션이 붙은 클래스는 Advisor로 등록됩니다.
  • @Pointcut: 부가 로직이 적용될 대상을 지정하며, AspectJ 표현식을 사용합니다.

Advice: 부가 로직 실행 시점 지정

Advice는 부가 로직이 실행되는 시점을 지정하는 어노테이션입니다.

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {
    private final LogService logService;

    @Around("eumsun.backend.config.log.LogPointcut.notToken()")
    public Object beforeAdviceNotTokenLog(ProceedingJoinPoint joinPoint) throws Throwable {
        return tryLog(joinPoint, this::notTokenLogging);
    }
    
    private Object tryLog(ProceedingJoinPoint joinPoint, Consumer<ProceedingJoinPoint> logMethod) throws Throwable {
        Object result = joinPoint.proceed();
        logMethod.accept(joinPoint);
        return result;
    }
    
    private void notTokenLogging(ProceedingJoinPoint joinPoint) {
        String requestBody = getRequestBody();
        viewLog(joinPoint, requestBody);
        createLog(joinPoint, requestBody);
    }
}

 

코드 분석

  1. @Around:
    • 부가 로직이 핵심 로직의 전후에 실행됩니다.
    • 가장 강력한 Advice 어노테이션입니다.
  2. ProceedingJoinPoint:
    • 유저 요청에 대한 정보를 담고 있는 객체로, 핵심 로직을 실행하거나 파라미터 정보를 얻을 수 있습니다.

만약 RequestBody를 Log에 찍고 싶다면 다음과 같은 클래스를 추가해야 한다.

더보기
더보기

RequestBody 캐싱을 위한 설정

유저의 RequestBody를 로그에 기록하려면 추가 설정이 필요합니다. 기본적으로 RequestBody는 한 번 읽으면 사라지기 때문에 캐싱해야 합니다.

필터 추가

@Component
public class RequestCacheFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (!(request instanceof ContentCachingRequestWrapper)) {
            request = new ContentCachingRequestWrapper(request);
        }
        filterChain.doFilter(request, response);
    }
}

코드 분석

  • OncePerRequestFilter를 상속하여 RequestBody를 캐싱합니다.
  • ContentCachingRequestWrapper를 사용해 데이터를 한 번 읽어도 이후에 다시 사용할 수 있도록 만듭니다.

마무리

AOP를 활용하여 부가 로직핵심 로직을 분리하면, 개발자는 부가 로직에 시간을 할애하지 않고 핵심 로직에 집중할 수 있습니다. 이를 통해 더욱 완성도 높은 서비스를 제공할 수 있습니다.

 

혹시라도 틀린 내용이 있다면 댓글로 알려주시면 감사하겠습니다!!

'Spring > AOP' 카테고리의 다른 글

[Spring] AOP로 MultiThread 구현하기  (1) 2025.05.28

+ Recent posts