MVC 기본 기능

Jaca·2021년 10월 13일
0

Controller

@Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.

@Controller와 @ResponseBody를 사용하게 되면, 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다. 이 경우에 view를 사용하지 않는다.

이 두가지를 편의성을 위해 합친 것이 @RestController 이다.

단순하게 보자면, @RestController 는 @Controller와 @ResponseBody 를 합친 것.

RequestMapping

@RequestMapping은,
DefaultAnnotationHandlerMapping에서 컨트롤러를 선택할 때 대표적으로 사용하는 애노테이션이다.
url당 하나의 컨트롤러에 매핑되던 다른 핸들러 매핑과 달리 메서드 단위까지 세분화하여 적용할 수 있으며, url 뿐 아니라 파라미터, 헤더 등 더욱 넓은 범위를 적용할 수 있다.

하지만 RequestMapping의 취약점은 모든 http 메서드를 허용한다는 것이다.
만약 데이터 저장을 위한 로직인데 Delete 메서드로 요청이 들어온다면 치명적일 것이다.
그래서 RequestMapping의 대표적인 속성으로 허용할 메서드를 지정할 수 있는데, 이것을 위한 편리한 축약 애노테이션을 제공한다.
대표적으로 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping 가 있다.

HTTP 요청

클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

  • GET - 쿼리 파라미터
  • POST - HTML Form
  • HTTP message body에 데이터를 직접 담아서 요청

쿼리 파라미터, HTML Form는 URL에 파라미터로 데이터를 전달하는 방식으로 써, 전달하는 방법만 다를뿐 같은 형식으로써 처리 방법이 같다.

먼저 파라미터 전달로 들어온 데이터를 다루는 방법을 알아보자.

PathVariable

사용 방법은 간단하다.
또한, @PathVariable 의 이름과 파라미터 이름이 같으면 name 속성을 생략할 수 있다.

RequestParam

먼저 RequestParam은 4가지 파라미터를 가지고 있다.

  • defaultVaule : 값이 없을 때 기본값, 기본값이 설정되면 자연스레 required는 무의미해진다.
  • name : URL에서 바인딩할 파라미터의 값
  • value : URL에서 바인딩하여 별칭으로 정할 값
  • required : 필수적으로 값이 전달되어져야 하는 파라미터, 빈 값을 전달해도 값이 있는 것으로 판단함.

RequestParam 또한, name과 바인딩할 변수의 이름이 같다면, name 속성이 생략이 가능하다.

또, name과 변수의 이름이 같고, String , int , Integer 등의 단순 타입이면 @RequestParam 애노테이션 자체의 생략도 지원한다.

파라미터를 Map, MultiValueMap으로 조회할 수 있다.

Map의 장점이라면, value의 타입을 Object로 지정하여 타입에 구애받지 않고 파라미터를 받을수 있다..?

PathVariable VS RequestParam

이 둘의 메서드를 비교하면 기능적으로는 큰 차이를 느끼기 힘들다.

이 둘은 어떤 차이가 있을까?

  • PathVariable : localhost:8080/url/username/{username}/age/{age}
  • RequestParam : localhost:8080/url?username={username}&age={age}

이렇게 놓고 비교하면 요청 URL의 패턴이 다른 것을 알 수 있다.
PathVariable의 경우 "/" 로 구분하며 매칭 되는 부분을 편리하게 조회할 수 있다.

RequestParam 은 마치 JSON 처럼 키 값의 쌍처럼 파라미터를 전달한다.

아직은 잘 모르겠으나... 유연성이 RequestParam이 더 좋아보인다..!
둘중 무엇을 사용할지 결정하는 것은 결국 협의를 통해 결정하는 것이라고..

ModelAttribute

실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다. like DTO

이러한 과정을 자동화 해주는 애노테이션이다.

스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

  • HelloData 객체를 생성한다.
  • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
  • 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.

@ModelAttribute 는 생략할 수 있다.
그런데 @RequestParam도 생략 가능하고, 사용법이 같은데..?

스프링은 해당 생략시 다음과 같은 규칙을 적용한다.

  • String , int , Integer 같은 단순 타입 = @RequestParam
  • 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

Http message body - TEXT

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 데이터가 넘어오는 경우는 @RequestParam , @ModelAttribute 를 사용할 수 없다. (HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)

스프링 MVC에서 HTTP 메시지 바디에 담긴 텍스트를 다루기 위해

여러가지 파라미터를 제공한다.

  • InputStream, OutputStream

InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회 OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력

  • HttpEntity

HTTP header, body 정보를 편리하게 조회할 수 있으며, 메시지 바디 정보를 직접 조회한다.

요청 파라미터를 조회하는 기능과 관계 없으며 (@RequestParam X, @ModelAttribute X)
HttpEntity는 응답에도 사용 가능하다.
메시지 바디 정보 직접 반환하며, 헤더 정보 포함 가능하고 view 조회를 하지 않는다.

HttpEntity를 상속받은 RequestEntity와 ResponseEntity 클래스가 있다.

  • RequestEntity : HttpMethod, url 정보가 추가, 요청에서 사용
  • ResponseEntity : HTTP 상태 코드 설정 가능, 응답에서 사용

