본 글은 김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의를 토대로 작성하였습니다.
이전 글들에서 Spring MVC에서 HTTP 요청을 받으면 이를 어떻게 처리하는지에 대해 알아보았다.
이번엔, HTTP 응답을 어떻게 보내는 지에 대해 알아보자.
Spring에서 HTTP 응답을 내보내는 방식은 크게 3가지가 있다.
순서대로 알아보자.
스프링 부트는 클래스 패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.
/static , /public , /resources , /META-INF/resources
정적 리소스 경로
src/main/resources/static
다음 경로에 파일이 들어있으면
src/main/resources/static/basic/hello-form.html
웹 브라우저에서 다음과 같이 실행하면 된다. localhost:8080/basic/hello-form.html
정적 리소스는 해당 파일을 변경 없이 그대로 서비스하는 것이다.
해당하는 뷰 리졸버를 호출하여 뷰를 만들어 내보내는 방식이다.
스프링 부트에서는 기본적으로 src/main/resources/templates의 경로로 뷰 템플릿 디렉토리를 지원한다.
뷰 템플릿을 내보내는 방식도 여러가지가 있는데, 순서대로 알아보자.
먼저,
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello")
.addObject("data", "hello!");
return mav;
}
생성자 인자로 렌더링할 뷰가 있는 templates 하위 경로를 적어주고 필요한 데이터들을 addObject()로 넣어주어 반환하면 된다.
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
model.addAttribute("data", "hello!!");
return "response/hello";
}
인자로 Model 객체를 선언하여 여기에 값들을 담고 서빙할 뷰 템플릿 경로를적어주면된다.
❗️추가
이 글을 적으면서 왜 Model은 인자에 선언해야하는 것일까라는 의문이 들었다.
그래서 그냥 메소드 안에 객체를 선언하려 해보니 Model은 인터페이스라 객체 생성이 불가했다.
그렇다면 저 인자의 model에는 어떤 클래스의 객체가 생성될까? 바로 찍어보자. System.out으로 찍어보니 BindingAwareModelMap이라는 클래스의 객체가 담겨져 있었다. 해당 클래스로 들어가보자.
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation.support;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.validation.BindingResult;
/**
* Subclass of {@link org.springframework.ui.ExtendedModelMap} that automatically removes
* a {@link org.springframework.validation.BindingResult} object if the corresponding
* target attribute gets replaced through regular {@link Map} operations.
*
* <p>This is the class exposed to handler methods by Spring MVC, typically consumed through
* a declaration of the {@link org.springframework.ui.Model} interface. There is no need to
* build it within user code; a plain {@link org.springframework.ui.ModelMap} or even a just
* a regular {@link Map} with String keys will be good enough to return a user model.
*
* @author Juergen Hoeller
* @since 2.5.6
* @see org.springframework.validation.BindingResult
*/
@SuppressWarnings("serial")
public class BindingAwareModelMap extends ExtendedModelMap {
@Override
public Object put(String key, @Nullable Object value) {
removeBindingResultIfNecessary(key, value);
return super.put(key, value);
}
@Override
public void putAll(Map<? extends String, ?> map) {
map.forEach(this::removeBindingResultIfNecessary);
super.putAll(map);
}
private void removeBindingResultIfNecessary(Object key, @Nullable Object value) {
if (key instanceof String attributeName) {
if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attributeName;
BindingResult bindingResult = (BindingResult) get(bindingResultKey);
if (bindingResult != null && bindingResult.getTarget() != value) {
remove(bindingResultKey);
}
}
}
}
}
들어가 보니 요러한 클래스였다. Spring Boot가 내부적으로 클라이언트에서 요청이 들어오면 해당 클래스 객체를 생성하여 알아서 model 변수에 넣어주는 것 같다.
이 또한 응답을 내보내는 방법이 여러가지가 있다. 단계별로 알아보자
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException
{
response.getWriter().write("ok");
}
가장 기본적인 서블릿 리스폰스 객체를 이용하는 방법이다.
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
HttpEntity 클래스를 상속받은 ResponseEntity를 이용하여 반환할 수 있다. 물론 HttpEntity도 사용가능하다.
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
return "ok";
}
메소드 레벨에 @ResponseBody를 붙이는 방법
내부적으로 HTTP 메시지 컨버터가 동작하여 반환 문자열을 HTTP Message Body에 담아 반환한다.
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
ResponseEntity를 이용하여 반환하는 방식 내부적으로 Http 메시지 컨버터가 동작하여 해당 객체를 JSON 형태로 바꾸어 HTTP Message Body에 담아 반환한다.
추가적으로
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
다음과 같이 ResponseBody를 붙이고 그냥 객체를 반환해도 동일하다.