Spring MVC

Spring MVC(3) - API 계층 -Controller (ResponseEntity 적용)

상상날개 2022. 9. 21. 13:52

■ 이번 페이지는 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

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html

 

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