Chapter 16 REST 방식으로 전환 - oneso123456789/2022 GitHub Wiki

feedback

Chapter 16 REST 방식으로 전환

지난 10년간 IT 분야의 가장 큰 변화는 역시 모바일
모바일은 현재 모든 사람들의 생활에 깊숙이 침투해서 생활 방식에 많은 영향을 주고 있음
모든 업무나 일상 생활에 필요한 작업들을 대부분 모바일을 통해서 할 수 있는 지금의
생활에서 WEB이라는 분야 역시 변화를 겪게 됨

모바일 시대가 되면서 WEB 분야의 가장 큰 변화는 서버 역할의 변화라고 할 수 있음
과거에는 서버의 데이터를 소비하는 브라우저라는 특정한 애플리케이션으로 제한적이었다면,
모바일의 시대가 되면서 앱이나 웹은 서버에서 제공하는 데이터를 소비하게 됨

즉 과거의 서버는 브라우저라는 하나의 대상만을 상대로 데이터를 제공했기 때문에
아예 브라우저가 소화 가능한 모든 데이터를 HTML이라는 형태로 전달하고,
브라우저는 이를 화면에 보여주는역할을 해 왔음

하지만 스마트폰에서는 앱(App)이라 불리는 고유한 애플리케이션을 이용해서 데이터를 소비하게 되고,
보이는 화면 역시 자신만의 방식으로 서비스하게 됨

앱에서 서버에 기대하는 것은 완성된 HTML이 아니라 그저 자신에게 필요한 순수한 데이터만을 요구하게 되어 있음
이처럼 서버의 역활은 점점 더 순수하게 데이터에 대한 처리를 목적으로 하는 형태로 진화하고 있음

또한, 브라우저와 앱은 서버에서 전달하는 데이터를 이용해서 앱 혹은 브라우저 내부에서
별도의 방식을 통해서 이를 소비하는 형태로 전환하고 있음

이러한 변화속에서 웹은 URI(Uniform Resource Identifier) 의미도 조금 다르게 변화하기 시작 했음 '

URL과 URI

URL(Uniform Resource Locator)과 URI를 같은 의미로 사용하는 경우가 많음
엄밀하게는 URL은 URI의 하위 개념이기 때문에 혼용해도 무방함 URI는 자원의 식별자라는 의미로 사용됨

  • URL: 이 곳에 가면 당신이 원하는 것을 찾을 수 있습니다.와 같은 상징적인 의미가 강하다면
  • URI: 당신이 원하는 곳의 주소는 여기 입니다.와 같이 좀더 현실적이고 구체적인 의미가 있음 또한 URI의 I는 DB의 PK와 같은 의미로 사용됨

예를 들어 과거에 제작된 웹페이지들의 경우 페이지를 이동하더라도 브라우저의 주소는 변화하지 않는 방식을 선호했음
(가장 대표적인 사이트가 네이버의 카페와 같은 경우)

반면에 최근의 웹 페이지들은 대부분 페이지를 이동하면 브라우저 내의 주소 역시 같이 이동하는 방식을 사용

REST 방식

REST : Representational State Transfer의 약어로 하나의 URI는 하나의 고유한
리소스(Resource)를 대표하도록 설계된다는 개념에 전송방식을 결합해서 원하는 작업을 지정함

예를 들어 /board/123은 게시물 중에서 123번이라는 고유한 의미를 가지도록 설계하고,
이에 대한 처리는 GET, POST 방식과 같이 추가적인 정보를 통해서 결정함

따라서 REST 방식은 URI + GET/POST/PUT/DELETE/...과 같이 구성된다고 생각 할 수 있음

Spring은 @RequestMapping이나 @ResponseBody와 같이 REST 방식의 데이터 처리를 위한
여러 종류의 어노테이션과 기능이 존재함

REST와 관련해서 알아두어야 하는 어노테이션

  • @RestController : Controller 가 REST 방식을 처리하기 위한 것임을 명시함
  • @ResponseBody : 일반적인 JSP와 같은 뷰로 전달되는 게 아니라 데이터 자체를 전달하기 위한 용도
  • @PathVariable : URL 경로의 있는 값을 파라미터로 추출하려고 할 때 사용
  • @CrossOrigin : Ajax의 크로스 도메인 문제를 해결해주는 어노테이션
  • @RequestBody : JSON 데이터를 원하는 타입으로 바인딩 처리

2022-05-26 오후 8시 53분 16.1 @RestController 전까지함

16.1 @RestController

