Spring - Bean Validation, RestTemplate

김상엽·2024년 2월 2일
0

Spring

목록 보기
11/26
post-thumbnail

TIL

Validation

  • 회원가입을 진행하면, 변수의 값을 검증하는 상황이 많이 발생한다.
  • 이렇게 의도한 변수와 다른 형태의 변수가 들어오지 않도록 검증을 해야한다.
  • 하지만 각각의 변수마다 검증메서드를 통해서 검증하는것은 굉장히 번거로운 작업이다.

Bean Validation

  • 어노테이션 형태로 제약 조건을 달아줘서 쉽게 검증할 수 있도록 돕는 API이다.
  • Bean Validation은 인터페이스로 된 명세일 뿐이고 실제 동작할 수 있도록 구현한 것이 Hibernate Validator이다.
implementation 'org.springframework.boot:spring-boot-starter-validation'

package com.sparta.springauth.dto;

import jakarta.validation.constraints.*;
import lombok.Getter;

@Getter
public class ProductRequestDto {
    @NotBlank
    private String name;
    @Email
    private String email;
    @Positive(message = "양수만 가능합니다.")
    private int price;
    @Negative(message = "음수만 가능합니다.")
    private int discount;
    @Size(min=2, max=10)
    private String link;
    @Max(10)
    private int max;
    @Min(2)
    private int min;
}
  • 이렇게 어노테이션 형태 편리하게 제약조건을 달아주면 된다.
@PostMapping("/validation")
@ResponseBody
public ProductRequestDto testValid(@RequestBody @Valid ProductRequestDto requestDto) {
    return requestDto;
}
  • 그리고 Bean Validation을 적용한 해당 Object에 @Valid를 통해 validation을 실행한다.

BindingResult

  • Validation 과정에서 예외가 발생하면 BindingResult 객체에 오류에 대한 정보가 담긴다.
  • bindingResult.getFieldErrors() 를 통해
    발생한 오류들에 대한 정보가 담긴 List<FieldError> 리스트를 가져올 수 있다.

RestTemplate

  • 지금까지는 Client와 Server간의 요청을 처리하는 개발을 진행해왔다.
  • 하지만 다른 Open API를 이용해야한다면, 그때는 우리의 서버가 Client의 입장이 되어 요청을 해야한다.
  • Spring에서는 서버에서 다른 서버로 간편하게 요청할 수 있도록 RestTemplate 기능을 제공한다.

RestTemplate 사용하기

  • PC 한대로 프로젝트 2개를 만들어서 Client(8080포트), Server(7070포트)으로 구현을 하였다.

RestTemplate의 Get 요청

Client

    1. RestTemplate을 주입 받는다.
private final RestTemplate restTemplate;

// RestTemplateBuilder의 build()를 사용하여 RestTemplate을 생성합니다.
public RestTemplateService(RestTemplateBuilder builder) {
    this.restTemplate = builder.build();
}
    1. 요청 받은 검색어를 Query String 방식으로 Server로 RestTemplate를 사용하여 요청한다.
public ItemDto getCallObject(String query) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/get-call-obj")
            .queryParam("query", query)
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

    ResponseEntity<ItemDto> responseEntity = restTemplate.getForEntity(uri, ItemDto.class);

    log.info("statusCode = " + responseEntity.getStatusCode());

    return responseEntity.getBody();
}
  • Spring의 UriComponentsBuilder를 사용해서 URI를 손쉽게 만들 수 있다.
  • RestTemplate의 getForEntity는 Get 방식으로 해당 URI의 서버에 요청을 진행한다.
    • 첫 번째 파라미터에는 URI, 두 번째 파라미터에는 전달 받은 데이터와 매핑하여 인스턴스화할 클래스의 타입을 주면됨
  • 요청의 결과값에 대해서 직접 JSON TO Object를 구현할 필요없이 RestTemplate을 사용하면 자동으로 처리 해준다.
    • 따라서 response.getBody() 를 사용하여 두 번째 파라미터로 전달한 클래스 타입으로 자동 변환된 객체를 가져올 수 있다.

Server

    1. Server 입장의 서버에서 itemList를 조회하여 요청받은 검색어에 맞는 Item을 반환한다.
