++
새로운 글을 작성하면서 내용을 덧붙였습니다!
2023.08.25 - [Study/Spring boot] - [Spring boot] WebClient 사용해보기 2-1
2023.08.26 - [Study/Spring boot] - [Spring boot] WebClient 사용해보기 2-2
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client
개발 중인 프로젝트에 공공데이터포털에서 제공하는 공공 API를 적용하기 위해 Spring WebClient를 적용하기 위해 공부 겸 정리하는 글을 작성해보고자 한다.
기존에는 RestTemplate를 많이 써왔지만 지금은 WebClient를 사용하는 걸 권장하고 있다고 하기 때문에 WebClient를 사용해보기로 했다. RestTemplate와 WebClient의 차이점은 구글에서 더 자세하고 알기 쉽게 설명해주기 때문에 참고할 만한 사이트를 첨부하는 걸로 대신 하기로 하고, 나는 프로젝트에 적용하는 과정을 위주로 작성할 것이다!
RestTemplate과 WebClient 차이점
https://velog.io/@yyong3519/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-WebClient
적용하려는 API
유기동물 조회 공공 API
https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098931
샘플코드
/* Java 1.8 샘플 코드 */
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.io.BufferedReader;
import java.io.IOException;
public class ApiExplorer {
public static void main(String[] args) throws IOException {
StringBuilder urlBuilder = new StringBuilder("http://apis.data.go.kr/1543061/abandonmentPublicSrvc/abandonmentPublic"); /*URL*/
urlBuilder.append("?" + URLEncoder.encode("serviceKey","UTF-8") + "=서비스키"); /*Service Key*/
urlBuilder.append("&" + URLEncoder.encode("bgnde","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*유기날짜(검색 시작일) (YYYYMMDD)*/
urlBuilder.append("&" + URLEncoder.encode("endde","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*유기날짜(검색 종료일) (YYYYMMDD)*/
urlBuilder.append("&" + URLEncoder.encode("upkind","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*축종코드 (개 : 417000, 고양이 : 422400, 기타 : 429900)*/
urlBuilder.append("&" + URLEncoder.encode("kind","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*품종코드 (품종 조회 OPEN API 참조)*/
urlBuilder.append("&" + URLEncoder.encode("upr_cd","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*시도코드 (시도 조회 OPEN API 참조)*/
urlBuilder.append("&" + URLEncoder.encode("org_cd","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*시군구코드 (시군구 조회 OPEN API 참조)*/
urlBuilder.append("&" + URLEncoder.encode("care_reg_no","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*보호소번호 (보호소 조회 OPEN API 참조)*/
urlBuilder.append("&" + URLEncoder.encode("state","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*상태(전체 : null(빈값), 공고중 : notice, 보호중 : protect)*/
urlBuilder.append("&" + URLEncoder.encode("neuter_yn","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*상태 (전체 : null(빈값), 예 : Y, 아니오 : N, 미상 : U)*/
urlBuilder.append("&" + URLEncoder.encode("pageNo","UTF-8") + "=" + URLEncoder.encode("1", "UTF-8")); /*페이지 번호 (기본값 : 1)*/
urlBuilder.append("&" + URLEncoder.encode("numOfRows","UTF-8") + "=" + URLEncoder.encode("10", "UTF-8")); /*페이지당 보여줄 개수 (1,000 이하), 기본값 : 10*/
urlBuilder.append("&" + URLEncoder.encode("_type","UTF-8") + "=" + URLEncoder.encode(" ", "UTF-8")); /*xml(기본값) 또는 json*/
URL url = new URL(urlBuilder.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-type", "application/json");
System.out.println("Response code: " + conn.getResponseCode());
BufferedReader rd;
if(conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
rd = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
}
StringBuilder sb = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
rd.close();
conn.disconnect();
System.out.println(sb.toString());
}
}
내 목표는 이 샘플 코드를 WebClient를 적용한 형태로 바꾸는 것이다.
적용하기
1. 의존성 추가
Webclient를 사용하기 위해서는 우선 dependencies를 추가해주어야한다.
implementation 'org.springframework.boot:spring-boot-starter-webflux'
2. 인스턴스 생성
WebClient의 인스턴스 생성방법에는 간단하게 바로 인스턴스를 생성하는 방법과 builder를 사용하는 방법 두가지가 있다.
- 간단하게 인스턴스를 생성하는 방법
// 기본 설정으로 생성
WebClient webclient = WebClient.create();
// base url을 추가하여 생성
WebClient webclient = WebClient.create("http://localhost:8080);
- builder를 사용하는 방법
WebClient client3 = WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultCookie("cookieKey", "cookieValue")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
builder에 들어갈 수 있는 옵션은 다음과 같다.
번역을 해서 첨부할까 했으나 내가 번역한 것보단 원문 그대로 첨부하는 편이 오역, 잘못된 정보전달의 위험이 없을 것 같아 그대로 올린다.
- uriBuilderFactory: Customized UriBuilderFactory to use as a base URL.
- defaultUriVariables: default values to use when expanding URI templates.
- defaultHeader: Headers for every request.
- defaultCookie: Cookies for every request.
- defaultRequest: Consumer to customize every request.
- filter: Client filter for every request.
- exchangeStrategies: HTTP message reader/writer customizations.
- clientConnector: HTTP client library settings.
cookie가 뭐고 header에는 뭐가 담기는 지는 당장 필요하지 않으니 이런 식으로 만들 수 있구나 하고 넘어가도록 하자. 개발공부는 써보면서 배우는게 가장 낫다고 생각하기 때문!
나는 API를 사용하기 위한 인증key를 사용하기 위해 인코딩을 해야했기 때문에 다음 글을 참고해서 파라미터 인코딩도 해주었다.
https://wpioneer.tistory.com/222
// uribuild 설정을 도와주는 DefaultUriBUilderFactory 호출
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
// 인코딩 모드 설정
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
WebClient webClient = WebClient.builder()
.uriBuilderFactory(factory)
.baseUrl(BASE_URL)
.build();
인코딩 모드 종류는 다음과 같다
- TEMPLATE_AND_VALUES: URI 템플릿을 먼저 인코딩 하고 URI 변수를 적용할 때 인코딩한다.
- VALUES_ONLY: URI 템플릿을 인코딩하지 않고 URI 변수를 템플릿에 적용하기 전에 엄격히 인코딩한다.
- URI_COMPONENT: URI 변수를 적용한 후에 URI 컴포넌트를 인코딩한다.
- NONE: 인코딩을 적용하지 않는다.
3. 응답값 요청
이제 위에서 인스턴스를 생성했으니 응답값을 요청하면 된다.
소스코드를 보면서 하나하나 설명을 붙여가 보겠음!
String response = webClient.get()
.uri(uriBuilder -> uriBuilder
.queryParam("serviceKey", serviceKey)
.queryParam("upkind", upkind)
.queryParam("upr_cd", upr_cd)
.queryParam("state", state)
.queryParam("pageNo", pageNo)
.queryParam("NumOfRows", NumOfRows)
.queryParam("_type", _type)
.build())
.retrieve()
.bodyToMono(String.class)
.block();
webClient.get() - 내가 사용하려는 API의 문서를 보면 GET방식을 사용한다고 적혀있기 때문에 webclient의 요청방식을 get으로 해주었다.
uri(uriBuilder -> uriBuilder ...) - baseURI 및 파라미터를 지정해주는 부분이다. 난 위에서 이미 baseURI를 지정해주었고, 그 뒤에 파라미터들을 uriBuilder로 build해주었다.
retrieve() - 응답값을 받게 해주는 메소드이다.
bodyToMono(String.class) - response body를 String 타입으로 받게 해준다.
block() - webclient는 기본적으로 비동기 방식인데 block메서드를 이용해 동기 방식으로 바꿔준다. block을 붙여줘야 string으로 바꿀 수 있다고 한다.
+
동기 & 비동기 / 블럭 & 논블럭의 개념은 비슷해 보이지만 엄연히 다르다고 한다. 저것들의 이해가 부족해서 혼합해서 쓰는 에러를 범해버렸음... 해당 개념들을 스터디하고 정리해서 포스팅해야겠다!
mono와 flux의 개념을 알기 위해선 Spring WebFlux를 공부해야한다. 아직 완벽한 이해가 부족하기 때문에 참고를 위해 링크를 남겨놓겠다
https://devuna.tistory.com/108
모든 코드를 따로 나누지 않고 한 곳에 담아서 테스트 해보았다.
private final static String BASE_URL = "http://apis.data.go.kr/1543061/abandonmentPublicSrvc/abandonmentPublic";
private final String API_KEY = "Y8ysgjcfITdLKYzk9pQp6pzphI2yY95czKzFUggqOQCdYuYLm9oAOBh%2Fhn1meZKp1UPtONWLAAIbu7McjP9R9Q%3D%3D";
@RequestMapping(value = "/webclient-test", produces = "application/json; charset=utf8")
public String getAbandonedAnimal(Model model) {
String serviceKey = API_KEY;
String upkind = "422400";
String upr_cd = "6110000";
String state = "notice";
String pageNo = "1";
String NumOfRows = "8";
String _type = "json";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
WebClient webClient = WebClient.builder()
.uriBuilderFactory(factory)
.baseUrl(BASE_URL)
.build();
Mono<Map<Object, Object>> response = webClient.get()
.uri(uriBuilder -> uriBuilder
.queryParam("serviceKey", serviceKey)
.queryParam("upkind", upkind)
.queryParam("upr_cd", upr_cd)
.queryParam("state", state)
.queryParam("pageNo", pageNo)
.queryParam("NumOfRows", NumOfRows)
.queryParam("_type", _type)
.build())
.retrieve()
.bodyToMono(String.class)
.block();
model.addAttribute("response", response);
return "test/test";
}
응답값으로 받은 Object를 JSP로 보내주는 코드이다.
이렇게 테스트까지 성공적으로 마쳤다.
마치며
처음 사용해보는 WebClient를 다루면서 애를 많이 먹었다. 처음부터 하나하나 공부하기엔 시간도 부족하고 재미도 떨어져서 하나씩 찾아보면서 적용해보고 글로 정리하니 WebClient를 처음 접했을 때보다 두려움이 많이 줄어들었다.
이렇게 또 하나를 배우고 나니 뿌듯하구만!
정리하면서 생긴 스터디 할 것들은 빠른 시일 내에 정리하도록 하자!
참고
[Spring boot] WebClient를 이용한 API호출
'Study > Spring boot' 카테고리의 다른 글
[JPA] ORM(Object-Relational Mapper) (0) | 2022.06.05 |
---|---|
[Spring boot] Ajax로 보낸 JSON 서버에서 받기 (0) | 2022.04.12 |
[Spring boot] file upload 한글 깨짐 (0) | 2022.03.23 |
[Spring boot] Snake_case to camelCase (0) | 2022.03.22 |
[Spring boot] 의존성 주입(DI) 복습 (0) | 2022.02.23 |