REST 방식에서 가장 먼저 기억해야 하는 점은 서버에서 전송하는 것이 순수한 데이터라는 점임
기존의 Controller에서 Model에 데이터를 담아서 JSP 등과 같은 뷰(View)로 전달하는 방식이 아니라
기존의 Controller와는 조금 다르게 동작함

@RestController라는 어노테이션은 스프링4에서부터 추가됨
해당 Controller의 모든 메서드의 리턴 타입을 기존과 다르게 처리한다는것을 명시해줌

@RestController이전에는 @Controller와 메서드 선언부에 @ResponseBody를 이용해서
동일한 결과를 만들 수 있었음

@RestController는 메서드의 리턴 타입으로 사용자가 정의한 클래스 타입을 사용할 수 있고,
이를 JSON이나 XML로 자동으로 처리 할 수 있음

16.1.1 예제 프로젝트 준비

예제를 위해 Spring Legacy Project를 이용해서 ex03 프로젝트 생성
기본패키지는 com.crow.controller로 지정함

pom.xml의 스프링 버전은 5.0.7 Java버전이나 Maven Complile 버전은 1.8버전으로 수정하고
프로젝트를 업데이트해줌

작성된 프로젝트에는 우선적으로 JSON 데이터를 처리하기 위한 jackson-databind라는
라이브러리를 추가해줌
(버전은 책은 2.9.6이지만 나는 2.13.2.2사용함 제일 최근버전에서 하나 전 버전임)

jackson-databind 라이브러리는 나중에 브라우저에 객체를 JSON이라는 포맷의 문자열로
변환시켜 전송할때 필요함

JSON이란

JavaScript Object Notation의 약어로 구조가 있는 데이터를 {}로 묶고
키와 값을 구성하는 경량의 데이터 포맷임
프로그래밍 언어에서 말하는 객체(Object)들의 구조는 {}를 이용해서 표현할 수 있음

 {
    "이름": "홍길동",
    "나이": 25,
    "성별": "",
    "주소": "서울특별시 양천구 목동",
    "특기": ["농구", "도술"],
    "가족관계": {"#": 2, "아버지": "홍판서", "어머니": "춘섬"},
    "회사": "경기 수원시 팔달구 우만동"
 }

구조를 표현한 문자열은 프로그래밍 언어에 관계 없이 사용할 수 있기 때문에
XML과 더불어 가장 많이 사용되는 데이터 표현 방식임


또한 XML의 처리는 jackson-dataformat-xml 라이브러리를 이용함 (이 라이브러리 역시 책의 버전은 2.9.6이지만 나는 2.13.2버전을 이용)

테스트 할 때는 직접 Java 인스턴스를 JSON 타입의 문자열로 변환해야 하는 일들도 있으므로
gson 라이브러리도 추가함
(책의 버전은 2.8.2이지만 나는 2.8.9 사용)

작성된 프로젝트의 서블릿 버전을 수정하고,Lombok 관련 설정을 추가해줌
서블릿 버전은 3.1.0 Lombok은 1.18.0

jackson 라이브러리들과 다르게 Lombok과 servlet버전을 예전 버전을 사용하는 이유는
보안이슈가 발견된 부분이 없거나 적어서 사용했음

마지막으로 테스트를 위해서 JUnit 버전을 4.13.1로 변경(책은 4.12),
Spring test 관련 모듈을 추가해줌

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>



16.2 @RestController의 반환 타입

스프링의 @RestController는 특별히 기존의 @Controller와 다른 점은 없음
com.crow.controller 패키지에 SampleController 생성

SampleController 클래스

package com.crow.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.apache.commons.logging.Log;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.crow.domain.SampleVO;

import lombok.extern.log4j.Log4j;
import lombok.extern.log4j.Log4j2;

@RestController
@RequestMapping("/sample")
@Log4j
public class SampleController {

}

작성중 @Log4j(log처리) 오류 발생 디버깅일지 2022-05-28 참고

16.2.1 단순 문자열 반환

@RestController는 JSP와 달리 순수한 데이터를 반환하는 형태이므로 다양한 포맷의
데이터를 전송 할 수 있음
주로 많이 사용하는 형태는 일반 문자열, JSON, XML 등임

SampleController에 문자열을 반환하는 getText()메서드 구현

SampleController 클래스

package com.crow.controller;

import org.apache.commons.logging.Log;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.crow.domain.SampleVO;

import lombok.extern.log4j.Log4j;
import lombok.extern.log4j.Log4j2;

