48.5. 비동기 응답

48.5.1. 서버의 비동기 처리

48.5.1.1. 개요

서버 측에서 비동기 호출을 처리하는 목적은 스레드를 보다 효율적으로 사용할 수 있도록 하고 궁극적으로 서버의 요청 스레드가 모두 차단되기 때문에 클라이언트 연결 시도가 거부되는 시나리오를 방지하는 것입니다. 호출이 비동기적으로 처리되면 요청 스레드가 거의 즉시 해제됩니다.

참고

서버 측에서 비동기 처리를 사용하도록 설정해도 서버에서 응답을 수신 할 때까지 클라이언트는 여전히 차단된 상태로 유지됩니다. 클라이언트 쪽에서 비동기 동작을 보려면 클라이언트 쪽 비동기 처리를 구현해야 합니다.If you want to see asynchronous behavior on the client side, you must implement client-side asynchronous processing. 49.6절. “클라이언트의 비동기 처리” 을 참조하십시오.

48.5.1.2. 비동기 처리를 위한 기본 모델

그림 48.1. “비동기 처리를 위한 스레딩 모델” 서버 측에서 비동기 처리를 위한 기본 모델에 대한 개요를 보여줍니다.

그림 48.1. 비동기 처리를 위한 스레딩 모델

asyncresponse 01

개요에서 요청은 비동기 모델에서 다음과 같이 처리됩니다.In outline, a request is processed as follows in the asynchronous model:

  1. 비동기 리소스 메서드는 요청 스레드 내에서 호출되고 AsyncResponse 개체에 대한 참조를 받습니다.An asynchronous resource method is invoked within a request thread (and receives a reference to an AsyncResponse object, which will be needed later to send the response).
  2. 리소스 메서드는 요청을 처리하는 데 필요한 모든 정보 및 처리 논리를 포함하는 실행 가능 개체에서 일시 중지된 요청을 캡슐화합니다.
  3. 리소스 메서드는 실행 가능한 개체를 executor 스레드 풀의 차단 대기열로 푸시합니다.
  4. 이제 리소스 메서드가 반환되어 요청 스레드가 해제될 수 있습니다.
  5. 실행 가능 개체가 큐의 맨 위에 도달하면 executor 스레드 풀의 스레드 중 하나에 의해 처리됩니다.When the Runnable object gets to the top of the queue, it is processed by one of the threads in the executor thread pool. 그런 다음 캡슐화된 AsyncResponse 오브젝트를 사용하여 응답을 클라이언트에 다시 보냅니다.

48.5.1.3. Java executor를 사용한 스레드 풀 구현

java.util.concurrent API는 매우 쉽게 전체 스레드 풀 구현을 생성할 수 있는 강력한 API입니다. Java 동시성 API의 용어에서 스레드 풀을 execut or라고 합니다. 이는 작업 스레드 및 해당 스레드를 제공하는 차단 대기열을 포함하여 전체 작업 스레드 풀을 만들려면 단일 코드 줄만 필요합니다.

예를 들어 그림 48.1. “비동기 처리를 위한 스레딩 모델” 에 표시된 Executor Thread Pool 과 같은 전체 작업 스레드 풀을 생성하려면 다음과 같이 java.util.concurrent.Executor 인스턴스를 만듭니다.

Executor executor = new ThreadPoolExecutor(
    5,                                    // Core pool size
    5,                                    // Maximum pool size
    0,                                    // Keep-alive time
    TimeUnit.SECONDS,                     // Time unit
    new ArrayBlockingQueue<Runnable>(10)  // Blocking queue
);

이 생성자는 스레드 5개가 포함된 새 스레드 풀을 만들고, 단일 차단 대기열에 의해 제공되며 최대 10개의 Runnable 개체를 보유할 수 있습니다. 스레드 풀에 작업을 제출하려면 executor.execute 메서드를 호출하여 실행 가능한 개체( asynchronous 작업을 캡슐화하는 실행 가능 오브젝트)에 대한 참조를 전달합니다.

48.5.1.4. 비동기 리소스 메서드 정의

비동기적인 리소스 메서드를 정의하려면 @Suspended 주석을 사용하여 javax.ws.rs.container.AsyncResponse 유형의 인수를 주입하고 메서드가 void 를 반환하는지 확인합니다. 예를 들면 다음과 같습니다.

// Java
...
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;

