개발/[Spring] 블로그 만들기

[코드로 배우는 스프링 웹 프로젝트] 16강. REST 방식으로 전환 (REST/ @RestController/Restlet Client 프로그램)

ee2ee2 2022. 3. 6. 23:09
728x90
반응형

해당 프로젝트는 코드로 배우는 스프링 웹 프로젝트(개정판)을 기반으로 진행됩니다.


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

Ex) '/boards/123'은 게시물 중에서 123번이라는 고유한 의미를 가지고, 이에 대한 처리는 GET/POST 방식 등 추가적인 정보를 통해서 결정

 

REST와 관련하여 알아두어야 할 어노테이션


16.1 @RestController

REST 방식에서 기억해야하는 점은 서버에서 전송하는 것이 순수한 데이터 라는 것이다.

기존의 Controller에서 Model에 데이터를 담아 JSP 등과 같은 뷰로 전달하는 방식이 아니다.


16.1.1 예제 프로젝트 준비

'Spring Legact Project'를 이용해서 'ex03' 프로젝트를 생성한다. 프로젝트의 기본 패키지는 'org.zerock.controller'로 지정한다. (기존 'ex02' 프로젝트에서 진행해도 상관 없다.)

pom.xml 일부 수정

: java 버전과 스프링 버전 수정

	<properties>
		<java-version>1.8</java-version>
		<org.springframework-version>5.0.7.RELEASE</org.springframework-version>
		<org.aspectj-version>1.6.10</org.aspectj-version>
		<org.slf4j-version>1.6.6</org.slf4j-version>
	</properties>

....생략......

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>

 

pom.xml의 Dependency 추가

: 작성 순서는 상관없으며, 본인의 편한 순서로 작성하면 된다. 버전 정보가 중요함.

<!-- Test  JUnit 버전 변경 -->
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
	<scope>test</scope>
</dependency>  

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

<!-- Servlet 버전 수정-->
<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.1.0</version>
	<scope>provided</scope>
</dependency>

..생략..

<!-- 롬복 관련 설정 -->
<dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
     <scope>provided</scope>
</dependency>

..생략..

<!-- jackson-databind : 브라우저에 객체를 JSON/XML 포맷의 문자열로 변환시켜 전송할 때 필요 -->    
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.9.6</version>
</dependency>

<dependency>
	<groupId>com.fasterxml.jackson.dataformat</groupId>
	<artifactId>jackson-dataformat-xml</artifactId>
	<version>2.9.6</version>
</dependency>

<!-- 테스트할 떼는 직접 Java 인스턴스를 JSON 타입의 문자열로 변환해야할 때 사용 -->
<dependency>
	<groupId>com.google.code.gson</groupId>
	<artifactId>gson</artifactId>
	<version>2.8.2</version>
</dependency>

 


16.2 @RESTController의 반환 타입

SampleControllrt.java 생성

16.2.1 문자열의 반환

SampleControllrt.java 

: 단순 문자열의 반환

package org.zerock.controller;

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 lombok.extern.log4j.Log4j;

@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 "안녕하세요";
	}
}

/*
	1) GetMapping 내 produces
	 : produces 를 추가하고 data Type을 지정하면 해당 dataType으로만 사용자에게 응답하겠다는 의미
	   추가로, consumes도 추가할 수 있는데 수신 받고자하는 데이터 포맷을 정의(Request Body에 담는 타입을 제한)
	  
	2) MIME(Multipurpose Internet Mail Extensions) 이란?
	: 파일 변환을 위한 포맷. 웹을 통해 전달되는 다양한 형태의 파일을 텍스트 문자 형태로 변환해서 이메일과 함께 전송하기 위해 개발된 포맷
*/

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

 

실행 결과

produces의 속성값으로 지정된 'text/plain' 결과과 나오는 것을 확인

 


16.2.2 객체의 반환

객체를 반환하는 작업은 JSON 이나 XML을 이용한다. 전달된 객체를 생산하기 위해서 'org.zerock.domain' 패키지를 생성하고, SampleVO 클래스를 작성한다.

SampleVO.java

package org.zerock.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SampleVO {
	
	private Integer mno;
	private String firstName;
	private String lastName;
}