@RestController
@RequestMapping("/sample")
@Log4j
public class SampleController {

    @GetMapping(value = "/getText", produces = "text/plain; charset=UTF-8")
    public String getText() {

        log.info("MIME TYPE: " + MediaType.TEXT_PLAIN_VALUE);

        return "안녕하세요";
    }

기존의 @Controller는 문자열을 반환하는 경우에는 JSP 파일의 이름으로 처리하지만,
@RestController의 경우에는 순수한 데이터가 됨

@GetMapping에 사용된 produces 속성은 해당 메서드가 생산하는 MIME 타입을 의미함
예제와 같이 문자열로 직접 지정할 수도 있고, 메서드 내의 MediaType이라는
클래스를 이용도 가능함

실행된 결과를 보면 produces의 속성값으로 지정된 text/plain 결과가 나오는것을 확인가능

16.2.2 객체의 반환

객체를 반환하는 작업은 JSON이나 XML을 이용함
전달된 객체를 생산하기 위해서 com.crow.domain패키지를 생성하고,
SampleVO.Class를 작성함

SampleVO 클래스

package com.crow.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SampleVO {

    private Integer mno;
    private String firstName;
    private String lastName;
}

SampleVO클래스는 비어있는 생성자를 만들기 위한 @NoArgsConstructor와
모든 속성을 사용하는 생성자를 위한 @AllArgsConstructor 어노테이션을 이용했음

SampleController에서는 SampleVO를 리턴하는 메서드를 설계함

SampleController 클래스

    @GetMapping(value = "/getSample", produces = { MediaType.APPLICATION_JSON_UTF8_VALUE,
            MediaType.APPLICATION_XML_VALUE })
    public SampleVO getSample() {

        return new SampleVO(32, "스타", "로드");
    }

getSample()은 XML과 JSON 방식의 데이터를 생성할 수 있도록 작성되었음
/getSample를 매핑시 XML형태로 데이터 전달됨
/getSample.json로 매핑시 기존과 달리 JSON형태로 데이터 전달

@GetMapping이나 @RequestMapping의 produces 속성은 반드시 지정해야 하는것은 아니므로
생략하는 것도 가능함

16.2.3 컬렉션 타입의 객체 반환

경우에 따라서는 여러 데이터를 한 번에 전송하기 위해서 배열, 리스트.맵 타입의
객체를 전송하는 경우도 발생함

SampleController의 일부

    @GetMapping(value = "/getList")
    public List<SampleVO> getList() {

        return IntStream.range(1, 10).mapToObj(i -> new SampleVO(i, i + "First", i + " Last"))
                .collect(Collectors.toList());
    }

getList()는 내부적으로 1부터 10미만까지의 루프를 처리하면서 SampleVO객체를
만들어서 List로 만들어냄

이 메서드 역시 /sample/getList를 호출하면 기본적으로는 XML 데이터를 전송
뒤에 확장자를 .json으로 처리하면[]로 싸여진 JSON형태의 배열 데이터 전송

맵의 경우에는 키와값을가지는 하나의 객체로 간주됨

SampleController의 일부

    @GetMapping(value = "/getMap")
    public Map<String, SampleVO> getMap() {

        Map<String, SampleVO> map = new HashMap<>();
        map.put("First", new SampleVO(111, "그루트", "주니어"));

        return map;
    }

Map을 이용하는 경우에는 키에 속하는 데이터는 XML로 변환되는 경우에
태그에 이름이 되기 때문에 문자열로 지정함

16.2.4 ResponseEntity 타입

REST 방식으로 호출하는 경우는 화면 자체가 아니라 데이터 자체를 전송하는 방식으로
처리되기 때문에 데이터를 요청한 쪽에서는 정상적인 데이터인지 비정상적인 데이터
인지를 구분할 수 있는 확실한 방법을 제공해야함

ResponseEntity는 데이터와 함께 HTTP 헤더의 상태 메시지 등을 같이 전달하는
용도로 사용함
HTTP의 상태 코드와 에러 메시지 등을 함께 전다랄 수 있기 때문에
받는 입장에서는 확실한 결과를 알 수 있음

SampleController의 일부

