Today
-
Yesterday
-
Total
-
  • OkHttp3 라이브러리 사용 with 비동기
    Language/Java 2023. 11. 29. 06:22

    서울시 공공 API를 한번에 약 2만개 가량 받아와서 db에 저장할 일이 있었는데, 이때 일반적으로 사용하는 동기 방식으로는 시간이 너무 오래걸렸었다.(6초~10초 정도..??) 그래서 비동기를 적용해 응답시간을 절반이상 줄일 수 있었는데, 이때 사용해본 비동기 요청 작업에 대해 정리하면서 다시 한번 학습 !!

    그전에 OkHttp3 라이브러리에 대해서 먼저 간단히 정리 📝

    0. OkHttp3 사용 방법

    1. OkHttp 클라이언트 객체 생성

    OkHttpClient client = new OkHttpClient.Builder()
                                          .build();

    2. Request 객체 생성

    OkHttp는 클라이언트(내 pc. 서버에서 값을 받아 사용할 pc)에서 API 서버로 값을 요청한 후에 응답오는 값을 받아서 사용하는 부분으로, 서버에 요청 값을 보낼 Request 객체를 생성해야 한다.

    Request request = new Request.Builder()
                                 .url(url)
                                 .get() // get , post 등 요청할 HTTP METHOD 작성
                                 .build();

    3. Response 응답 확인

    Request로 API서버에 요청을 보냈으면 Response 응답이 온다. 이것을 받아서 데이터를 확인 한 후 사용한다.

    3-1. 동기 통신

    Response response = client.newCall(request).execute(); // 동기 통신. 비동기 통신 하려면 newCall(request).enqueue(new Callback(){ .. });
    
    if (response.isSuccessful()) {
        // 응답 데이터 형식이 json이고, 이 데이터는 HTTP 메세지의 Body에 담겨져 오기 때문에, 
        // JSON데이터는 ResponseBody 객체를 통해서 확인할 수 있다.
        ResponseBody body = response.body();
        if (body != null) {
            System.out.println("Response:" + body.string());
        }
    }

    3-2. 비동기 통신

    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            // 요청 실패시 응답받는 부분
        }
    
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            // 요청 성공시 Response 응답 받아서 코드 작성
        }
    });

    이제 내가 사용해본 비동기 코드에 대해 정리 📝

    1. OkHttp3 라이브러리 작성

    비동기 관련 부분만 가져왔다.

    1. 전체 코드

    public class MyOkHttp {
        // api 요청시 개인에게 발급되는 인증key가 필요하여 생성
        private static final String API_KEY = PrivateInfo.API_KEY;
        
        private static OkHttpClient client;
        
        // 요청 URL에 데이터 범위를 입력해줘야 해서
        // Request.builder() 라는, Request 객체를 생성하는 작업 도구를 Class 변수로 생성했다.
        private static Request.Builder builder; 
    
        // 생성자에서 초기화 진행 
        public MyOkHttp() {
            client = new OkHttpClient.Builder().build();
            builder = new Request.Builder();
        }
    
        // 여러번의 통신 과정이 필요하다고 판단하여
        // API에서 보낼 Request 객체를 완성하는 부분을 별도의 메서드로 분리했다.
        public Request getRequest(int start, int end) {
            String url = "http://openapi.seoul.go.kr:8088/" + API_KEY + "/json/TbPublicWifiInfo/" + start + "/" + end + "/";
            return builder.url(url).get().build();
        }
    
        // 여러번의 통신 과정을 위해
        // API서버로 Request 요청을 보내는 작업을 하는 메서드를 별도로 분리하였다.
        public Call getOkClient(Request request) {
            return client.newCall(request);
        }
    
        // 비동기 통신을 수행
        public CompletableFuture<Result> getApiResult(int start, int end) {
            CompletableFuture<Result> future = new CompletableFuture<>();
            
            getOkClient(getRequest(start, end)).enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    System.err.println("Error Occurred");
                }
    
                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                    ResponseBody body = response.body();
                    if (response.isSuccessful() && body != null) {
                        future.complete(new Gson().fromJson(body.string(), Result.class));
                    }
                }
            });
            
            return future;
        }
    }

    2. 메서드 뜯어보기 🤓

    1. 클래스 변수 초기화

    OkHttpClient 인스턴스와, Request 인스턴스 초기화 Request 인스턴스에 URL과 HTTP 요청 메서드를 추가해주어야 하기 때문에, builder()까지만 작성하였다.

    private static OkHttpClient client;
    private static Request.Builder builder; 
    
    // 생성자
    public MyOkHttp() {
        client = new OkHttpClient.Builder().build();
        builder = new Request.Builder();
    }

    2. Request 객체에 Url 설정

    Request 객체에 요청 URL 입력을 해주어 Request 객체 완성

    public Request getRequest(int start, int end) {
        String url = "http://openapi.seoul.go.kr:8088/" + API_KEY + "/json/TbPublicWifiInfo/" + start + "/" + end + "/";
        return builder.url(url).get().build();
    }

    3. OkHttpClient에 API 서버 요청 정보를 담은 Request 객체를 담아 Call 객체 반환

    OkHttpClient의 newCall 메서드를 이용해서 Request 요청을 실행할 Call 객체를 반환한다.

    public Call getOkClient(Request request) {
        return client.newCall(request);
    }

    4. 비동기 통신 ! 🚀

    전체 코드

    public CompletableFuture<Result> getApiResult(int start, int end) {
        CompletableFuture<Result> future = new CompletableFuture<>();
    
        getOkClient(getRequest(start, end)).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                System.err.println("Error Occurred");
            }
    
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                ResponseBody body = response.body();
                if (response.isSuccessful() && body != null) {
                    future.complete(new Gson().fromJson(body.string(), Result.class));
                }
            }
        });
    
        return future;
    }

    1. 메서드 생성

    public CompletableFuture<Result> getApiResult(int start, int end) {
        CompletableFuture<Result> future = new CompletableFuture<>();
    
        return future;
    }
    • 반환 자료형 : CompletableFuture<Result>
    • 매개변수 : 요청 url에서 요구하는 데이터 범위

    CompletableFuture<Result> 여기서 Result는 내가 작성한 DTO 클래스이다. 비동기를 하려면 CompletableFuture 클래스에 대해 알아야 한다.

    CompletableFutre 클래스 ✏️

    Java 8에서 도입된 java.util.concurrent 패키지의 클래스로, 비동기 관련 동작을 할수 있게 해준다.

    Java의 Future 인터페이스를 구현하고 확장하여, 비동기 작업의 결과를 기다리는 동안 다른 작업을 수행할 수 있도록 해준다.

    뿐만 아니라, CompletableFuture는 다양한 메서드를 제공하여 비동기 작업의 결과를 변환하거나 조합할 수 있다.

    주요 기능 및 특징

    • 비동기 실행 비동기 작업을 실행하고 결과를 저장하는 데 사용된다. 작업이 완료되면 결과가 CompletableFuture 인스턴스에 저장되고, 나중에 이 결과를 검색할 수 있다.
    • 결과 변환 thenApply, thenCompose와 같은 메서드를 사용하여 CompletableFuture의 결과를 변환하거나 다른 비동기 작업과 연결할 수 있다.
    • 결과 조합 thenCombine, allOf, anyOf 등의 메서드를 사용하여 여러 CompletableFuture의 결과를 조합할 수 있다. 이를 통해 동시에 실행되는 여러 비동기 작업의 결과를 기다리거나 처리할 수 있다.
    • 에러 처리 exceptionally, handle 등의 메서드를 사용하여 비동기 작업에서 발생한 예외를 처리할 수 있다.
    • 콜백 등록 thenAccept, thenRun과 같은 메서드를 사용하여 CompletableFuture가 완료되었을 때 실행될 콜백을 등록할 수 있다. 이를 통해 작업이 완료되면 결과를 처리하거나 추가 작업을 수행할 수 있다.

    2. 기능 구현

    public CompletableFuture<Result> getApiResult(int start, int end) {
        CompletableFuture<Result> future = new CompletableFuture<>();
    
        getOkClient(getRequest(start, end)).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                System.err.println("Error Occurred");
            }
    
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                ResponseBody body = response.body();
                if (response.isSuccessful() && body != null) {
                    future.complete(new Gson().fromJson(body.string(), Result.class));
                }
            }
        });
    
        return future;
    }

    .enqueue()는 OkHttp 라이브러리의 Call 객체를 실행하는 비동기 메서드 이다. ( 동기적으로 요청을 실행하려면 execute() 메서드 사용 )

    enqueue()메서드에 Callback() 인터페이스를 구현하여 인자로 넘겨줘야 하는데, 요청 실패시 Callback의 onFailure() 메서드로, 요청 성공시 Callback의 onResponse() 메서드로 응답이 오게 된다.

    요청 성공시 응답 결과를 CompletableFuture 객체(future 변수)에 담았다.



    2. 위에서 작성한 OkHttp3 라이브러리 사용 코드

    public class Classs {
    
    	public void methodd {
    
            MyOkHttp ok = new MyOkHttp();
    
            // api로 받을수 있는 list 개수
            int cnt = ok.getPossibleCnt();
    
            List<WifiInfo> list = new ArrayList<>();
            
            // listCf = 여러개인 비동기 응답 객체 CompletableFuture를 담을 리스트
            List<CompletableFuture<Result>> listCf = new ArrayList<>();
            for (int i = 0; i < cnt + 1; i += 1000) {
                // OkHttp3 라이브러리 구현 클래스에서 비동기 통신을 수행하는 메서드가 getApiResult()
                // 이 메서드에서 CompletableFuture 가 반환되면 위에서 만든 listCf에 담는다.
                listCf.add(ok.getApiResult(1 + i, Math.min(1000 + i, cnt)));
            }
    
            // 위에서 담은 모든 CompletableFuture 객체를 하나씩 꺼내서
            // 응답이 완료된 객체에 대해 원하는 값으로 가공한다.
            for (CompletableFuture<Result> cf : listCf) {
                cf.thenAccept(result -> {
                
                    // 결과를 받아서 결과값에 들어있는 list( getRow() )를 list변수에 담는다.
                    list.addAll(result.getWifiListInfo().getRow());
                    
                    // 모든 데이터가 list 변수에 저장이 되었다면, db 저장을 위한 작업을 한다.
                    if (list.size() == cnt) {
                        service.insert(list);
                    }
                });
            }
    
        }
    }

    'Language > Java' 카테고리의 다른 글

    자바 메모리 모델 Java Memory Model  (0) 2023.11.30
    싱글톤 패턴  (0) 2023.11.30
    Thread  (0) 2023.11.29
    제네릭 Generic 프로그래밍  (0) 2023.11.28
    Maven , Gradle  (0) 2023.11.28

Designed by Tistory / Custom by 얼거스