/*
 * @NoArgsConstructor : 비어 있는 생성자를 만들기 위함.
 * @AllArgsConstructor : 모든 속성을 사용하는 생성자를 위함.
*/

여러 생성자가 생겼음을 확인 가능함.

 

SampleController.java

객체를 반환하는 메소드 추가

	//http://localhost:8080/sample/getSample.json
	//XML과 JSON 방식의 데이터를 생성할 수 있는 getSample()
	@GetMapping(value="/getSample", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE,
												MediaType.APPLICATION_XML_VALUE})
	public SampleVO getSample() {
		return new SampleVO(112,"스타","로드");	
	}

 

실행 결과

1) http://localhost:8080/sample/getSample

브라우저가 받은 데이터는 XML 형태

2) http://localhost:8080/sample/getSample.json

브라우저가 받은 데이터는 JSON 형태


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

	//http://localhost:8080/sample/getSample2.json
	@GetMapping(value="/getSample2")
	public SampleVO getSample2() {
		return new SampleVO(113,"로켓","라쿤");	
	}

 

실행결과

1) http://localhost:8080/sample/getSample2

브라우저가 받은 데이터는 XML 형태

2) http://localhost:8080/sample/getSample2.json

브라우저가 받은 데이터는 JSON 형태


16.2.3 컬렉션 타입의 객체 반환

: 경우에 따라 여러 데이터를 한 번에 전송하기 위해서 배열이나 리스트, 맵 타입의 객체들을 전송하는 경우도 있다.

SampleController.java  - List 타입

	//http://localhost:8080/sample/getList.json
	@GetMapping("/getList")
	public List<SampleVO> getList(){
		//Stream은 컬렉션, 배열등에 대해 저장되어있는 요소들을 하나씩 참조하며 반복적인 처리를 가능케하는 기능
		return IntStream.range(1, 10).mapToObj(i -> new SampleVO(i, i+"First", i+"Last")).collect(Collectors.toList());
	}

 

실행 결과

1) http://localhost:8080/sample/getList (기본적으로 XML 데이터를 전송함)

2) http://localhost:8080/sample/getList.json

[]로 둘러 싸여진 JSON 형태의 배열 데이터

 

 

SampleController.java - Map 타입

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

	//http://localhost:8080/sample/getMap.json
	@GetMapping("getMap")
	public Map<String, SampleVO> getMap(){
		Map<String, SampleVO> map = new HashMap<>();
		map.put("First", new SampleVO(111,"그루트","주니어"));
		
		return map;
	}

실행 결과

1) http://localhost:8080/sample/getMap (기본적으로 XML 데이터를 전송함)

 : Map을 이용하는 경우에는 키(Key)에 속하는 데이터는 XML로 변환되는 경우에 태그의 이름이 되기 때문에 문자열을 지정한다.

2) http://localhost:8080/sample/getList.json

 


16.2.4 ResponseEntity 타입

: REST 방식으로 호출하는 경우는 화면 자체가 아니라 데이터 자체를 전송하는 방식으로 처리된다. 따라서, 데이터를 요청한 쪽에서는 받은 데이터가 정상인지 비정상인지 구분할 수 있는 확실한 방법을 제공해야한 한다.

ResponseEntity는 데이터와 함께 HTTP 헤더의 상태 메시지 등을 같이 전달하는 용도로 사용한다.

 

SampleController.java 

	//http://localhost:8080/sample/check?height=170&weight=60
	@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.0) {
			result = ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(vo);
		} else {
			result = ResponseEntity.status(HttpStatus.OK).body(vo);
		}
		
		return result;
	}

실행 결과

1) http://localhost:8080/sample/check?height=170&weight=60

조건에 성립하여 200코드 반환

 

2) http://localhost:8080/sample/check?height=130&weight=60

조건에 맞지않아 502 코드 반환


16.3 @RestController에서 파라미터

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

  • @PathVariable : 일반 컨트롤러에서도 사용 가능하지만 REST 방식에서 자주 사용된다. URL 경로의 일부를 파라미터로 사용할 때 사용한다.
  • @RequestBody : JSON 데이터를 원하는 타입의 객체로 변환해야 하는 경우에 주로 사용한다.

16.3.1 @PathVariable

