Part 6. 스프링 MVC의 Controller
6.5 Controller의 리턴 타입
- 스프링 MVC의 구조가 기존의 상속과 인터페이스에서 어노테이션을 사용하는 방식으로 변한 이후에 가장 큰 변화 중 하나는 리턴 타입이 자유로워 졌다는 점이다,
- Controller의 메서드가 사용할 수 있는 리턴 타입은 주로 다음과 같다.
- String : jsp를 이용하는 경우에는 jsp 파일의 경로와 파일이름을 나타내기 위해 사용한다.
- void : 호출하는 URL과 동일한 이름의 jsp를 의미한다.
- VO, DTD 타입 : 주로 JSON 타입의 데이터를 만들어 반환하는 용도로 사용한다.
- ResponseEntity 타입 : response 할 때 Http 헤더 정보와 내용을 가공하는 용도로 사용한다.
- Model, ModelAndView : Model로 데이터를 반환하거나 화면까지 같이 지정하는 경우에 사용한다.
- HttpHeaders : 응답에 내용 없이 Http 헤더 메시지만 전달하는 용도로 사용한다.
6.5.1 void 타입
- 메서드의 리턴 타입을 void로 지정하는 경우 일반적인 경우에는 해당 URL의 경로를 그대로 jsp 파일의 이름으로 사용하게 된다.
< Sample Controller >
@GetMapping("/ex05")
public void ex05() {
log.info("/ex05..........");
}
- 브라우저에서 SampleController의 경로에 ex05()의 경로를 합쳐 '/sample/ex05'를 호출하면 다음과 같은 결과를 보게 된다.

- 에러 메시지의 원인이 '/WEB-INF/views/sample/ex05.jsp가 존재하지 않아서 생기는 문제라는 것을 볼 수 있다.
- 이것은 servlet-context.xml의 아래 설정과 같이 맞물려 URL 경로를 View로 처리하기 때문에 생기는 결과다.
< servlet-context.xml >
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
6.5.2 String 타입
- void 타입과 더불어 가장 많이 사용하는 것은 String 타입이다.
- String 타입은 상황에 따라 다른 화면을 보여줄 필요가 있을 경우에 유용하게 사용한다. (if ~ else와 같은 처리가 필요한 상황)
- 일반적으로 String 타입은 현재 프로젝트의 경우 JSP 파일의 이름을 의미한다.
- 프로젝트 생성 시 기본으로 만들어진 HomeController의 코드를 보면 String을 반환타입으로 사용하는 것을 볼 수 있다.
< HomeController >
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
- home() 메서드는 'home'이라는 문자열을 리턴했기 때문에 경로는 '/WEB-INF/views/home.jsp' 경로가 된다.
- String 타입에는 다음과 같은 특별한 키워드를 붙여 사용할 수 있다.
- redirect : 리다이렉트 방식으로 처리하는 경우
- forward : 포워드 방식으로 처리하는 경우
6.5.3 객체 타입
- Controller의 메서드 리턴 타입을 VO나 DTO타입 등 복합적인 데이터가 들어간 객체 타입으로 지정할 수 있는데, 이 경우는 주로 JSON 데이터를 만들어 내는 용도로 사용한다.
- 우선 이를 위해 jackson-databind 라이브러리를 pom.xml에 추가한다.
< pom.xml에 추가되는 Jackson-databind 라이브러리 >
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
- SampleController에는 아래와 같은 메서드를 생성한다.
< SampleController >
@GetMapping("/ex06")
public @ResponseBody SampleDTO ex06() {
log.info("/ex06..........");
SampleDTO dto = new SampleDTO();
dto.setAge(10);
dto.setName("홍길동");
return dto;
}
- 스프링 MVC는 자동으로 브라우저에 JSON 타입으로 객체를 변환해서 전닫ㄹ하게 된다.

