일반적으로 Spring MVC(동기)방식을 권장하지만 필요에 따라 비동기를 설정할 순 있다.

 

 

기본적인 비동기

package com.example.async;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class AsyncApplication {

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

}

@EnableAsync 어노테이션 추가

 

package com.example.async.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class AsyncService {

    @Async
    public void hello() {

        for(int i=0;i<10;i++){
            try {
                Thread.sleep(2000);
                log.info("thread sleep check");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

@Async aop 기반이기 때문에 프록시 패턴을 따른다. 때문에 public 메소드에만 적용이 가능하다.

package com.example.async.controller;

import com.example.async.service.AsyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class ApiController {

    private final AsyncService asyncService;

    @GetMapping("/hello")
    public String hello(){
        asyncService.hello();
        log.info("method end");
        return "hello";
    }
}

@Async 추가를 하게 되면 해당 메소드가 비동기 처리가 되어 쓰레드가 해당 메소드를 실행하고 클라이언트는 쓰레드의 작업이 끝나지 않았으나 응답을 받는다.

 

 

여러 API들의 요청을 조인하여 응답 내려주기 - CompletableFuture

package com.example.async.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Slf4j
@Service
public class AsyncService {

    private int count =0;

    @Async
    public CompletableFuture run() {

        return new AsyncResult(hello()).completable();
    }



    public String hello() {
        int idx = ++count;
        for(int i=0;i<10;i++){
            try {
                Thread.sleep(2000);
                log.info("thread sleep check {} : {}",idx,i+1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        return "async hello";
    }
}

CompletableFuture 반환하는 함수에 @Async 어노테이션을 추가

@Async로 설정한 메소드 내에서 같은 클래스의 같은 메소드를 호출할 때는 Async를 타지 않는다.

 

package com.example.async.controller;

import com.example.async.service.AsyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;

@Slf4j
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class ApiController {

    private final AsyncService asyncService;

    @GetMapping("/hello")
    public CompletableFuture hello(){
        log.info("CompletableFuture init");
        CompletableFuture api1 =  asyncService.run();
        CompletableFuture api2 =  asyncService.run();
        CompletableFuture api3 =  asyncService.run();
        return api3;
    }
}

api1, api2, api3 결과가 모두 종료되고 클라이언트에게 결과를 반환한다.

위 내용들은 Spring에서 정한 쓰레드들을 사용하게 된다.

 

 

직접 쓰레드 만들기

package com.example.async.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
public class AppConfig {


    @Bean("async-thread")
    public Executor asyncThread(){
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setMaxPoolSize(100);
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(10);
        threadPoolTaskExecutor.setThreadNamePrefix("Async-");
        return threadPoolTaskExecutor;
    }
}

쓰레드 풀을 지정하는 것은 환경, 리퀘스트 양에 따라 달라진다. 때문에 이 풀이 어떻게 동작하는지 정확하게 알고 있어야 설정을 잘할 수 있다.

 

package com.example.async.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Slf4j
@Service
public class AsyncService {

    @Async("async-thread")
    public CompletableFuture run() {

        return new AsyncResult(hello()).completable();
    }



    public String hello() {
        for(int i=0;i<10;i++){
            try {
                Thread.sleep(2000);
                log.info("thread sleep check ");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        return "async hello";
    }
}

@Async 어노테이션 인자에 Bean으로 설정한 쓰레드 풀의 이름을 설정하게 되면 해당 메소드를 사용할 때 지정한 쓰레드 풀을 사용한다.

  • 몽고DB, Nosql : Spring Webflux 사용 권장
  • rdb : 사실상 async가 의미가 없음 , 데이터베이스 단에서 트랜잭션 떄문에 동기 방식으로 동작하기 때문에 async를 적용시켜도 전체적인 플로우 자체가 async로 동작할 수 없음

'Spring' 카테고리의 다른 글

JUnit  (0) 2025.04.21
Server to Server - RestTemplate  (1) 2025.04.16
Interceptor  (0) 2025.04.15
서블릿 컨테이너와 Spring 컨텍스트  (0) 2025.04.15
Filter  (1) 2025.04.15

+ Recent posts