스프링 MVC에는 @PathVariable 어노테이션을 이영해서 URL 상에 경로의 일부를 파라미터로 사용할 수 있다.

예시1) http://localhost:8080/sample/{sno}

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

 

SampleController.java 메소드 추가

//http://localhost:8080/sample/product/bags/123
@GetMapping("/product/{cat}/{pid}")
public String[] getPath(@PathVariable("cat") String cat, @PathVariable("pid") Integer pid) {
	//값을 얻을 때에는 int, double 같은 기본 자료형은 사용할 수 업음.
	return new String[] {"category : " + cat , "productId : " + pid};	
}

실행 결과


16.3.2 @RequestBody

@RequestBody는 전달된 요청(request)의 내용(Body)을 이용해서 해당 파라미터의 타입으로 변환을 요구한다. 대부분의 경우에는 JSON 데이터를 서버에 보내서 원하는 타입의 객체로 변환하는 용도로 사용되지만, 경우에 따라 원하는 포맷의 데이터를 보내고, 이를 해석해서 원하는 타입으로 사용하기도 한다.

 

예제 생성하기

'org.zerock.domain' 패키지에 Ticket 클래스를 생성한다.

​Ticket.java

package org.zerock.domain;

import lombok.Data;

@Data
public class Ticket {

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

 

SampleController.java 에 Ticket을 사용하는 메소드 추가

: convert()메소드를 JSON으로 전달되는 데이터를 받아서 Ticket 타입으로 반환하는 메소드이다.

//JSON 형태로 전달되는 데이터를 받아 Ticket 형태로 변환 후 Return하는 함수
@PostMapping("/ticket")
public Ticket convert(@RequestBody Ticket ticket) {
	log.info("convert.......ticket" + ticket);
	
		return ticket;
}

다른 메소드와 달리 @PostMapping이 적용되었다. 이것은 @RequestBody가 말 그대로 요청한 내용을 처리하기 때문에 일반적인 파라미터 전달방식이 사용할 수 없다. 이에 대한 테스트는 아래와 같이 진행한다.


16.4 REST 방식의 테스트

16.4.1 JUnit 기반의 테스트

'src/test/java' 폴더 아래 SampleControllerTests 클래스를 생성한다.

 

SampleControllerTests.java

package org.zerock.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.zerock.domain.Ticket;

import com.google.gson.Gson;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@WebAppConfiguration

@ContextConfiguration({"file:src\\main\\webapp\\WEB-INF\\spring\\root-context.xml", "file:src\\main\\webapp\\WEB-INF\\spring\\appServlet\\servlet-context.xml"})

//Java Config
//@ContextConfiguration(classes = {org.zerock.config.RootConfig.class, org.zerock.config.ServletConfig.class})
@Log4j
public class SampleControllerTests {

	@Setter(onMethod_ = {@Autowired})
	private WebApplicationContext ctx;
	
	private MockMvc mockMvc;
	
	
	@Before
	public void setup() {
		this.mockMvc =  MockMvcBuilders.webAppContextSetup(ctx).build();
	}
	
	@Test
	public void textConvert() throws Exception {
		Ticket ticket = new Ticket();
		ticket.setTno(123);
		ticket.setOwner("Admin");
		ticket.setGrade("AAA");
		
		String jsonStr = new Gson().toJson(ticket);
		
		log.info(jsonStr);
		
		//JSON 형태로 데이터를 던지는 부분
		mockMvc.perform(post("/sample/ticket")
				.contentType(MediaType.APPLICATION_JSON)
				.content(jsonStr))
				.andExpect(status().is(200));
	}
}

 

실행 결과

JSON으로 전달되는 데이터를 받아서 Ticket 타입으로 반환

 


16.4.2 기타 도구

테스트 코드를 작성하지 않고, 크롬 확장 프로그램을 사용하는 방법도 있다. 브라우저에서 테스트 하기 어려운 POST방식을 포함한 PUT, DELETE와 같은 전송 방식도 손쉽게 테스트 가능하다.

 

REST 전송 방식 테스트를 위한 크롬 확장 프로그램 - Restlet Client

https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=ko 

 

Talend API Tester - Free Edition

Visually interact with REST, SOAP and HTTP APIs.

chrome.google.com

 

실행 결과