public Item getCallObject(String query) {
    for (Item item : itemList) {
        if(item.getTitle().equals(query)) {
            return item;
        }
    }
    return null;
}

만약 결과값이 다중 JSON으로 넘어온다면?

public List<ItemDto> fromJSONtoItems(String responseEntity) {
    JSONObject jsonObject = new JSONObject(responseEntity);
    JSONArray items  = jsonObject.getJSONArray("items");
    List<ItemDto> itemDtoList = new ArrayList<>();

    for (Object item : items) {
        ItemDto itemDto = new ItemDto((JSONObject) item);
        itemDtoList.add(itemDto);
    }

    return itemDtoList;
}
    1. JSON To Object를 사용하지 않고 일단 String 값 그대로를 가져와서
    1. 문자열 정보를 JSONObject로 바꾸기
    1. JSONObject에서 items 배열 꺼내기
    1. JSONArray로 for문 돌면서 상품 하나씩 JSONObject에서 ItemDto로 변환하기

RestTemplate의 Post 요청

Client

    1. 요청 받은 검색어를 Query String 방식으로 Server로 RestTemplate를 사용하여 요청한다.
public ItemDto postCall(String query) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/post-call/{query}")
            .encode()
            .build()
            .expand(query)
            .toUri();
    log.info("uri = " + uri);

    User user = new User("Robbie", "1234");

    ResponseEntity<ItemDto> responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class);

    log.info("statusCode = " + responseEntity.getStatusCode());

    return responseEntity.getBody();
}
  • UriComponentsBuilderexpand를 사용하여 {query} 안의 값을 동적으로 처리할 수 있다.
  • RestTemplate의 postForEntityPost 방식으로 해당 URI의 서버에 요청을 진행한다.
    • 첫 번째 파라미터에는 URI, 두 번째 파라미터에는 HTTP Body에 넣어줄 데이터를 주면됨
    • Java 객체를 두 번째 파라미터에 넣으면 자동으로 JSON 형태로 변환됨
    • 세 번째 파라미터에는 전달 받은 데이터와 매핑하여 인스턴스화할 클래스의 타입을 주면됨

Server

    1. Server에서 itemList를 조회하여 요청받은 검색어에 맞는 Item을 반환한다.
public Item postCall(String query, UserRequestDto userRequestDto) {
    System.out.println("userRequestDto.getUsername() = " + userRequestDto.getUsername());
    System.out.println("userRequestDto.getPassword() = " + userRequestDto.getPassword());

    return getCallObject(query);
}
  • 전달 받은 HTTP Body의 User 데이터를 확인한다.

RestTemplate의 exchange

Client

    1. RestTemplate의 exchange를 사용한다.
public List<ItemDto> exchangeCall(String token) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/exchange-call")
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

    User user = new User("Robbie", "1234");

    RequestEntity<User> requestEntity = RequestEntity
            .post(uri)
            .header("X-Authorization", token)
            .body(user);

    ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);

    return fromJSONtoItems(responseEntity.getBody());
}
  • exchange 메서드의 첫 번째 파라미터에 RequestEntity 객체를 만들어 전달해주면 uri, header, body의 정보를 한번에 전달할 수 있다.

Server

    1. 전달된 header와 body의 정보를 확인할 수 있다.
public ItemResponseDto exchangeCall(String token, UserRequestDto requestDto) {
    System.out.println("token = " + token);
    System.out.println("requestDto.getUsername() = " + requestDto.getUsername());
    System.out.println("requestDto.getPassword() = " + requestDto.getPassword());

    return getCallList();
}

오늘의 회고

이제까지는 local서버에서 꼼지락거리는 느낌의 개발이었지만, 오늘은 Naver의 Open API를 활용해보았는데 상당히 재밌었따.
메이플스토리나 로스트아크, 롤 처럼 전적검색하는 사이트를 보면서 참 신기하다고 생각했는데 이제는 작동원리를 알게되어 참 신기하다.
강의도 얼마 안남았으니 빨리 털고 코딩하고싶다 :)

profile
개발하는 기록자

0개의 댓글