일반적으로 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 |