JSON 응답과 요청 처리

예지성준·2024년 7월 17일

스프링프레임워크

목록 보기
13/14
post-thumbnail

JSON 응답과 요청 처리

1. JSON 개요

  • JSON(JavaScript Object Notation): 자바스크립트 객체 표기법
  • {"이름":"값","이름":"값",...}
  • JSON은 간단한 형식을 갖는 문자열로 데이터 교환에 주로 사용한다
{
	"name": "유관순",
	"birthday": "1902-12-16",
	"age": 17,
	"related": ["남동순", " 류예도"]
}

값 -> 문자열, 숫자, boolean, null, 배열, 다른 객체

2. Jackson 의존 설정

jackson-databind
jackson-datatype-jsr310
-> Date & Time API - java.time 패키지

  • Jackson은 자바 객체와 JSON 형식 문자열 간 변환을 처리하는 라이브러리이다.

ObjectMapper
자바 객체를 JSON 문자열로 바꿔줌
-> writeObjectAsString메서드 (자바 객체)
JSON 문자열 -> 자바객체
-> readValue() 메서드

  • Jackson은 자바 객체와 JSON간의 변환을 처리한다.

  • Jackson은 프로퍼티(get 메서드 또는 설정에 따라 필드)의 이름과 값을 JSON 객체의 (이름, 값) 쌍으로 사용한다.

3. @RestController로 JSON 형식 응답

@RestController -> JSON 형식으로 데이터를 응답할때

REST(RepresEntational State Transfer): 표현적 상태 전이
GET /api/member/list - 조회 상태 URL
POST /api/member

  1. 반환값이 getter가 있는 자바객체 -> JSON 문자열로 자바 객체 변환 후 출력
  • 응답 헤더: Content-Type: application/json
  1. 반환값이 없는 경우: 응답 body가 비어있음(출력x)
  • 응답헤더 X

rest컨트롤러의 경우 반환값이 void도 가능함(일반 컨트롤러는 불가능), 내부적으로(콘솔) 호출만가능

  1. 문자열을 반환하는 경우: 템플릿이 아니라 문자열 그대로 출력이 된다.
  • 응답 헤더: Content-Type: text/plain

🔼 rest형태 만들곳!

예제) 반환값이 getter가 있는 자바객체인 경우