여기서 궁금한게, ResponseBody와 ResponseEntity의 차이점인데..

@ResponseBody나 ResponseEntity를 return 하는거나 결과적으로는 같은 기능이지만..
그 구현 방법이 달라지게된다.
예를 들어 header 값을 변경시켜야 할 경우엔 @ResponseBody의 경우 파라미터로 Response 객체를 받아서 이 객체에서 header를 변경시켜야 하고, ResponseEntity에서는 이 클래스 객체를 생성한뒤 객체에서 header 값을 변경시키면 된다.

이와 동일하게 파라미터로 RequestBody가 존재한다.

RequestBody를 사용하면, 예와 같이 아주 편리하게 body 메세지를 받을 수 있다.

RequestBody도 ResponseEntity와 같이 header 따위의 기타 정보가 필요하다면, HttpEntity로 받거나, 별도의 헤더 설정을 통해서 정보를 가져와야한다.

결국 두 방식은 총 로직은 같아지나 내부 설계가 달라지니 그때그때 알맞는 방법을 사용해야한다는 것..

Http message body - JSON

이제까진 서블릿을 통한 데이터 처리는 보지 않았지만, 이번엔 한번 보고 가자.

문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 를 사용해서 자바 객체로 변환하는 과정이 필요하다.

하지만 위의 @RequestBody 를 사용하면 이 과정을 생략할 수 있다.

HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.
HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해준다.

v3, v4 모두 objectMapper의 변환 과정을 생략했다.

요청과 응답의 변환 과정은 아래와 같다.

@RequestBody 요청
JSON 요청 -> HTTP 메시지 컨버터 -> 객체
@ResponseBody 응답
객체 -> HTTP 메시지 컨버터 -> JSON 응답

HTTP 응답

스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다.

  • 정적 리소스
    예) 웹 브라우저에 정적인 HTML, css, js을 제공할 때는, 정적 리소스를 사용한다.
  • 뷰 템플릿 사용
    예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
  • HTTP 메시지 사용
    HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

정적 리소스

스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.
/static , /public , /resources , /META-INF/resources

src/main/resources 는 리소스를 보관하는 곳이고, 또 클래스패스의 시작 경로이다. 따라서 다음 디렉토리에 리소스를 넣어두면 스프링 부트가 정적 리소스로 서비스를 제공한다.

만약 src/main/resources/static/basic/hello-form.html 에 파일이 들어있다면, 웹 브라우저에서 다음과 같이 실행할 수 있다. http://localhost:8080/basic/hello-form.html

정적 리소스는 해당 파일을 변경 없이 그대로 서비스하는 것이다.

뷰 템플릿

뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다.
일반적으로 HTML을 동적으로 생성하는 용도로 사용하지만, 다른 것들도 가능하다. 뷰 템플릿이 만들 수 있는 것이라면 뭐든지 가능하다.

스프링 부트는 기본 뷰 템플릿 경로를 제공한다.
src/main/resources/templates

만약 아래와 같은 템플릿을 사용하고자 한다면,

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p th:text="${data}">empty</p>
</body>
</html>

코드 중 ${data} 은 마치 매개변수 같은 것으로, 비어있을 경우 empty가 출력 된다.

만약 위와 같은 메서드를 사용한다면,
model 객체의 메서드를 사용해 템플릿의 매개변수 이름과 일치시켜주면 두번째 매개변수의 데이터가 들어가게된다.

여기서 눈 여겨 봐야할 점은 리턴 타입이 String 이라는 점이다.

String을 반환하는 경우 - View or HTTP 메시지
View를 보여줄지, 단순 텍스트가 넘어갈지 결정하는 것은 @ResponseBody의 유무이다.
@ResponseBody 가 없으면 response/hello 로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 한다.
@ResponseBody 가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자가 입력된다.

여기서는 뷰의 논리 이름인 response/hello 를 반환하면 다음 경로의 뷰 템플릿이 렌더링 되는 것을 확인할 수 있다.

Void를 반환하는 경우,
@Controller 를 사용하고, HttpServletResponse ,OutputStream(Writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용한다.

  • 요청 URL: /response/hello
  • 실행: templates/response/hello.html

참고로 이 방식은 명시성이 너무 떨어지고 이렇게 딱 맞는 경우도 많이 없어서, 권장하지 않는다.

HTTP API, 메시지 바디에 직접 입력

HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

responseBodyV2
제네릭 타입을 String 으로 지정해준 것을 볼 수 있다.
HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 가지고 있다. ResponseEntity 는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.

responseBodyV3
@ResponseBody 를 사용하면 view를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력할 수 있다. ResponseEntity 도 동일한 방식으로 동작한다.

responseBodyJsonV1
ResponseEntity 를 반환한다. HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다.

responseBodyJsonV2
ResponseEntity 는 HTTP 응답 코드를 설정할 수 있는데, @ResponseBody 를 사용하면 이런 것을 설정하기 까다롭다.
@ResponseStatus(HttpStatus.OK) 애노테이션을 사용하면 응답 코드도 설정할 수 있다.

물론 애노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다. 프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity 를 사용하면 된다.

profile
I am me

0개의 댓글