[spring] 스프링 MVC의 Controller - 2
업데이트:
Model 데이터 전달자
Controller의 메서드를 작성할 때는 특별하게 Model
이라는 타입을 파라미터로 지정할 수 있따. Model 객체는 JSP에 컨트롤러에서 생성된 데이터를 담아서 전달하는 역할을 한다. 이를 이용해 JSP와 같은 뷰(View)로 전달해야 하는 데이터를 담아서 보낼 수 있다.
Model은 모델 2 방식에서 사용하는 request.setAttribute()와 유사하다.
// servlet
request.setAttribute("serverTime", new java.util.Date());
RequestDispatcher dispatcher
= request.getRequestDispatcher("/WEB-INF/jsp/home.jsp");
dispatcher.forward(request, response);
위의 코드를 스프링에서는 Model을 이용해서 다음과 같이 처리한다.
// 스프링
public String home(Model model){
model.addAttribute("serverTime", new java.util.Data());
return "home";
}
Model을 사용해야 하는 경우는 주로 Controller에 전달된 데이터를 이용해서 추가적인 데이터를 가져와야 하는 상황이다.
예를들면
- 리스트 페이지 번호를 파라미터로 전달받고, 실제 데이터를 View로 전달해야 하는경우
- 파라미터들에 대한 처리 후 결과를 전달해야 하는 경우
@ModelAttribute 어노테이션
Model의 경우는 파라미터로 전달된 데이터는 존재하지 않지만 화면에서 필요한 데이터를 전달하기 위해서 사용한다.
스프링 MVC의 Controller는 기본적으로 Java Beans 규칙에 맞는 객체는 다시 화면으로 전달한다. 좁은 의미에서 Java Beans의 규칙은 단순히 생성자가 없거나 빈 생성자를 가져야 하며, getter/setter를 가진 클래스의 객체들을 의미한다.
반면에 기본 자료형의 경우는 파라미터로 선언하더라도 기본적으로 화면까지 전달되지는 않는다.
// sampleController에 추가
@GetMapping("/ex04")
public String ex04(SampleDTO dto, int page) {
log.info("dto : " + dto);
log.info("page : " + page);
return "/sample/ex04";
}
위의 경로에 ex04.jsp 파일을 생성한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>SAMPLEDTO ${sampleDTO}</h2>
<h2>PAGE ${page}</h2>
</body>
</html>
작성 후 서버를 실행하며 다음과 같이 주소창에 작성한다.
http://localhost:8080/sample/ex04?name=aaa&age=11&page=9
결과를 보면 SampleDTO만 전달되고 int 타입인 page는 전달되지 않았다.
@ModelAttribute는 강제로 전달받은 파라미터를 Model에 담아서 전달하도록 할 때 필요한 어노테이션이다. @ModelAttribute가 걸린 파라미터는 타입에 관계없이 무조건 Model에 담아서 전달된다.
// sampleController 수정
@GetMapping("/ex04")
public String ex04(SampleDTO dto, @ModelAttribute("page") int page) {
log.info("dto : " + dto);
log.info("page : " + page);
return "/sample/ex04";
}
다시 위의 주소를 들어가면 page 부분이 화면에 출력되는 걸 확인할 수 있다.
RedirectAttributes
스프링 MVC가 자동으로 전달해 주는 타입 중에는 RedirectAttributes 타입이 존재한다. RedirectAttributes는 일회성으로 데이터를 전달하는 용도로 사용된다. RedirectAttributes 기존 Serlvet에서는 response.sendRedirect()를 사용할 떄와 동일한 용도로 사용된다.
// servlet
response.sendRedirect("/home?name=aaa&age=10");
// 스프링
rttr.addFlashAttribute("name", "AAA");
rttr.addFlashAttribute("age", 10);
return "redirect:/";
RedirectAttributes는 Model과 같이 파라미터로 선언해서 사용하고, addFlashAttribute(이름, 값) 메서드를 사용해서 화면에 한 번만 사용하고 다음에는 사용되지 않는 데이터를 전달하기 위해서 사용한다.
Controller의 리턴 타입
스프링 MVC의 구조가 기존의 상속과 인터페이스에서 어노테이션을 사용하는 방식으로 변한 이후에 가장 큰 변화 중 하나는 리턴 타입이 자유로워졌다는 점이다.
리턴 타입
- String : jsp를 이용하는 경우에 jsp 파일의 경로와 파일 이름
- void : 호출하는 URL과 동일한 이름의 jsp를 의미
- VO, DTO : 주로 JSON 타입의 데이터를 만들어서 반환
- ResponseEntity : response 할 때 Http 헤더 정보와 내용을 가공하는 용도로 사용
- Model, ModelAndView : Model로 데이터를 반환하거나 화면까지 같이 지정하는 경우에 사용하는데 요즘에는 잘 사용하지 않는다.
- HttpHeaders : 응답에 내용 없이 Http 헤더 메시지만 전달하는 용도
void 타입
// SapleController
@GetMapping("/ex05")
public void ex05() {
log.info("/ex05...................................");
}
프로젝트를 서버로 실행하고 주소창에 /sample/ex05를 호출하면 에러 메세지가 뜬다.
이 메세지가 뜨는 원인은 servlet-context.xml에서 아래 설정과 맞물려 URL 경로를 View로 처리하기 때문에 생기는 결과이다.
<!-- Resolves views selected for rendering by @Controllers to .jsp
resources in the /WEB-INF/views directory -->
<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>
String 타입
String 타입은 상황에 따라 다른 화면을 보여줄 필요가 있을 경우에 유용하게 사용한다. 일반적으로 String 타입은 현재 프로젝트의 경우 JSP 파일의 이름을 의미한다.
src/main/java > HomeController.java가 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 : 포워드 방식으로 처리하는 경우
객체 타입
VO나 DTO를 타입 등 복합적인 데이터가 들어간 객체 타입으로 지정할 수 있는데, 이 경우는 주로 JSON
데이터를 만들어내는 용도로 사용한다.
일단 JSON을 사용하기 위해 pom.xml에 라이브러리를 추가한다.
<!-- pom.xml -->
<!-- JSON -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/
jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
// sampleController
@GetMapping("/ex06")
public @ResponseBody SampleDTO ex06() {
log.info("/ex06!!!!!!!!!!!!!!!!!!!!!!!");
SampleDTO dto = new SampleDTO();
dto.setAge(10);
dto.setName("bbb");
return dto;
}
스프링 MVC는 자동으로 브라우저에 JSON 타입으로 객체를 변환해서 전달한다.
ResponseEntity 타입
ResponseEntity은 HTTP 프로토콜에서 원하는 헤더의 정보나 데이터를 전달할 수 있다.
// SampleController
@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);
}
파일 업로드 처리
파일 업로드를 하기 위해서는 전달되는 파일 데이터를 분석해야 한다. 이 과정이 servlet 3.0 전까지는 commons의 파일 업로드를 이용하거나 cos.jar 등을 이용해서 처리했다. Servlet 3.0 이후에는 기본적으로 업로드 되는 파일을 처리할 수 있는 기능이 추가되어 별도로 라이브러리를 추가하지 않아도 된다.
‘Spring Legacy Project’의 경우 Servlet 2.5를 기준으로 생성되기 때문에 3.0 이후에 지원되는 설정을 사용하기 어렵다. 그래서 일단 commons-fileupload를 이용해보자.
<!-- pom.xml -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
파일이 임시로 업로드될 폴더를 C 드라이브 아래 upload/tmp 폴더를 생성한다.
servlet-context.xml
파일 업로드의 경우에는 serlvet-context.xml 내 beans 생성시 반드시 id 속성의 값을 multipartResolver
로 설정해야 한다.
<beans:bean id="multipartResolver" class="org.springframework.web.
multipart.commons.CommonsMultipartResolver">
<beans:property name="defaultEncoding" value="utf-8"></beans:property>
<!-- defaultEncoding : 파일 이름이 한글일 경우 깨지는 문제 처리-->
<!-- 1024 * 1024 * 10 bytes 10MB -->
<beans:property name="maxUploadSize" value="104857560"></beans:property>
<!-- maxUploadSize : 한 번의 Request로 전달될 수 있는 최대의 크기-->
<!-- 1024 * 1024 * 2bytes 2MB -->
<beans:property name="maxUploadSizePerFile" value="2097152"></beans:property>
<!-- maxUploadSizePerFile : 하나의 파일 최대 크기 -->
<beans:property name="uploadTempDir" value="file:/c:/upload/tmp"></beans:property>
<!-- uploadTempDir : 지정한 크기 이상의 데이터는 uploadTempDir에 임시 파일의 형태로 보관된다. -->
<beans:property name="maxInMemorySize" value="10485756"></beans:property>
<!-- maxInMemorySize : 메모리상에서 유지하는 최대의 크기-->
</beans:bean>
// SampleController 추가
@GetMapping("/exUpload")
public void exUpload() {
log.info("/exUpload!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
exUpload.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta 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>
// SampleController
@PostMapping("/exUploadPost")
public void exUploadPost(ArrayList<MultipartFile> files) {
files.forEach(file -> {
log.info("---------------------------------------");
log.info("name: " + file.getOriginalFilename());
log.info("size : " + file.getSize());
});
}
Controller의 Exception
스프링 MVC에서의 예외 처리 방법
- @ExceptionHandler와 @ControllerAdvice를 이용한 처리
- @ResponseEntity를 이용하는 예외 메세지 구성
@ControllerAdvice
@ControllerAdvice는 AOP
를 이용하는 방식이다. AOP란 간단하게 말하면 핵심적인 로직은 아니지만 프로그램에서 필요한 ‘공통적인 관심사’는 분리하자는 개념이다. AOP 방식을 이용하면 공통적인 예외사항에 대해 별도로 @ControllerAdvice를 이용해서 분리한다.
- 예제 생성
package org.zerock.exception;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import lombok.extern.log4j.Log4j;
@ControllerAdvice
@Log4j
public class CommonExceptionAdvice {
@ExceptionHandler(Exception.class)
public String except(Exception ex, Model model) {
log.error("Exception ........ " + ex.getMessage());
model.addAttribute("exception", ex);
log.error(model);
return "error_page";
}
}
- @ControllerAdvice : 해당 객체가 스프링의 컨트롤러에서 발생하는 예외를 처리하는 존재임을 명시
- @ExceptionHandler : 해당 메서드가 () 안에 들어가는 예외 타입을 처리한다는 의미
- Exception.class : 모든 예외에 대한 처리
만약 특정한 타입의 예외를 다루고 싶다면 구체적인 예외 클래스를 지정해주어야 한다. JSP 화면에서 구체적인 메세지를 보고 싶다면 Model을 이용해서 전달하는 것이 좋다.
servlet-context.xml
<context:component-scan base-package="org.zerock.exception" />
CommonExceptionAdvice의 except()의 리턴값은 문자열이므로 JSP 파일의 경로가 된다. JSP는 error_page.jsp이므로 /WEB-INF/views 폴더 내에 생성한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page session="false" import="java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4><c:out value="${exception.getMessage()}"></c:out></h4>
<ul>
<c:forEach items="${exception.getStackTrace()}" var="stack">
<li><c:out value=${stack}"></c:out></li>
</c:forEach>
</ul>
</body>
</html>
작성 후 일부러 예외가 발생하도록 age에 숫자가 아닌 문자열을 넣어 호출해보면 에러 메세지가 출력된다.
http://localhost:8080/sample/ex04?name=aaa&age=열살&page=9
404 에러 페이지
WAS의 구동 중 가장 흔한 에러와 관련된 HTTP 상태 코드는 ‘404’와 ‘500’dlek. 500 메세지는 ‘Internal Server Error’이므로 @ExceptionHandler를 이용해서 처리되지만 잘못된 URL 호출로 인한 404 에러는 다른 방식으로 처리하는 것이 좋다.
스프링 MVC의 모든 요청은 DispatcherServlet을 이용해서 처리되므로 404도 에러도 같이 처리될 수 있도록 web.xml을 수정한다.
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<!-- 추가 -->
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
// CommonExceptionAdvice
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handler404(NoHandlerFoundException ex) {
return "custom404";
}
custom404.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>해당 URL은 존재하지 않습니다.</h1>
</body>
</html>
브라우저에서 존재하지 않는 URL을 호출하면 custom404.jsp 페이지가 보인다.
위의 방법으로 오픈 소스를 이용하면 404 페이지를 좀 더 예쁘게 꾸밀 수도 있다.
- 참고 : 코드로 배우는 스프링 웹 프로젝트
댓글남기기