- 개발자 도구를 통해 살펴보면 서버에서 전송하는 MIME 타입이 'application/json'으로 처리되는 것을 볼 수 있다.
- 만일 Jackson-databind 라이브러리가 포함되지 않았다면 다음과 같은 에러 화면이 나타난다.
- 스프링 MVC는 리턴 타입에 맞게 데이터를 변환해 주는 역할을 지정할 수 있는데 기본적으로 JSON은 처리가 되므로 별도의 설정이 필요하지 않다.

6.5.4 ResponseEntity 타입
- Web을 다루다 보면 HTTP 프로토콜의 헤더를 다루는 경우도 종종 있다.
- 스프링 MVC의 사상은 HttpServletRequest나 HttpServletResponse를 직접 핸들링하지 않아도 이런 작업이 가능하도록 작성되었기 때문에 이러한 처리를 위해 ResponseEntity를 통해 원하는 헤더 정보나 데이터를 전달할 수 있다.
< Sample Controller >
@GetMapping("/ex07")
public ResponseEntity<String> ex07() {
log.info("/ex07..........");
// {"name": "홍길동"}
String msg = "{\"name\": \"홍길동\"}";
HttpHeaders header = new HttpHeaders();
header.add("Content-Type", "application/json;charset=UTF-8");
return new ResponseEntity<>(msg, header, HttpStatus.OK);
}
- ResponseEntity는 HttpHeaders 객체를 같이 전달할 수 있고, 이를 통해 원하는 HTTP 헤더 메시지를 가공하는 것이 가능하다.
- ex07()의 경우 브라우저에는 JSON 타입이라는 헤더 메시지와 200 OK라는 상태 코드를 전달한다.


6.5.5 파일 업로드 처리
- 파일 업로드를 하기 위해서는 전달되는 파일 데이터를 분석해야 하는데 이를 위해서는 Servlet 3.0 전까지는 commons의 파일 업로드를 이용하거나 cos.jar 등을 이용해 처리해 왔다.
- Servlet 3.0 이후에는 기본적으로 업로드되는 파일을 처리할 수 있는 기능이 추가되어 있으므로 더 이상 추가적인 라이브러리가 필요하지 않다.
- Spring Legacy Project로 생성되는 프로젝트의 경우 Servlet 2.5를 기준으로 생성되므로 3.0 이후에 지원되는 설정을 사용하기 어렵다.
- 예제는 일반적으로 많이 사용하는 commons-fileupload를 이용한다.
< pom.xml에 추가되는 commons-fileupload 라이브러리 >
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
- 라이브러리를 추가한 후 파일이 임시로 업로드될 폴더를 D드라이브 아래 upload/tmp로 작성한다.
servlet-context.xml 설정
- servlet-context.xml은 스프링 MVC의 특정한 객체를 설정해서 파일을 처리한다.
- 다른 객체를 설정하는 것과 달리 파일 업로드의 경우에는 반드시 id 속성의 값을 multipartResolver로 정확하게 지정해야 한다.
< servlet-context.xml >
<beans:bean id="multiparResolver" class="org.springframework.web.multipart.commons/CommonsMultipartResolver">
<beans:property name ="defaultEncoding" value="utf-8"></beans:property>
<beans:property name="maxuploadSize" value="104857560"></beans:property>
<beans:property name="maxUploadSizePerFile" value="2097152"></beans:property>
<beans:property name="uploadTempDir" value="file:/D:/upload/tmp"></beans:property>
<beans:property name="maxInMemorySize" value="10485756"></beans:property>
</beans:bean>
- maxUploadSize는 한 번의 Request로 전달될 수 있는 최대의 크기를 의미하고, maxUploadSizePerFile은 하나의 파일 최대 크기, maxInMemorySize는 메모리상에서 유지하는 최대의 크기를 의미한다.
- 만일 이 크기 이상의 데이터는 uploadTempDir에 임시 파일의 형태로 보관된다.
- uploadTempDir에서 절대 경로를 이용하려면 URI 형태로 제공해야 하기 때문에 file:/로 시작하도록 한다. defaultEncoding은 업로드 하는 파일의 이름이 한글일 경우 깨지는 문제를 처리한다.
- SampleController에서는 다음과 같이 get 방식으로 파일을 업로드할 화면을 처리한다.
< SampleController >
@GetMapping("/exUpload")
public void exUpload() {
log.info("/exUpload........");
}
- 파일 업로드를 해볼 /WEB-INF/views/sample/exUpload.jsp 파일을 작성한다.
< exUpload.jsp >
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equive="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/sample/exUploadPost" method="post" enctype="multipart/form-data">
<div>
<input type='file' name='files'>
</div>
<div>
<input type='file' name='files'>
</div>
<div>
<input type='file' name='files'>
</div>
<div>
<input type='file' name='files'>
</div>
<div>
<input type='file' name='files'>
</div>
<div>
<input type='submit'>
</div>
</form>
</body>
</html>
- exUpload.jsp는 여러 개의 파일을 한꺼번에 업로드하는 예제로 작성했다.
- < form > 태그의 action 속성, method 속성, enctype 속성에 주의해 작성해야 한다.
- 브라우저는 아래와 같은 모습으로 보인다.