@Path("/bookstore")
public class BookContinuationStore {
    ...
    @GET
    @Path("{id}")
    public void handleRequestInPool(@PathParam("id") String id,
                                    @Suspended AsyncResponse response) {
        ...
    }
    ...
}

삽입된 AsyncResponse 오브젝트가 나중에 응답을 반환하는 데 사용되므로 리소스 메서드가 void 를 반환해야 합니다.

48.5.1.5. AsyncResponse 클래스

javax.ws.rs.container.AsyncResponse 클래스는 들어오는 클라이언트 연결에서 추상 핸들을 제공합니다. AsyncResponse 오브젝트가 리소스 메서드에 삽입되면 기본 TCP 클라이언트 연결이 초기에 일시 중지된 상태입니다. 나중에 응답을 반환할 준비가 되면 기본 TCP 클라이언트 연결을 다시 활성화하고 AsyncResponse 인스턴스에서 resume 를 호출하여 응답을 다시 전달할 수 있습니다. 또는 호출을 중단해야 하는 경우 AsyncResponse 인스턴스에서 취소 를 호출할 수 있습니다.

48.5.1.6. 일시 중단된 요청을 실행 가능으로 캡슐화

그림 48.1. “비동기 처리를 위한 스레딩 모델” 에 표시된 비동기 처리 시나리오에서는 일시 중단된 요청을 전용 스레드 풀에서 나중에 처리할 수 있는 큐로 푸시합니다. 그러나 이러한 접근 방식이 작동하려면 개체에서 일시 중단된 요청 을 캡슐화하는 방법이 필요합니다. 일시 중단된 요청 오브젝트는 다음 사항을 캡슐화해야 합니다.

  • 들어오는 요청의 매개 변수(있는 경우).
  • 들어오는 클라이언트 연결에 대한 핸들과 응답을 다시 보내는 방법을 제공하는 AsyncResponse 오브젝트입니다.
  • 호출 논리입니다.

이러한 사항을 캡슐화하는 편리한 방법은 Runnable 클래스를 정의하여 일시 중지된 요청을 나타내는 것입니다. 여기서 Runnable.run() 메서드는 호출 논리를 캡슐화하는 것입니다. 가장 우아한 방법은 다음 예제와 같이 로컬 클래스로 Runnable 을 구현하는 것입니다.

48.5.1.7. 비동기 처리 예

비동기 처리 시나리오를 구현하려면 리소스 메서드 구현에서 실행 가능한 실행 가능 오브젝트를 executor 스레드 풀에 전달해야 합니다.To implement the asynchronous processing scenario, the implementation of the resource method must pass a Runnable object (representing the suspended request) to the executor thread pool. Java 7 및 8에서는 다음 예제와 같이 일부 새로운 구문을 사용하여 Runnable 클래스를 로컬 클래스로 정의할 수 있습니다.

// Java
package org.apache.cxf.systest.jaxrs;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.CompletionCallback;
import javax.ws.rs.container.ConnectionCallback;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.container.TimeoutHandler;

import org.apache.cxf.phase.PhaseInterceptorChain;

@Path("/bookstore")
public class BookContinuationStore {

    private Map<String, String> books = new HashMap<String, String>();
    private Executor executor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS,
                                        new ArrayBlockingQueue<Runnable>(10));

    public BookContinuationStore() {
        init();
    }
    ...
    @GET
    @Path("{id}")
    public void handleRequestInPool(final @PathParam("id") String id,
                                    final @Suspended AsyncResponse response) {
        executor.execute(new Runnable() {
            public void run() {
                // Retrieve the book data for 'id'
                // which is presumed to be a very slow, blocking operation
                // ...
                bookdata = ...
                // Re-activate the client connection with 'resume'
                // and send the 'bookdata' object as the response
                response.resume(bookdata);
            }
        });
    }
    ...
}

리소스 메서드 인수, idresponseRunnable 로컬 클래스 정의로 직접 전달되는 방법을 확인합니다. 이 특수 구문을 사용하면 로컬 클래스에서 해당 필드를 정의하지 않고도 Runnable.run() 메서드에서 직접 리소스 메서드 인수를 사용할 수 있습니다.

중요

이 특수 구문이 작동하려면 리소스 메서드 매개 변수를 final 로 선언 해야 합니다(즉, 메서드 구현에서 변경되지 않아야 함).