개발

[React.js, 스프링부트, AWS로 배우는 웹 개발] 2장 - 컨트롤러 레이어

jih0ssang 2024. 7. 21. 13:01

컨트롤러 레이어는 비즈니스 로직 처리보다는 HTTP 요청/응답을 담당하는 레이어이다.

HTTP는 GET/POST/PUT/DELETE/OPTIONS 등과 같은 메서드와 URI를 이용해 서버에 HTTP 요청을 보낼 수 있다.

그렇다면 서버는 이 요청을 받은 후 처리를 어떻게 할까?

 

[HTTP 요청]

GET /test HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Content-Length: 17

{
	"id": 123
}
  • 클라이언트가 서버 localhost:8080에 HTTP GET 메서드를 이용해 test라는 리소스를 요청함
  • 서버는 자기 주소를 제외한 /{리소스}부분과  어떤 HTTP 메서드사용했는지 알아야 함
    그 이후 해당 리소스의 HTTP 메서드에 연결된 메서드를 실행해야 함

 

스프링 부트 스타터 웹(spring-boot-starter-web)

스프링 부트 스타터 웹(spring-boot-starter-web)의 어노테이션을 이용하면 이 연결을 쉽게 할 수 있다.

스프링 부트 스타터 웹은 스프링 부트 프로젝트 설정 시 함께 다운받았던 라이브러리이다.

 

[build.gradle]

implementation 'org.springframework.boot:spring-boot-starter-web'

 

이제부터 할 연결 작업의 어노테이션은 모두 스프링 부트 스타터 웹 패키지에서 제공하는 것이다.

 

 

TestController

[TestController.java]

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1") // 리소스
public class TestController {
  @GetMapping("/healthcheck")
  public String testController() {
    return "Healthcheck OK!";
  }
}
  • 우리는 REST API를 구현하므로 @RestController 어노테이션을 이용해 이 컨트롤러가 RestController 임을 명시함
  • RestController를 이용하면 HTTP와 관련된 코드 및 요청/응답 매핑을 스프링이 알아서 해줌

* 경로 맨 앞에 / 붙이는 거랑 / 안붙이는 거랑 큰 차이 없음 !

 

  • Client
    GET http://localhost:8080/api/v1/healthcheck
  • Server 
    @RestController  HTTP 요청 처리
    @RequestMapping("/api/v1")   리소스 경로 지정

    @GetMapping("/healthcheck")  HTTP GET 메서드 처리, 리소스 경로 지정
    @GetMapping(value = "healthcheck") 해도 됨

 

리소스 경로는 @RequestMapping 또는 @GetMapping에 지정하면 된다. 둘다 해도 되고, 둘 중 하나에 해도 됨!

 

실행 결과

./gradlew bootRun 실행하면 애플리케이션이 실행되어 위의 사진같이 나온다.

 

 


 

 

매개변수 넘겨받는 방법

/test가 아닌 /test/{id}로 PathVariable 이나 /test?id=123처럼 요청 매개변수를 받아야 한다면 어떻게 할까?

 

 

매개변수 넘겨받는 방법은 2가지가 있다.

  1. @PathVariable
  2. @RequestParam

 

@PathVariable

  • /{id}와 같이 URI의 경로로 넘어오는 값을 변수로 받을 수 있음

 

import org.apringframework.web.bind.annotation.PathVariable;

@GetMapping("/{id}")
public String testControllerWithPathVariables(@PathVariable(required = false) int id) {
    return "Hello World! ID " + id;
}
  • Client
    GET http://localhost:8080/test/123
  • Server 
    @RestController  HTTP 요청 처리
    @RequestMapping("test")   리소스 경로 지정

    @GetMapping("/{id}")  HTTP GET 메서드 처리, 변수 id에 매핑
    • /{id} 경로로 들어오는 임의의 숫자 또는 문자를 변수 id에 매핑하라는 뜻
    • 컨트롤러의 id 변수에 123이 들어간다.

 

@RequestParam

import org.apringframework.web.bind.annotation.RequestParam;

@GetMapping("/testRequestParam")
public String testControllerRequestParam(@RequestParam(required = false) int id) {
 return "Hello World! ID " + id;
}
  • ?id={id}와 같이 요청 매개변수로 넘어오는 값을 변수로 받을 수 있음

  • Client
    GET http://localhost:8080/test/testRequestParam?id=123
  • Server 
    @RestController  HTTP 요청 처리
    @RequestMapping("test")   리소스 경로 지정
    @GetMapping("/testRequestParam")  HTTP GET 메서드 처리,  리소스 경로 지정

    testControllerRequestParam(@RequestParam(required = false)  int id)
    • @RequestParam으로 들어온 임의의 숫자 또는 문자를 변수 id에 매핑
    • 컨트롤러의 id 변수에 123이 들어간다. 

 

@RequestBody

  • 보통 반환하고자 하는 리소스가 복잡할 때 사용한다.
  • 예를 들어 String이나 int 같은 기본 자료형이 아니라 오브젝트처럼 복잡한 자료형을 통째로 요청에 보내고 싶은 경우이다.

 

