Spring MVC(3) - API 계층 -Controller (ResponseEntity 적용)
■ 이번 페이지는 https://coding-mid-life.tistory.com/60?category=1287134 에서 작성된 Controller 예제의 개선을 진행한다
Spring MVC - Controller (MVC 개요/핸들러 메서드)
1. Controller 클래스 설계 및 구조 생성 API 계층을 Spring MVC 기반의 코드로 구현해 본다 1) 애플리케이션 경계 설정 커피 주문 애플리케이션으로 설정 2) 애플리케이션 기능 요구사항 수집 기능적
coding-mid-life.tistory.com
1. MemberComtroller 개선
- ResponseEntity를 사용한다
- ResponseEntity는 HttpEntity의 확장 클래스이다
- HttpStatus 상태 코드를 추가한 전체 HTTP응답을 표현한다(상태코드, 헤더, 본문)
- @Controller @RestController 애너테이션이 붙은 Controller 클래스의 핸들러 메서의 요청에 대한 응답을 표현한다
- RestTemplate에서 외부 API통신에 대한 응답을 전달받아서 표현할 경우에 사용한다 - Map 과 HashMap을 사용한다
1) 기존 작성 코드
package com.dreamfactory.exam_controller.member;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLOutput;
@RestController
@RequestMapping(value ="v1/members", produces = MediaType.APPLICATION_JSON_VALUE)
public class MemberController {
@PostMapping
public String postMember(@RequestParam("email")String email,
@RequestParam("name")String name,
@RequestParam("phone")String phone) {
System.out.println("# email: " + email);
System.out.println("# name: " + name);
System.out.println("# phone: " + phone);
//---> code change <---
String reponse =
"{\"" +
"email\":\""+email+"\"," +
"\"name\":\""+name+"\",\"" +
"phone\":\"" + phone+
"\"}";
return reponse;
}
@GetMapping("/{member-id}")
public String getMember(@PathVariable("member-id")long memberId) {
System.out.println("# memberId: " + memberId);
//not implementation
return null;
}
@GetMapping
public String getMembers() {
System.out.println("# get Members");
//not implementation
return null;
}
}
- 위의 코드에서 다음 부분이 개선이 우선된다
- JSON 형식으로 응답을 받기 위해 작성된 코드이다
String reponse =
"{\"" +
"email\":\""+email+"\"," +
"\"name\":\""+name+"\",\"" +
"phone\":\"" + phone+
"\"}";
return reponse;
- 위의 수동 코드를 Map 메서드를 사용하여 개선하였다
//JSON문자열 응답 타입을 수작업 코드에서 Map객체로 변경->produce애트리뷰트를 삭제할 수 있다
//MAp 객체를 리턴하면 내부적으로 응답 데이터를 JSON데이터로 자동 변환해야 한다고 인식한다
Map<String, String> map = new HashMap<>();
map.put("email", email);
map.put("name", name);
map.put("phone", phone);
//리턴 값을 변경된 ResponseEntity로 대체
//ResponseEntity 객체를 생성하고 생성자 파라미터로 map과 HttpStatus.CREATED를 반환한다
//HttpStatus.CREATED 는 201, created를 의미한다
return new ResponseEntity<>(map, HttpStatus.CREATED)
- 전체적으로 개선된 코드는 아래와 같다
- getMember()와 getMembers() 핸들러 메서드도 ResponseEntity 객체를 리턴하고 HttpStatus.OK의 응답을 전송하는 것으로 수정되었다
package com.dreamfactory.exam_controller.member;
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLOutput;
//@애터테이션에 의해 자동으로 import 된다
import java.util.HashMap;
import java.util.Map;
@RestController
//Spring MVC에서는 특정 클래스에 @RestController 를 추가하면 해당 클래스가 REST API의 리소스(자원, Resource)를
처리하기 위한 API 엔드포인트로 동작함을 정의한다
//@RestController가 추가된 클래스는 애플리케이션 로딩 시, Spring Bean 으로 등록 해 준다
//REST API란 REST 방식을 통해서 리소스에 접근하기 위한 서비스 API를 지칭한다
//REST(Representational State Transfer)는 HTTP 네트워크 상의 리소스(Resource, 자원)를 정의하고
해당 리소스를 URI라는 고유의 주소로 접근하는 접근 방식을 의미한다
@RequestMapping("v1/members"/*,produce=MediaType.APPLICATION_JSON_VALUE*/)
//produces애트리뷰트(Attribute)는 응답 데이터를 어떤 미디어 타입으로 클라이언트에게 전송할 지를 설정한다
public class MemberController {
@PostMapping
//postMember() 메서드는 회원 정보를 등록해주는 핸들러 메서드이다
//클라이언트의 요청 데이터(request body)를 서버에 생성할 때 사용하는 애너테이션이다
//클라이언트에서 요청 전송 시 HTTP Method 타입을 동일하게 맞춰 주어야 한다(POST)
public /*String*/ResponseEntity postMember(@RequestParam("email")String email,
@RequestParam("name")String name,
@RequestParam("phone")String phone) {
//@RequestParam() 핸들러 메서드의 파라미터 종류 중 하나이다
/*클라이언트에서 전송하는 요청 데이터를 쿼리 파라미터(Query Parmeter 또는 Query String),
폼 데이터(form-data), x-www-form-urlencoded 형식으로 전송하면 서버 쪽에서 전달 받을 때
사용하는 애너테이션이다*/
// System.out.println("# email: " + email);
// System.out.println("# name: " + name);
// System.out.println("# phone: " + phone);
// JSON문자열 응답 타입을 수작업 코드에서 Map객체로 변경->produce애트리뷰트를 삭제할 수 있다
//MAp 객체를 리턴하면 내부적으로 응답 데이터를 JSON데이터로 자동 변환해야 한다고 인식한다
Map<String, String> map = new HashMap<>();
map.put("email", email);
map.put("name", name);
map.put("phone", phone);
//리턴 값을 변경된 ResponseEntity로 대체
//ResponseEntity 객체를 생성하고 생성자 파라미터로 map과 HttpStatus.CREATED를 반환한다
//HttpStatus.CREATED 는 201, created를 의미한다
return new ResponseEntity<>(map, HttpStatus.CREATED);
//클라이언트 쪽에서 JSON 형식의 데이터를 전송 받아야 하기 때문에
//응답 문자열을 JSON 형식에 맞게 작성한다
// String reponse =
// "{\"" +
// "email\":\""+email+"\"," +
// "\"name\":\""+name+"\",\"" +
// "phone\":\"" + phone+
// "\"}";
// return reponse;
}
@GetMapping("/{member-id}")
//@GetMapping은 클라이언트가 서버에 리소스를 조회할 때 사용하는 애너테이션이다
//@GetMapping 애너테이션의 괄호 안에는 몇 가지 애트리뷰트(Attribute)를 사용할 수 있다
//여기서는 전체 HTTP URI의 일부를 지정했다
//클라이언트에서 getMember() 핸들러 메서드에 요청을 보낼 경우, 최종 URI는 형태는 아래와 같다
// /v1/members/{member-id}
//{member-id}는 회원 식별자를 의미한다
//클라이언트가 요청을 보낼 때 URI로 어떤 값을 지정하느냐에 따라서 동적으로 바뀌는 값이다
public /*String*/ResponseEntity getMember(@PathVariable("member-id")long memberId) {
//getMember() 메서드는 특정 회원의 정보를 클라이언트 쪽에 제공하는 핸들러 메서드이다
//@PathVariable역시 핸들러 메서드의 파라미터 종류 중 하나이다
//@PathVariable의 괄호 안에 입력한 문자열 값은 @GetMapping("/{member-id}") 처럼 중괄호({ })
안의 문자열과 동일해야 한다
//여기서는 두 문자열 모두 “member-id” 로 동일하게 지정했다
//두 문자열이 다르면 MissingPathVariableException이 발생한다
System.out.println("# memberId: " + memberId);
//리턴 값을 변경된 ResponseEntity로 대체
//HttpStatus.OK 는 200, OK를 의미한다
return new ResponseEntity<>(HttpStatus.OK);
//not implementation
// return null;
}
@GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/members”)에 매핑된다
//getMembers() 메서드는 회원 목록을 클라이언트에게 제공하는 핸들러 메서드이다
public /*String*/ResponseEntity getMembers() {
System.out.println("# get Members");
//리턴 값을 변경된 ResponseEntity로 대체
//HttpStatus.OK 는 200, OK를 의미한다
return new ResponseEntity<>(HttpStatus.OK);
//not implementation
// return null;
}
}
- 수정된 코드를 실행한 후 postman에서 post로 request한 결과는 아래와 같다
- map에 의한 파라미터 값과 HttpStatus.CREATED의 결과값 201 Created가 반환된다
- ResponseEntity를 사용하지 않았던 코드의 결과값 200 OK 가 변경된 결과이다
- CoffeeController 와 OrderController 도 개선된 코드로 수정한다
- CoffeeController
package com.dreamfactory.exam_controller.coffee;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping(value="v1/coffees", produces = MediaType.APPLICATION_JSON_VALUE)
//v1은 버젼을 의미한다. coffees는 데이터 post, get 의 조회 위치를 의미한다
public class CoffeeController {
@PostMapping
//postCoffee() 메서드는 커피 정보를 등록해 준다
public /*String*/ResponseEntity postCoffee(@RequestParam("coffee")String coffee,
@RequestParam("coffeeId")String coffeeId,
@RequestParam("korName")String korName,
@RequestParam("engName")String engName,
@RequestParam("price")int price) {
// System.out.println("# coffee:" + coffee);
// System.out.println("# coffeeId:" + coffeeId);
// System.out.println("# korName:" + korName);
// System.out.println("# engName:" + engName);
// System.out.println("# price:" + price);
//Map객체로 변경
Map<String, String> map = new HashMap<>();
map.put("coffee", coffee);
map.put("coffeeId", coffeeId);
map.put("korName", korName);
map.put("engName", engName);
map.put("price", String.valueOf(price));
//return 값을 ResponseEntity로 변경
return new ResponseEntity<>(map, HttpStatus.CREATED);
// String reponse =
// "{\"" +
// "coffee\":\""+coffee+"\"," +
// "\"coffeeId\":\""+coffeeId+"\"," +
// "\"korName\":\""+korName+"\"," +
// "\"engName\":\""+engName+"\"," +
// "\"price\":\""+price+
// "\"}";
// return reponse;
}
@GetMapping("/{coffee-id}")
//getCoffee() 메서드는 커피 정보을 클라이언트에게 제공하는 핸들러 메서드이다
public /*String*/ResponseEntity getCoffee(@PathVariable("coffee-id")long coffeeId) {
System.out.println("# coffeeId: " + coffeeId);
return new ResponseEntity<>(HttpStatus.OK);
}
@GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/coffees”)에 매핑된다
//getCoffees() 메서드는 커피 목록을 클라이언트에게 제공하는 핸들러 메서드이다
public /*String*/ResponseEntity getCoffees() {
System.out.println("# get Coffees");
return new ResponseEntity<>(HttpStatus.OK);
}
}
- OrderController
package com.dreamfactory.exam_controller.order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping(value="v1/orders"/*, produces = MediaType.APPLICATION_JSON_VALUE*/)
public class OrderController {
@PostMapping
//postOrder() 메서드는 커피 주문 정보를 등록한다
public /*String*/ResponseEntity postOrder(@RequestParam("memberId") String memberId,
@RequestParam("coffeeId") String coffeeId) {
Map<String, String> map = new HashMap<>();
map.put("memberId", memberId);
map.put("coffeeId", coffeeId);
return new ResponseEntity(map, HttpStatus.CREATED);
// System.out.println("# memberId:" + memberId);
// System.out.println("# coffeeId:" + coffeeId);
//
// String reponse =
// "{\"" +
// "memberId\":\"" + memberId + "\"," +
// "\"coffeeId\":\"" + coffeeId +
// "\"}";
// return reponse;
}
@GetMapping("/{order-id}")
public /*String*/ResponseEntity getOrder(@PathVariable("order-id") long orderId) {
System.out.println("# orderId: " + orderId);
return new ResponseEntity(HttpStatus.OK);
}
@GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/orders”)에 매핑된다
//getOrders() 메서드는 주문 목록을 클라이언트에게 제공하는 핸들러 메서드이다
public /*String*/ResponseEntity getOrders() {
System.out.println("# get Orders");
return new ResponseEntity(HttpStatus.OK);
}
}
※ 참조 링크
▶ ResponseEntity : https://itvillage.tistory.com/44
ResponseEntity 알아보기
ResponseEntity란? ResponseEntity는 HttpEntity의 확장 클래스로써 HttpStatus 상태 코드를 추가한 전체 HTTP 응답(상태 코드, 헤더 및 본문)을 표현하는 클래스입니다. ResponseEntity를 어디에 사용할 수 있나..
itvillage.tistory.com
ResponseEntity (Spring Framework 5.3.21 API)
Extension of HttpEntity that adds an HttpStatus status code. Used in RestTemplate as well as in @Controller methods. In RestTemplate, this class is returned by getForEntity() and exchange(): ResponseEntity entity = template.getForEntity("https://example.co
docs.spring.io