/*json 형태로 응답하는 RestController*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/member")
public class ApiMemberController {

    private final MemberMapper mapper;

    @GetMapping("/info/{email}")
    public Member info(@PathVariable("email") String email){
        Member member = mapper.get(email); //개별조회

        return member; //반환값은 자바객체
    }//이메일로 회원조회 후 출력
}

응답을 json형태로 하는 rest컨트롤러임

예쁘게 안나오면 크롬에 추가하기

응답헤더에 content-type은 application/json


예제) 반환값이 문자열인 경우

응답헤더 따로 설정없이 돌려보니 한글이 깨진다? 확인해보니 charset이 잘못 설정되어있었음

  • @GetMapping 애노테이션의 ✨produces 속성은 컨트롤러 메서드가 생성하는 응답의 콘텐츠 타입을 지정✨하는 데 사용된다..

produces = "text/html;charset=UTF-8": 이 메서드가 반환하는 응답의 콘텐츠 타입을 text/html로 설정하고, 문자 인코딩을 UTF-8로 지정

임의로 회원 정보 넣고 리스트 형태 가져오기

문자열 데이터 json형태로 출력


4. @ResponseBody 애노테이션

  • RestController을 정의하지 않고 사용하는 방법

  • @Controller로 설정된 일반 컨트롤러 메서드를 Rest로 응답하게 만들어주는 애노테이션

    1. 자바 객체 반환, 2. 문자열 반환, 3. 반환값 없음 똑같이 동작

@ResponseBody 애노테이션

  • 메서드가 반환하는 값을 HTTP 응답 본문에 직접 작성하도록 지정한다.
  • Spring MVC는 반환된 객체를 JSON이나 XML 형식으로 변환한 후, HTTP 응답 본문에 넣음

@RestController

  • @Controller 와 @ResponseBody를 합친 역할을 한다. 해당 애노테이션이 붙은 클래스 내의 모든 메서드에 자동으로 @ResponseBody 애노테이션이 적용된다.

🔹MemberController

5. @Jsonlgnore를 이용한 제외 처리

  • JSON 변환시 제외

json 출력 결과를 보면 비밀번호 내용까지 출력된다.
보통 암호와 같이 민감한 데이터는 응답 결과에 포함시키면 안되므로 응답 결과에서 제외시켜야한다.

중요 개인정보 json 변환에서 제외 시킴

6. 날짜 형식 변환 처리: @JsonFormat 사용

  • 출력 날짜, 입력 날짜 형식을 한정

날짜 형식을 변환할 모든 대상에 이 애노테이션을 매번 붙이기도 번거롭다.

🔽 다른 방법 🔽

@JsonFormat 주석하기!!

MvcConfig

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    // 날짜와 시간을 "yyyy-MM-dd HH:mm:ss" 형식으로 포맷하는 DateTimeFormatter 생성
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    // ObjectMapper 생성 및 설정
    ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
            .json() // JSON 형식으로 ObjectMapper 빌드 시작
            .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter))
            // LocalDateTime 타입을 지정된 포맷으로 직렬화하는 설정 추가
            .build();
    
    // 커스텀 ObjectMapper를 사용하는 MappingJackson2HttpMessageConverter 생성 후 converters 리스트의 맨 앞에 추가
    converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper));
}

    }

📌직렬화: 객체를 저장하거나 전송할 수 있도록 데이터 형식으로 변환하는 과정

  1. DateTimeFormatter 설정: LocalDateTime을 yyyy-MM-dd HH:mm:ss 형식의 문자열로 변환하는 포맷터를 설정합니다.

  2. ObjectMapper 설정: Jackson의 ObjectMapper를 사용하여, LocalDateTime을 위에서 설정한 포맷터를 이용해 직렬화하도록 설정합니다.

  3. HttpMessageConverter 추가: 이 설정을 사용하는 변환기를 Spring의 HTTP 메시지 변환기 목록에 추가하여, 컨트롤러가 JSON 응답을 반환할 때 이 설정을 사용하도록 합니다.

🔽xml로 응답하는 형태🔽

implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2'

응답을 xml로 바꿔주면 됨

하지만 json을 더 많이씀!

7. @RequestBody JSON 요청 처리

  • Rest 방식에선 응답코드 정확히 알려주는게 중요하다

rest 방식에서
POST
PUT
PATCH 사용할 수 있는 테스트 툴

  • POSTMAN: REST 테스트
  • ARC(Advanced Rest Client): REST 테스트

서버를 껐다 키지 않고 rest 테스트를 하려면
-> MockMVc 사용

ApiMemberController

테스트

테스트쪽에도 환경변수 설정

  • Content-Type: application/x-www-form-urlencoded(기본 양식 형태)
    URL 인코딩된 형식의 데이터
    // 이름=값&이름=값...
  • Content-Type: application/json
    JSON 형식의 데이터 (xml 의존성 추가했던거 제거하기)

커맨드 객체 변환 기준 - Content-Type: application/x-www-form-urlencoded;

  • 🌟커맨드객체 앞에 @RequestBody를 추가하면 Content-Type:application/json으로 판단하고 데이터 변환함

회원가입 되는지 확인

8. ResponseEntity로 객체 리턴하고 응답 코드 지정하기

  • ResponseEntity -> 응답 헤더와 바디쪽을 상세하게 설정하는 경우 사용

ResponseEntity를 이용한 응답 데이터 처리

2) ResponseEntity.status(상태코드).body(객체) : 응답 상태 코드 + 출력 데이터

ApiMemberController

...
    @GetMapping("/list")
    public ResponseEntity<List<Member>> list(){
        List<Member> members = IntStream.rangeClosed(1,10)
                .mapToObj(i -> Member.builder()
                        .email("user"+i+"@test.org")
                        .password("12345678")
                        .userName("사용자"+i)
                        .regDt(LocalDateTime.now())
                        .build())
                .toList();

        return ResponseEntity.status(HttpStatus.OK).body(members);
        //출력데이터가 포함된 바디 형태
        //응답코드 200
    }
    ...

ApiMemberControllerTest

...
    @Test
    void test2() throws Exception{
        mockMvc.perform(get("/api/member/list"))
                .andDo(print());
    }

바디데이터 담겨있음

3) ResponseEntity.status(상태코드).build(); : 응답 상태 코드 / 출력 데이터는 없는 형태 (바디없음)

@Slf4j
@RequestMapping("/api/member")
@RestController
@RequiredArgsConstructor
public class ApiMemberController {
    private final MemberMapper mapper;
    private final JoinService joinService;

    @PostMapping //POST /api/member -> 회원가입으로 동작
    public ResponseEntity join(@RequestBody RequestJoin form)
    {
        joinService.process(form);
        
        // 응답코드 201, 출력 바디 없음
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
    
    ...
    ...

ApiMemberControllerTest

...
@Test
    void test1() throws Exception {
        // Content-Type: application/json

        ObjectMapper om = new ObjectMapper();
        om.registerModule(new JavaTimeModule());

        RequestJoin form = new RequestJoin();
        form.setEmail("user100@test.org");
        form.setPassword("12345678");
        form.setConfirmPassword("12345678");
        form.setUserName("사용자100");
        form.setAgree(true);

        String json = om.writeValueAsString(form);
        mockMvc.perform(
                post("/api/member")
                .contentType(MediaType.APPLICATION_JSON) //요청헤더
                .content(json) //요청바디
        ).andDo(print())
                .andExpect(status().isCreated());
                //201응답코드인지 테스트
}

응답코드 201, 출력바디 없음


🔸ResponseEntity사용해서 응답 상세하게 보기

ApiMemberController


4) ReponseEntity.ok(member)

5) noContent() : 204

6) badRequest() : 400

7) notFound() : 404

주소 오류

✍️JSON 응답은 형식을 고정해서 응답하는것이 중요하다.

🔹예를들어 개발자마다 에러코드를 message or msg or 메시지 이렇게 서로 다르게 지정하면 다 다르게 메시지가 출력된다. ->
예상 가능하게 형식을 고정시켜야함 통일성!!

🔖JsonData

success: true이면 성공 false이면 실패
message: 주로 실패시 띄울 메시지

응답 성공이 많고 응답코드 200이 많이 뜰거니까 고정값으로 true, 응답코드200d으로 설정해준것

data는 바꿀수있어야 하기때문에 NonNull로 지정

🔖ApiMemberController

컨트롤러 커맨드객체에 검증 필요함

/???

9. @ExceptionHandler 적용 메서드에서 ResponseEntity로 응답하기

임의로 예외 발생

🔖 ApiMemberController

응답 실패시 공통적으로 에러처리 할 메서드
에러도 통일성 있게 JSONData형태로! 상태코드 응답

응답 상태 출력

🔼공통적인 값 유지, 공통적인 예외처리 위해 사용된다.

...
// 모든 컨트롤러에서 발생하는 예외를 중앙에서 처리하고, 일관된 형태의 JSON 응답을 반환할 수 있다.
@RestControllerAdvice("org.choongang")
public class RestCommonControllerAdvice {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<JSONData> errorHandler(Exception e) { //모든 예외를 잡아서 처리

        Object message = e.getMessage(); //예외메시지 가져와서 저장

        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; // 기본 상태 코드 500
        if (e instanceof CommonException commonException) { //내가 정의한 예외 타입인지
            status = commonException.getStatus(); //내가 정의한 상태코드

            Map<String, List<String>> errorMessages = commonException.getErrorMessages();

            if (errorMessages != null) message = errorMessages; //커맨드 객체 검증 에러일때
        }

        JSONData data = new JSONData(); //응답 데이터 구성
        data.setSuccess(false); //요청 실패
        data.setMessage(message); //예외 메시지 또는 맵 설정
        data.setStatus(status); //필드에 상태코드 설정

        e.printStackTrace();

        return ResponseEntity.status(status).body(data); //반환된 응답은 HTTP 응답으로 변환되어 클라이언트에게 전달
    }
}

10. @Valid 에러 결과를 JSON으로 응답하기

advanced rest client 설치(테스트용)

advanced rest client

요청데이터는 json 형식으로~

ApiMemberController

이쪽으로 유입 된것!

Errors
발생한 에러 정보가 담겨있는 메서드

  • getFieldErrors(): 필드별 전체 에러 정보(rejectValue())
  • getGlobalErrors(): 커맨드 객체 자체 에러 정보(reject())
  • getAllErrors(): 전체 에러 정보

🔼 field Error

공백일수없습니다.. 말고
내가 정의한 메시지 쓰고싶을때!

프로퍼티스에 있는 메시지 쓰기위해 -> messageSource

참고) 메시지 프로퍼티 파일
코드 = 문자열 형태

👩‍💻필드오류
필드 오류는 특정 입력 필드에 대한 유효성 검사에서 발생한 오류임. 예를 들어, 회원가입 폼에서 이메일 필드가 빈 값이거나 형식이 잘못된 경우, 해당 필드에 대한 오류가 발생한다. 필드 오류는 특정 입력 필드에 직접 연관되어 있다.

👩‍ 글로벌 오류
글로벌 오류는 특정 필드에 직접 연관되지 않은 폼 전체에 대한 유효성 검사 오류이다. 예를 들어, 사용자가 입력한 두 개의 비밀번호가 일치하지 않는 경우, 이 오류는 특정 필드보다는 폼 전체에 대한 오류로 간주된다. 글로벌 오류는 폼의 일반적인 규칙에 위배될 때 발생

  • 편의 기능 클래스 추가
    global - Utils클래스
    -> 유효성 검사에서 발생한 오류 메시지를 가공하는 역할
    -> 주로 폼 유효성 검사에서 발생한 오류 메시지를 사용자에게 보여주기 쉽게 가공하는 데 사용

가공 하는 이유 -> 에러 코드를 그대로 보여주면 사용자가 인식하지 못할수도있다
ex) NotBlank.password

getErrorMessages 메서드는 getCodeMessages 메서드를 사용하여 오류 코드를 메시지로 변환한다.

commonException, BadRequestException 수정

RestControllerAdvice

ApiMemberController

및 코드 수정..

Utils 추가

💡Json응답요청 관련 코드 분석 해보기!!!


UriComponentsBuilder

package org.tests;

import org.junit.jupiter.api.Test;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

public class Ex02 {

    @Test
    void test2(){
        URI url = UriComponentsBuilder.fromUriString("http://www.naver.com")
                .path("/news/{0}")
                .queryParam("t1","v1")
                .queryParam("t2","v2")
                .queryParam("t3","한글")
                .queryParam("t4","aa{1}")
                .fragment("hash")
                .encode()
                .build("AAAAA","BBBBB");
        System.out.println(url);

    }
}


주석 처리 안하면 계속 에러 던져질것이다 무시무시

RestTemplate 클래스

  • ResponseEntity는 Spring Framework에서 HTTP 응답을 나타내는 클래스

  • HTTP 응답의 상태 코드(status), 헤더(바디 정보 담겨있음), 본문(body) 등을 포함하고 있어서, 클라이언트가 HTTP 요청을 보내고 그 결과를 상세히 처리할 수 있도록 돕는다.

🔽 메서드 🔽

1. <T> ResponseEntity<T> getForEntity(...)

  • 상세한 응답 정보가 담겨있다. 상세한 정보 확인하기 위해 사용
@Test
    @DisplayName("일반 양식 형식으로 전송 - Content-Type: application/x-www-form-url-urlencoded")
    void test5() {
        RestTemplate restTemplate =  new RestTemplate();

        // 요청 데이터를 키:값 형태로 담음
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("email","user998@test.org"); //put은 하나의 이름에 여러개 담을때, 단일 데이터일경우 add
        params.add("password","12341234");
        params.add("confirmPassword","12341234");
        params.add("userName","사용자998");
        params.add("agree","true"); //약관동의
        //post형태로 바디 데이터 만듦

        //헤더
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //전송하는 데이터 형식 알려줌

        //바디데이터의 종류를 알려주기
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

        String url = "http://localhost:5000/day05/member/join";

        ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

        System.out.println(response);

    }

<302 FOUND Found,[Location:"/day05/member/login", Content-Language:"ko-KR", Content-Length:"0", Date:"Thu, 18 Jul 2024 06:29:25 GMT", Keep-Alive:"timeout=20", Connection:"keep-alive"]>

가입된 회원일경우 자세한 정보로 에러 메시지 나온다.

2. <T> T getForObject

  • GET메서드를 사용하여 서버로부터 리소스 가져옴
    요청 URL을 통해 리소스를 직접 객체로 매핑하여 반환함
@SpringJUnitWebConfig
@ContextConfiguration(classes = MvcConfig.class)
public class Ex02 {

    @Autowired
    private ObjectMapper om;

    @Test
    void test1() {
...
    }

    @Test
    void test2() {
        RestTemplate restTemplate = new RestTemplate();
        PostData data = restTemplate.getForObject("https://jsonplaceholder.typicode.com/posts/1", PostData.class);
        System.out.println(data);
    }
    
    @Test
    void test3() {
    ...
    }
 }   

TypeReference

3. <T> ResponseEntity<T> postForEntity

  • POST메서드를 사용하여 리소스를 생성하고 결과를 ResoponseEntity형태로 받음

HttpEntity -> 헤더 바디 등 함께 전송시 필요

4. <T> T postForObject

  • POST 메서드를 사용하여 리소스를 생성하고, 그 결과를 지정한 타입의 객체로 반환

5. <T> ResponseEntity<T> exchange(...)

  • 가장 유연한 형태의 HTTP 요청을 보내고, 그 결과를 ResponseEntity 형태로 반환받음

  • HTTP 메서드(GET, POST, PUT, DELETE 등)와 함께 요청을 보낼 수 있다.

  • 요청과 응답의 상세 정보(헤더, 본문 등)를 제어할 수 있음

  • 반환값 ResponseEntity 객체

profile
꽁꽁 얼어붙은 한강 위로 😺

0개의 댓글