- exUpload.jsp의 action 속성값은 '/sample/exUploadPost'로 작성되었으므로, 이에 맞는 메서드를 SampleController에 추가한다.
< SampleController >
@PostMapping("/exUploadPost")
public void exUploadPost(ArrayList<MultipartFile> files) {
files.forEach(file -> {
log.info("-----------------------------------------");
log.info("name:" + file.getOriginalFilename());
log.info("size:" + file.getSize());
});
- 스프링 MVC는 전달된는 파라미터가 동일한 이름으로 여러 개 존재하면 배열로 처리가 가능하므로 파라미터를 MultipartFile의 배열 타입으로 작성한다.
- 실제로 파일을 업로드해 보면 아래와 같은 결과를 볼 수 있다.
- 현재 설정은 한 파일의 최대 크기가 2MB이므로 그보다 작은 크기의 파일을 지정해 업로드를 테스트한다.

INFO : org.zerock.controller.SampleController - -----------------------------------------
INFO : org.zerock.controller.SampleController - name:ex01.txt
INFO : org.zerock.controller.SampleController - size:0
INFO : org.zerock.controller.SampleController - -----------------------------------------
INFO : org.zerock.controller.SampleController - name:ex02.txt
INFO : org.zerock.controller.SampleController - size:0
INFO : org.zerock.controller.SampleController - -----------------------------------------
INFO : org.zerock.controller.SampleController - name:ex03.txt
INFO : org.zerock.controller.SampleController - size:0
INFO : org.zerock.controller.SampleController - -----------------------------------------
INFO : org.zerock.controller.SampleController - name:ex04.txt
INFO : org.zerock.controller.SampleController - size:0
INFO : org.zerock.controller.SampleController - -----------------------------------------
INFO : org.zerock.controller.SampleController - name:ex05.txt
INFO : org.zerock.controller.SampleController - size:0

- 위의 그림에서 중간에 보이는 로그는 SampleController에서 업로드 정보가 올바르게 처리되는 것을 보여주고 있다.
- 최종 byte[]를 처리해야 하는데 예제는 아직 처리하지 않은 상태다.
Java 설정을 이용하는 경우
- Java 설정을 이용하는 경우에는 @Bean을 이용해서 처리하기는 하지만, id 속성값을 같이 부여한다.
- servlet-context.xml과 관련된 설정이므로 ServletConfig 클래스를 이용해서 처리한다.
< ServletConfig >
@Bean(name = "multipartResolver")
public CommonsMultipartResolver getResolver() throws IOException {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
// 10MB
resolver.setMaxUploadSize(1024 * 1024 * 10);
// 2MB
resolver.setMaxUploadSizePerFile(1024 * 1024 * 2
// 1MB
resolver.setMaxInMemorySize(1024 * 1024);
// temp upload
resolver.setUploadTempDir(new FileSystemResource("D:\\upload\\tmp"));
resolver.setDefaultEncoding("UTF-8");
return resolver;
}