[RequestBody 실습을 위한 TestRequestBodyDTO]

package com.example.demo.dto;

import lombok.Data;

@Data
public class TestRequestBodyDTO {
 private int id;
 private String message;
}

 

package com.example.demo.controller;

import com.example.demo.dto.ResponseDTO;
import com.example.demo.dto.TestRequestBodyDTO;
import org.springframwork.http.ResponseEntity;
import org.springframwork.web.bind.annotation.GetMapping;
import org.springframwork.web.bind.annotation.PathVariable;
import org.springframwork.web.bind.annotation.RequestBody;
import org.springframwork.web.bind.annotation.RequestMapping;
import org.springframwork.web.bind.annotation.RequestParam;
import org.springframwork.web.bind.annotation.RequestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("test")
public class TestController {
  @GetMapping("/testRequestBody")
  public String testControllerRequestBody(@RequestBody TestRequestBodyDTO testRequestBodyDTO) {
    return "Hello! ID " + testRequestBodyDTO.getId() + " Message:" + testRequestBodyDTO.getMessage();
  }
}
    • TestRequestBodyDTO를 요청 바디로 받는 testControllerRequestBody() 메서드이다.

  • Client
    GET http://localhost:8080/test/testRequestParam {'id`: 123, 'message`: `Hello!`}
  • Server 
    @RestController  HTTP 요청 처리
    @RequestMapping("test")   리소스 경로 지정
    @GetMapping("/testRequestParam")  HTTP GET 메서드 처리,  리소스 경로 지정

    testControllerRequestBody(@RequestBody TestRequestBodyDTO testRequestBodyDTO)
  • JSON 형태의 String인  {'id`: 123, 'message`: `Hello!`} 를 TestRequestBodyDTO로 변환 후 전달

* JSON의 내부는 구조가 TestRequestBodyDTO와 같아야 함. 

 

[JSON 형태의 TestRequestBody DTO]

{
    "id"      : 123,
    "message" : "Hello!"
}

 

 

 

@ResponseBody

  • 문자열보다 복잡한 오브젝트를 리턴하려면 어떻게 할까 ?
  • 요청을 통해 오브젝트를 가져올 수 있는데 응답으로 오브젝트를 리턴할 수 있다.
  • 간단하게 그냥 오브젝트를 리턴하면 된다. 이런 간단함의 비밀은 @RestController 어노테이션에 있다.
@Controller
@ResponseBody
public @interface RestController {
...
}
  • @Controller
    • "이 클래스는 웹 요청을 처리하는 컨트롤러이다."를 스프링에게 알려줌
    • 스프링은 이 정보를 바탕으로 해당 클래스의 오브젝트를 만들고 다른 오브젝트와의 의존성을 연결함
  • @ResponseBody 
    • HTTP 응답의 바디(Body)로 변환하여 클라이언트에게 직접 전송하도록 하는 어노테이션
    • 이 어노테이션이 붙은 메서드의 반환 값은 JSON이나 XML 등으로 변환되어 HTTP 응답으로 전송됨
  • @RestController
    • @Controller와 @ResponseBody의 조합
    • 이 어노테이션을 사용하면 클래스의 모든 메서드에 @ResponseBody가 자동으로 적용
    • 별도로 @ResponseBody를 붙일 필요 없이, 메서드의 반환 값이 HTTP 응답의 바디로 전송

[ResponseDTO를 반환하는 컨트롤러 메서드]

@GetMapping("/testResponseBody")
public ResponseDTO<String> testControllerResponseBody() {
 List<String> list = new ArrayList<>();
 list.add("Hello!");
 ResponseDTO<String> response = ResponseDTO.<String>builder().data(list).build();
 return response;
}

 

 

[localhost:8080/test/testResponseBody HTTP 응답]

{
	"error": null,
    "data": "Hello!"
}
  • 컴파일 후 localhost:8080/test/testResponseBody를 실행시키면 다음과 같은 JSON이 리턴된다.
  • 우리가 작성할 컨트롤러는 모두 ResponseEntity를 반환할 예정이다.

 

[ResponseEntity 를 반환하는 컨트롤러 메서드]

@GetMapping("/testResponseEntity"]
public ResponseEntity<?> testControllerResponseEntity() {
 List<String> list = new ArrayList<>();
 list.add("Hello! And you got 400!");
 ResponseDTO<String> response = ResponseDTO.<String>builder().data(list).build();
 // http status를 400으로 설정
 return ResponseEntity.badRequest().body(response);
}
  • 정상적으로 응답을 반환한다면 badRequest()가 아닌 ok() 메서드를 사용해야 함

 

ResponseEntity 리턴   VS    ResponseDTO 리턴   비교

  • 공통점: 리턴된 Body에는 아무 차이가 없고 모두 오브젝트를 리턴함
  • 차이점: 전자는 헤더와 HTTP Status를 조작할 수 있음     → badRequest() or ok()