티스토리 뷰
개요
- Java 진영의 Spring 생태계에서 가장 널리 쓰이는 REST 클라이언트 라이브러리로는
Spring RestTemplate
,Netflix Feign
,Square Retrofit
이 있다. 이 중에서도 Square Retrofit은 마치 @RestController의 클라이언트 버전을 보는 것과 같은 우아하게 구조화된 인터페이스를 제공하여 독보적이라 할 만하다. Retrofit
은 트위터의 최고 경영자인 잭 도시가 소유한 또 다른 미국 소재의 PG 결제 서비스 전문 회사인 Square에서 만든 라이브러리이다. Retrofit은 내부적인 로우 레벨 통신을OkHttp
가 담당하는데 이 또한 같은 회사에서 만든 라이브러리이다. 안드로이드 진영에서는 둘다 킬러 라이브러리로 폭넓게 쓰이고 있다.- Retrofit은 요청과 응답 데이터에 있어 타입을 강제한다.(Type-safe라고도 한다.) 따라서 개발자는 IDE 레벨에서 안전하고 실수 없는 충분히 예측 가능한 소스 코드를 작성할 수 있다.
- 본 글에서는 Spring Boot 프로젝트에 Retrofit을 적용하는 방법을 설명하고자 한다. REST 요청의 대상이 되는 서비스는 REST 테스트용 더미 서비스를 제공하는 JSONPlaceholder로 하였다.
프로젝트 생성
- Spring Initializr에 접속하여 기본 골격 프로젝트를 생성한다. Dependencies 항목에서 Web을 추가하고 Generate Project를 클릭하면 압축된 프로젝트 파일을 다운로드할 수 있다. 압축을 해제하고 build.gradle 파일을 IDE에서 불러오면 된다.
build.gradle 추가
- 생성된 기본 /build.gradle 파일에 아래 내용을 추가한다.
dependencies {
compile group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.3.0'
compile group: 'com.squareup.retrofit2', name: 'converter-jackson', version: '2.3.0'
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.9.3'
provided group: 'org.projectlombok', name: 'lombok', version: '1.16.20'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
- 만약
RxJava
기반의 비동기 HTTP 요청을 구현하고자 한다면 아래와 같이 라이브러리 종속성을 추가한다.
dependencies {
compile group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.1.9'
compile group: 'com.squareup.retrofit2', name: 'adapter-rxjava2', version: '2.3.0'
}
- RxJava와 Retrofit RxJava Adaptor 아티팩트를 추가하면
Observable
모델을 이용한 유연하고 직관적인 요청 로직을 구현할 수 있다.
POJO 작성
- 대상 서비스에 대한 요청, 응답 데이터를 담을 POJO 클래스를 아래와 같이 작성한다.
package com.example.demo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class Post {
@JsonProperty("id")
private Long postId;
@JsonProperty("userId")
private Long userId;
@JsonProperty("title")
private String title;
@JsonProperty("body")
private String body;
}
package com.example.demo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class Comment {
@JsonProperty("id")
private Long commentId;
@JsonProperty("postId")
private Long postId;
@JsonProperty("name")
private String name;
@JsonProperty("body")
private String body;
}
- POJO 클래스를 통해 개발자는 로우 레벨의 HTTP 인터페이스를 잊고 소스 코딩 하듯이 REST 요청을 수행할 수 있다.
Interceptor 작성
- 인터셉터 역할을 수행할 클래스를 아래와 같이 작성한다.
package com.example.demo; class="language-java"
import okhttp3.Interceptor;
import okhttp3.Response;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class JsonPlaceholderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
return chain.proceed(
chain.request().newBuilder()
.addHeader("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE)
.addHeader("Cache-Control", "no-cache")
.addHeader("Cache-Control", "no-store")
.build()
);
}
}
- 인터셉터 구현체의 구현은 필수가 아닌 옵션이다. 전체 REST 요청에 있어 반복되게 나타나는 동일 파라메터를 한 번에 처리하기 위한 용도로 주로 사용된다. 위 예제에서는 모든 요청에 첨부될 공통 헤더 파라메터를 추가한 것이다.
Service 작성
- Retrofit를 빛내주는 핵심 기능인 Service 클래스를 아래와 같이 작성한다.
package com.example.demo;
import retrofit2.Call;
import retrofit2.http.*;
import java.util.List;
public interface JsonPlaceholderService {
@GET("/posts/{postId}")
Call<Post> getPost(@Path("postId") long postId);
@GET("/posts")
Call<List<Post>> getPosts();
@POST("/posts")
Call<Post> createPost(@Body Post post);
@PUT("/posts/{postId}")
Call<Post> updatePost(@Path("postId") long postId, @Body Post post);
@DELETE("/posts/{postId}")
Call deletePost(@Path("postId") long postId);
@GET("/posts/{postId}/comments")
Call<List<Comment>> getCommentsByPostId(@Path("postId") long postId);
@GET("/comments")
Call<List<Comment>> getComments(@Query("postId") long postId);
}
- 위 예제는 JSONPlaceholder에서 제공하는 REST 서비스 엔드포인트를 추상화한 것이다. 한 눈에 봐도 @RestController와 같이 직관적인 인터페이스를 제공한다는 것을 알 수 있다. 앞서 작성된 POJO 클래스가 요청 바디 및 응답 바디를 담을 목적으로 사용되었다.
- 리턴 타입은 기본적으로
Call<T>
타입이 요구된다. 요청의 대한 응답을 한 번 더 감싸 성공 여부 및 상태 코드를 제공한다. 만약 Java8CallAdapterFactory를 적용할 경우CompletableFuture<T>
로 대체할 수 있다. 이 경우 Java 8이 제공하는 레퍼런스 비동기 코드를 작성할 수 있다. - 아래와 같이 RxJava의
Observable<T>
타입으로 리턴 타입을 교체하면 RxJava의 모든 특징을 이용하여 비동기 요청 처리 로직을 구현할 수 있다.
@GET("/posts/{postId}")
Observable<Post> getPost(@Path("postId") long postId);
Config 클래스 작성
- 앞서의 기능을 작동하게 해주는 빈을 등록할 차례이다.
package com.example.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
@Configuration
public class JsonPlaceholderConfig {
@Autowired
private Interceptor jsonPlaceholderInterceptor;
@Bean("jsonPlaceholderOkHttpClient")
public OkHttpClient jsonPlaceholderOkHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(jsonPlaceholderInterceptor)
.build();
}
@Bean("jsonPlaceholderObjectMapper")
public ObjectMapper jsonPlaceholderObjectMapper() {
return Jackson2ObjectMapperBuilder.json()
.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.modules(new JavaTimeModule())
.build();
}
@Bean("jsonPlaceholderRetrofit")
public Retrofit jsonPlaceholderRetrofit(
@Qualifier("jsonPlaceholderObjectMapper") ObjectMapper jsonPlaceholderObjectMapper,
@Qualifier("jsonPlaceholderOkHttpClient") OkHttpClient jsonPlaceholderOkHttpClient
) {
return new Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com")
.addConverterFactory(JacksonConverterFactory.create(jsonPlaceholderObjectMapper))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(jsonPlaceholderOkHttpClient)
.build();
}
@Bean("jsonPlaceholderService")
public JsonPlaceholderService jsonPlaceholderService(
@Qualifier("jsonPlaceholderRetrofit") Retrofit jsonPlaceHolderRetrofit
) {
return jsonPlaceHolderRetrofit.create(JsonPlaceholderService.class);
}
}
- 앞서 Retrofit은 내부적으로 OkHttp를 사용한다고 언급했었다. 위 예제와 같이 커스터마이징된
OkHttpClient
빈을 등록해둔다. 이 과정에서 먼저 작성한 인터셉터 클래스를 등록할 수 있다. - REST 추상화의 핵심은 실제 RAW 데이터와 POJO 오브젝트 간의 매끄러운 상호변환이다. Retrofit은 다양한 오픈 소스 JSON 라이브러리에 대한 컨버터를 제공한다. 위 예제는 Spring Boot의 기본 JSON 라이브러리인
ObjectMapper
빈을 등록하였다. Retofit
빈은 핵심 코어이다. 앞서 작성한 빈을 이용하여 빈을 등록한다. 이 과정에서 대상 REST 서비스에 대한baseUrl
을 설정할 수 있다.- 마지막으로 Service 빈을 등록한다.
사용 예
- 앞서의 작업으로 모든 설정이 끝났다. 이제 작성된 기능을 사용할 차례이다.
@Autowired
JsonPlaceholderService jsonPlaceholderService;
...
Response<List<Post>> getPostsResponse = jsonPlaceholderService.getPosts().execute();
Post newPost = new Post();
newPost.setTitle("newTitle");
newPost.setBody("newBody");
newPost.setUserId(1L);
Response<Post> createPostResponse = jsonPlaceholderService.createPost(newPost).execute();
Response<Post> getPostResponse = jsonPlaceholderService.getPost(100L).execute();
Response<Comment> getCommentsByPostIdResponse = jsonPlaceholderService.getCommentsByPostId(1L).execute();
- RxJava의 Observable
리턴 타입으로 구현했을 경우 아래와 같이 작성할 수 있다.
jsonPlaceholderService.getPost(1L)
.subscribeOn(Schedulers.io())
.repeat() // 해당 요청을 반복하고 싶을 경우 실행, 생략하면 1회 요청
.doOnError(error -> {
if (error instanceof UnknownHostException) {
// UnknownHostException 예외 발생시 처리
}
if (error instanceof HttpException) {
// 상태 코드 2XX 외의 응답시 예외 처리
}
})
.subscribe(post -> {
log.info("postId: {}", post.getPostId()); // 실제 로직의 실행
});
.subscribeOn()
메써드에 스케쥴러 타입을 명시할 수 있다. 명시하지 않을 경우 HTTP 요청은 현재 쓰레드에서 동기로 처리된다.Schedulers.io()
를 명시하면 CPU 또는 메모리를 많이 소모하지 않는 파일, 네트워크 관련 작업에 적합한 쓰레드 풀을 이용하여 요청을 비동기로 처리한다. HTTP 요청은 전형적인 네트워크 Non-Blocking 작업이므로 적합하다.
참고 글
댓글
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- node.js
- graylog
- Kendo UI
- Docker
- 태그를 입력해 주세요.
- 자전거
- Kendo UI Web Grid
- jpa
- spring
- 구동계
- Spring MVC 3
- maven
- Spring Boot
- jstl
- Tomcat
- 로드바이크
- MySQL
- JavaScript
- chrome
- 로드 바이크
- Eclipse
- bootstrap
- 평속
- CentOS
- 알뜰폰
- JHipster
- jsp
- DynamoDB
- kotlin
- java
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함