    @GetMapping(value = "/check", params = { "height", "weight" })
    public ResponseEntity<SampleVO> check(Double height, Double weight) {

        SampleVO vo = new SampleVO(0, "" + height, "" + weight);

        ResponseEntity<SampleVO> result = null;

        if (height < 150) {
            result = ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(vo);
        } else {
            result = ResponseEntity.status(HttpStatus.OK).body(vo);
        }

        return result;
    }

check()는 반드시 height 와 weight를 파라미터로 전달 받음
이때 만일 height값이 150보다 작다면 502(bad gateway) 상태코드와 데이터를 전송하고
그렇지 않다면 200(ok) 코드와 데이터를 전송함

/sample/check.json?height=140&weight=65와 같이 JSON 타입의 데이터를 요구하고,
height 값을 150보다 작게 하는경우에는 502와 데이터가 전송됨


16.3 @RestController에서 파라미터

@RestController는 기존의 @Controller에서 사용하던 일반적인 타입이나 사용자가
정의한 타입(클래스)을 사용함
여기에 추가로 몇 가지 어노테이션을 이용하는 경우가 있음

  • @PathVariable : URL 경로의 일부를 파라미터로 사용할 때 이용
    (일반 컨트롤러에서도 사용이 가능하지만 REST 방식에서 자주 사용됨)

  • @RequestBody : JSON 데이터를 원하는 타입의 객체로 변환해야 하는 경우 주로 사용

16.3.1 @PathVariable

REST 방식에서는 URL내에 최대한 많은 정보를 담으려고 노력해야함
예전에는 ?뒤에 추가되는 쿼리 스트링(query string)이라는 형태로 파라미터를 이용해서
전달되던 데이터들이 REST 방식에서는 경로의 일부로 차용되는 경우가 많음

스프링 MVC에서는 @PathVariable 어노테이션을 이용해서 URL 상에 경로의 일부를
파라미터로 사용가능 http://localhost:****/sample/{sno} or
http://localhost:****/sample/{sno}/page/{pno}

위의 URL에서 {}로 처리된 부분은 컨트롤러의 메서드에서 변수로 처리가 가능함
@PathVariable은 {}의 이름을 처리할 때 사용함

REST 방식에서는 URL 자체에 데이터를 식별할 수 있는 정보들을 표현하는 경우가
많으므로 다양한 방식으로 @PathVariable이 사용됨

SampleController의 일부

    @GetMapping("/product/{cat}/{pid}")
    public String[] getPath(@PathVariable("cat") String cat, @PathVariable("pid") Integer pid) {

        return new String[] { "category: " + cat, "productid: " + pid };
    }

@PathVariable을 적용하고 싶은 경우에는 { }를 이용해서 변수명을 지정하고,
@PathVariable을 이용해서 지정된 이름의 변숫값을 얻을 수 있음
단 값을 얻을 때에는 int, double과 같은 기본 자료형은 사용할 수 없음

브라우저에서 http://localhost:****/sample/product/gom/0830로 호출하면
cat과 pid 변수의 값으로 처리됨

16.3.2 @RequestBody

@RequestBody는 전달된 요청(request)의 내용(body)을 이용해서 해당 파라미터의
타입으로 변환을 요구함

내부적으로는 HttpMessageConverter 타입의 객체들을 이용해서 다양한 포맷의 입력 데이터를
변환할 수 있음

대부분의 경우에는 JSON 데이터를 서버에 보내서 원하는 타입의 객체로 변환하는 용도로
사용되지만, 경우에 따라서는 원하는 포맷의 데이터를 보내고,
이를 해석해 원하는 타입으로 사용하기도 함

변환 예제를 위해서 com.crow.domain 패키지에 Ticket 클래스를 정의함

Ticket 클래스

package com.crow.domain;

import lombok.Data;

@Data
public class Ticket {

    private int tno;
    private String owner;
    private String grade;
}

Ticket 클래스의 번호(bno), 소유주(owner), 등급(grade)를 지정

Ticket을 사용하는 메서드 convert()를 SampleController에 추가함

SampleController의 일부

    @PostMapping("/ticket")
    public Ticket convert(@RequestBody Ticket ticket) {
        
        log.info("convert......ticket" + ticket);
        
        return ticket;
    }

SampleController의 다른 메서드와 달리 @PostMapping이 적용된 것을 볼 수 있음
이것은 @RequestBody가 말 그대로 요청(request)한 내용(body)을 처리하기 때문에
일반적인 파라미터 전달방식을 사용할 수 없기 때문임

작성한 convert()에 대한 테스트는 REST 방식의 테스트를 학습한 후 진행


2022-05-28 오후 8시 48분 16.4 REST 방식의 테스트 전까지함

16.4 REST 방식의 테스트

⚠️ **GitHub.com Fallback** ⚠️