티스토리 뷰



스프링 부트에서는 쓰레드나 스프링 프레임워크의 TaskExecutor 등을 사용하지 않아도, 쉽게 비동기 메소드를 실행할 수 있습니다.


가장 기본적인 예제로는 역시 spring 공홈에 있는게 쉽고 보기가 좋습니다.
( 링크 : 
https://spring.io/guides/gs/async-method/ )


공홈에 있는 예제를 따라 해보도록 하겟습니다.


추가적으로, 소개겸 간단한 사용으로 lombok도 사용해보도록 하죠.


먼저, 스프링 부트 프로젝트를 간단하게 생성합니다.


Eclipse -> File메뉴 -> New -> Spring Start Project 로 스프링 프로젝트를 생성하도록 하죠.




예제에서 필요한 라이브러리는 Web 만 있으면 됩니다만, 소개겸 간단 사용을 위해 Lombok 을 같이 포함시켰습니다. Finish를 눌러 프로젝트 생성을 합니다.


공홈에서 보여주는 예제의 필요한 라이브러리는 모두 포함됩니다. pom.xml 파일을 Eclipse에서 볼 때, Dependency Hierarchy 탭이 있는데 여길 보면 의존성에 포함되어 있는 모든 라이브러리를 확인할 수 잇습니다.



그럼 기본 예제대로 먼저, User Object를 생성하도록 하겟습니다.



가장 기본적인 예제이므로, User는 name과 blog의 변수값만 가집니다. 옆에 Outline을 같이 보여드린 이유는 Lombok의 기능을 설명드리기 위해서 입니다.


사실 Eclipse에서는 getter/setter, toString 등 기본적으로 생성해주는 기능이 있어, getter/setter 등을 생성하는 것은 일도 아닙니다만..

lombok을 사용한 이후에는 getter/setter 코드도 작성되어서 보이는 것이 지저분해 보이더군요.


lombok을 사용하면, @Data 애노테이션을 활용해서, getter/setter, toString, hashCode, equals, canEqual 이 기본적으로 생성됩니다.

@Data외에도 다양한 애노테이션과 상세 설정을 위한 애노테이션을 지원하니 링크 - https://projectlombok.org/ 를 참조해주세요.


@JsonIgnoreProperties(ignoreUnknown=true)는 JSON데이터를 받아와서 객체로 Serialize할 때, name, blog 값 외에는 무시하라는 의미입니다.


예제에서 제공해주는 사용자 정보 목록은 name, blog 외에도 다양한 값이 있습니다.  ( 링크 - https://api.github.com/users 참조 )


간단한 예제 사용을 위해, 스프링에서 main 처럼 시작과 동시에 실행할 수 있는, CommandLineRunner 인터페이스를 활용하도록 하겟습니다.

스프링 부트 프로젝트의 Main 클래스 격인 [ProjectName]Application.java 파일을 열어주세요.



CommandLineRunner를 상속받으면, run 메소드가 오버라이드 되고, 이 부분에다가 예제를 실행할 코드를 작성하겟습니다.


실행 코드를 작성하기에 앞서, 공홈의 예제 처럼 데이터를 조회하는 Service 클래스를 만들도록 하겟습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package kr.co.async;
 
import java.util.concurrent.CompletableFuture;
 
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
 
@Service
public class GitHubLookupService {
 
  RestTemplate restTemplate = new RestTemplate();
 
  @Async
  public CompletableFuture<User> findUser(String user) throws InterruptedException {
    System.out.println("Looking up " + user);
    User results = restTemplate.getForObject("https://api.github.com/users/" + user, User.class);
    // Artificial delay of 1s for demonstration purposes
    Thread.sleep(1000L);
    return CompletableFuture.completedFuture(results);
  }
}
 
cs


공홈 예제와는 다르게 여기서는 Future 대신에 CompletableFuture를 사용하도록 하겟습니다.

CompletableFuture도 Future 를 상속한 클래스이고, Java 8에서 추가되었습니다.  

완료가능한 퓨처의 의미로 기존 퓨처와 달리 여러 비동기 이벤트들을 합성할 수 있습니다. 

여러 액션을 지원하지만, 이외에 사용하는 API들은 검색을 해보시길 바랍니다. ( 링크 - CompletableFuture )


GitHubLookupService는 위에서 설명드렸던 User API에서 사용자 이름을 입력받아 해당하는 사용자의 정보를 가져오는 예제입니다.

외부의 API를 조회하여, 데이터를 가져오는 기능이라고 보면 다른 곳에도 적용하기 쉬울 것 같네요.


필요한 기능은 모두 작성된 것 같습니다. 실행을 위한 코드를 작성해 보도록할게요.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package kr.co.async;
 
import java.util.concurrent.CompletableFuture;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
@EnableAsync
public class AsyncExampleApplication implements CommandLineRunner {
 
  @Autowired
  GitHubLookupService gitHubLookupService;
 
  public static void main(String[] args) {
    SpringApplication.run(AsyncExampleApplication.class, args);
  }
 
  @Override
  public void run(String... arg0) throws Exception {
    long start = System.currentTimeMillis();
 
    CompletableFuture<User> page1 = gitHubLookupService.findUser("PivotalSoftware");
    CompletableFuture<User> page2 = gitHubLookupService.findUser("CloudFoundry");
    CompletableFuture<User> page3 = gitHubLookupService.findUser("Spring-Projects");
 
    CompletableFuture.allOf(page1, page2, page3).join();
 
    System.out.println("Elapsed time: " + (System.currentTimeMillis() - start) + " ms");
 
    System.out.println(page1.get());
    System.out.println(page2.get());
    System.out.println(page3.get());
 
  }
}
 
cs


run() 메소드의 내용을 잘 살펴보도록 하겟습니다.


비동기로 작성한 코드를 실행하기 전에 AsyncExampleApplication 상단에 @EnableAsync 애노테이션이 추가된 것을 확인할 수 있습니다.

만약 @EnableAsync가 없으면, @Async라는 애노테이션이 적용된 기능이 모두 순차적으로 실행이 되고 맙니다.

@EnableAsync가 있을 때, 없을 때를 구분하여 실행해보세요. 실행하는 순서와 시간의 차이를 확인하실 수 잇습니다.


공홈 예제에서는 기본 Future를 사용하여, 무한 조건반복문을 돌려 각각의 비동기로 실행되는 page1, page2, page3 이 종료 되는 것을 

각각 체크해서 모두 처리가 완료된 것을 확인(isDone() 메소드) 하여, 결과 값을 출력합니다.


위 예제에서는 CompletableFuture를 사용하여, 해당 클래스에서 지원하는 allOf 메서드를 활용합니다.

allOf의 변수로 종료를 확인하고자 하는 비동기 메소드들을 파라미터로 모두 입력하면, 해당 비동기 메소드 결과들이 저장될 때 까지 대기합니다. 


비동기로 실행한 결과 값이 필요없이 실행만 원하는 기능을 만드신다면 굳이, 종료를 기다리는 위와 같은(allOf나 공홈의 isDone 등)을 사용하실 필요가 없습니다. 그냥 실행하고 넘어가면 되죠.


최근에 비동기로 실행해야 하는 경우가 있어서, 찾아보고 사용한 뒤에 정리를 위해 스프링 공홈에 있는 쉬운 예제로 정리를 해보았습니다.


감사합니다!



댓글