2장. 스프링 MVC 설정
ch05. 스프링 MVC의 기본 구조
- web.xml
- Tomcat 구동과 관련된 설정
- root-context.xml, servlet-context.xml
- 스프링과 관련된 설정
web.xml 설정
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 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> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
- <context-param>
- root-context.xml의 경로 설정
- <listener>
- 스프링 MVC의 ContextLoaderListener 등록
- ContextLoaderListener는 해당 웹 어플리케이션 구동 시 같이 동작하므로 해당 프로젝트를 실행하면 가장 먼저 로그를 출력
- org.springframework.web.servlet.DispatcherServlet
- 스프링 MVC의 구조에서 가장 핵심적인 역할을 하는 클래스
- DispatcherServlet
- root-context.xmㅣ에 정의된 객체(Bean)들은 스프링의 영역(context)안에 생성되고, 객체들 간이 의존성이 처리됨
- root-context.xml이 처리된 후 스프링 MVC에서 사용하는 DispatcherServlet이라는 서블릿과 관련된 설정이 동작됨
- servlet-context.xml
- 내부적으로 웹 관련 처리의 준비작업을 진행하는데 사용하는 파일
- DispatcherServlet에서 XmlWebApplicationContext를 이용해서 servlet-context.xml을 로딩하고 해석함
- 이 과정에서 등록된 객체(Bean)들은 기존에 만들어진 객체(Bean)들과 같이 연동됨
- <context-param>
- 스프링 MVC의 기본 사상
- model2 방식에서는 Servlet/JSP 기술을 사용
- 스프링 MVC는 내부적으로 Servlet/JSP 처리
- HttpServletRequest/HttpServletResponse 등과 같이 Servlet/JSP API 사용 필요성이 현저히 줄어듬(스프링이 중간 역할을 하여 코드를 작성하지 않고도 원하는 기능 구현 가능)
- 스프링 MVC의 기본 구조
- DispatcherServlet
- 사용자의 Request는 Front-Controller인 DispatcherServlet을 통해서 처리
- 생성된 프로젝트의 web.xml을 보면 모든 Request를 DispatcherServlet이 받도록 처리
- HandlerMapping / HandlerAdapter
- HandlerMapping은 Request의 처리를 담당하는 컨트롤러를 찾기 위해서 존재
- HandlerMapping 인터페이스를 구현한 여러 객체들 중 RequestMappingHandlerMap-ping 같은 경우 개발자가 @RequestMapping 어노테이션이 적용된 것을 기준으로 판단
- 적절한 컨트롤러가 찾아졌다면 HandlerMapping을 이용하여 해당 컨트롤러를 동작
- Controller
- Controller는 개발자가 작성하는 클래스로 실제 Request를 처리하는 로직을 작성
- View에 전달해야 하는 데이터는 주로 Model이라는 객체에 담아서 전달
- Controller는 다양한 타입의 결과를 반환하는데 이에 대한 처리는 ViewResolver를 이용
- ViewResolver
- ViewResolver는 Controller가 반환한 결과를 어떤 View를 통해서 처리하는 것이 좋을지 해석
- 가장 흔하게 사용하는 설정은 servlet-context.xml에 정의된 Inter-nalResourceViewResolver
- View
- View는 실제로 응답 보내야 하는 데이터를 Jsp 등을 이용해서 생성하는 역할
- 만들어진 응답은 DispatcherServlet을 통해 전송
- DispatcherServlet
ch06. 스프링 MVC의 Controller
- 스프링 MVC의 Controller 특징
- HttpServletRequest, HttpServletResponse를 거의 사용할 필요 없이 필요한 기능 구현
- 다양한 타입의 파라미터 처리, 다양한 타입의 리턴 타입 사용 가능
- Get / Post 방식 등 전송 방식에 대한 처리를 어노테이션으로 처리 가능
- 상속/인터페이스 방식 대신에 어노테이션만으로도 필요한 설정 가능
@Controller, @RequestMapping
@RequestMapping은 현재 클래스의 모든 메서드들의 기본적인 URL경로가 됨
(/sample/*에 대한 URL은 모두 SampleController에서 처리 됨)SampleController.java
package org.zerock.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import lombok.extern.log4j.Log4j; @Controller @RequestMapping("/sample/*") @Log4j public class SampleController { @RequestMapping("") public void basic() { log.info("basic get..................."); } }
SampleController 클래스는 스프링의 객체(Bean)으로 자동 등록
(servlet-context.xml에 설정해두었기 때문)<context:component-scan base-package="org.zerock.controller" />
@RequestMapping의 변화
@RequestMapping의 경우 몇 가지의 속성 추가 가능
가장 많이 사용하는 속성은 method 속성
Method 속성은 흔히 GET / POST 방식을 구분해서 사용할 때 이용 함
스프링 4.3버젼부터는 @RequestMapping을 줄여서 사용할 수 있는 @GetMapping, @PostMapping 축약형 표현 가능
일반적인 경우에는 GET / POST 방식만을 사용하지만 최근에는 PUT, DELETE 방식 등도 점점 많이 사용 중
SampleController.java
@RequestMapping(value = "/basic", method = { RequestMethod.GET, RequestMethod.POST }) public void basicGet() { log.info("basic get..................."); } @GetMapping("/basicOnlyGet") public void basicGet2() { log.info("basic get only get..................."); }
- Lombok의 @Data 어노테이션을 이용하여 처리
(getter/setter, equals(), toStirng()등의 메서드를 자동으로 생성) - SampleDTO.java
package org.zerock.domain;
import lombok.Data;
@Data
public class SampleDTO {
private String name;
private int age;
}
Controller의 파라미터 수집
Controller를 작성할 때 가장 편리한 기능은 파라미터가 자동으로 수집되는 기능
이 기능을 통해 매번 request.getParameter()를 이용하는 불편함을 없앨 수 있음
SampleController의 경로가 '/sample/*'이므로 ex01을 호출하는 경로는 '/sample/ex01'
필요한 파라미터를 URL뒤에 '?name=AAA&age=10'과 같은 형태로 추가해서 호출
- SampleController.java
package org.zerock.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.zerock.domain.SampleDTO; import lombok.extern.log4j.Log4j; @Controller @RequestMapping("/sample/*") @Log4j public class SampleController { @RequestMapping("") public void basic() { log.info("basic get..................."); } @RequestMapping(value = "/basic", method = { RequestMethod.GET, RequestMethod.POST }) public void basicGet() { log.info("basic get..................."); } @GetMapping("/basicOnlyGet") public void basicGet2() { log.info("basic get only get..................."); } @GetMapping("/ex01") public String ex01(SampleDTO dto) { log.info("" + dto); return "ex01"; } }
만일 기본 자료형이나 문자열 등을 이용한다면 파라미터의 타입만을 맞게 선언해주는 방식을 사용할 수 있음
SampleController.java
@GetMapping("/ex02") public String ex02(@RequestParam("name") String name, @RequestParam("age") int age) { log.info("name: " + name); log.info("age: " + age); return "ex02"; }
List
스프링은 파라미터의 타입을 보고 객체를 생성하므로 파라미터의 타입은 List<>와 같이 인터페이스 타입이 아닌 실제적인 클래스 타입으로 지정함
같은 이름의 파마리터가 여러 개 전달되더라도 ArrayList<String>이 생성되어 자동으로 수집
(호출 예시 /sample/ex02List?ids=111&ids=222&ids=333)- SampleController.java
@GetMapping("/ex02List") public String ex02List(@RequestParam("ids") ArrayList<String> ids) { log.info("ids: " + ids); return "ex02List"; }
배열
배열도 List와 동일하게 처리
SampleController.java
@GetMapping("/ex02Array") public String ex02Array(@RequestParam("ids") String[] ids) { log.info("array ids: " + Arrays.deepToString(ids)); return "ex02Array"; }
객체 리스트
전달하는 데이터가 SampleDTO와 같이 객체 타입이고, 여러 개를 처리해야할 때 유용
SampleDTOList.java
package org.zerock.domain; import java.util.ArrayList; import java.util.List; import lombok.Data; @Data public class SampleDTOList { private List<SampleDTO> list; public SampleDTOList() { list = new ArrayList<>(); } }
SampleController.java
@GetMapping("/ex02Bean") public String ex02Bean(SampleDTOList list) { log.info("list dtos: " + list); return "ex02Bean"; }
파라미터는 [인덱스] 형식으로 전달해서 처리
(호출 예시 : /sample/ex02Bean?list[0].name=aaa&list[1].name=bbb)톰캣 버젼에 따라 [] 특수문자를 허용하지 않을 수 있음
JavaScript를 이용하는 경우 encodeURIComponent()와 같은 방법으로 해결할 수 있음
임시방편으로 %5B, %5D로 처리 가능
바인딩
- 파라미터의 수집을 다른 용어로는 Binding이라고 함
- 변환이 가능한 데이터는 자동으로 변환되지만 경우에 따라서 파라미터를 변환해서 처리해야하는 경우가 존재함
ex) 2018-01-01과 같이 문자열로 전달된 데이터를 java.util.Date 타입으로 변환하는 작업
@initBinder
TodoDTO.java
package org.zerock.domain; import java.util.Date; import lombok.Data; @Data public class TodoDTO { private String title; private Date dueDate; }
SampleController.java
@InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd"); binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(dateFormat, false)); } @GetMapping("/ex03") public String ex03(TodoDTO todo) { log.info("todo: " + todo); return "ex03"; }
호출예시 : /sample/ex03?title=test&dueData=2018-01-01
@DateTimeFormat
파라미터로 사용되는 인스턴스 변수에 적용하여 변환
DateTimeFormat 사용 시 @InitBinder는 필요하지 않음
TodoDTO.java
package org.zerock.domain; import java.util.Date; import org.springframework.format.annotation.DateTimeFormat; import lombok.Data; @Data public class TodoDTO { private String title; @DateTimeFormat(pattern = "yyyy/MM/dd") private Date dueDate; }
SampleController.java
@InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd"); binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(dateFormat, false)); } @GetMapping("/ex03") public String ex03(TodoDTO todo) { log.info("todo: " + todo); return "ex03"; }
Model
Model 객체는 JSP에 컨트롤러에서 생성된 데이터를 담아서 전달하는 역할
JSP와 같은 뷰(View)에 전달해야 하는 데이터를 담아서 전송
SampleController.java
@GetMapping("/ex04") public String ex04(SampleDTO dto, @ModelAttribute("page") int page) { log.info("dto: " + dto); log.info("page: " + page); return "ex04"; }
ex04.jsp 페이지에 전달
RedirectAttributes
Model 타입과 더불어서 스프링 MVC가 자동으로 전달해주는 타입 중 하나
일회성으로 데이터를 전달하는 용도
기존 Servlet에서는 response.sendRedirect()와 동일
Servlet
response.sendRedirect("/home?name=aaa&age=10");
Spring MVC
rttr.addFlashAttribute("name", "AAA"); rttr.addFlashAttribute("age", 10); return "redirect:/"
Controller의 리턴 타입
스프링 MVC의 구조가 기존의 상속과 인터페이스에서 어노테이션을 사용하는 방식으로 변한 이후에 가장 큰 변화 중 하나는 리턴 타입이 자유로워짐
리턴 타입의 종류
String
- jsp를 이용하는 경우에는 jsp 파일의 경로와 파일이름을 나타내기 위해서 사용
- 키워드를 붙여서 사용 가능
- redirect : 리다이렉트 방식으로 처리하는 경우
- forward : 포워드 방식으로 처리하는 경우
void
- 호출하는 URL과 동일한 이름의 jsp를 의미
VO, DTO 타입
주로 JSON 타입의 데이터를 만들어서 반환하는 용도로 사용
jackson-databind 라이브러리 필요 (스프링3 이전 버전은 별도의 Converter 작성 필요)
pom.xml
<!-- 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>
ResponseEntity 타입
response할 때 Http 헤더 정보와 내용을 가공하는 용도로 사용
HttpServletRequest나 HttpServletResponse를 직접 핸들링하지않고 ResponseEntity를 통해 처리 가능
SampleController.java
@GetMapping("/ex07") public ResponseEntity<String> 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); }
Model, ModelAndView
- Model로 데이터를 반환하거나 화면까지 같이 지정하는 경우 사용 (최근에는 많이 사용하지 않음)
HttpHeaders
- 응답에 내용 없이 Http 헤더 메시지만 전달하는 용도로 사용
파일업로드 처리
pom.xml
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>
servlet-context.xml은 스프링 MVC의 특정한 객체(Bean)를 설정함
다른 객체(Bean)를 설정하는 것과 달리 파일 업로드의 경우, 반드시 id 속성의 값을 'multipartResolver'로 정확하게 지정해야함
- maxUploadSize : 한 번의 Request로 전달될 수 있는 최대의 크기
- maxUploadSizePerFile : 하나의 파일 최대 크기
- maxInMemorySize : 메모리상에서 유지하는 최대의 크기
- uploadTempDir : 지정된 크기보다 큰 파일을 업로드할 경우, 임시저장 경로
- defaultEncoding : 업로드 파일명이 한글일 경우 깨지는 문제 처리
servlet-context.xml
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <beans:property name="defaultEncoding" value="utf-8"></beans:property> <!-- 1024 * 1024 * 10 bytes 10MB --> <beans:property name="maxUploadSize" value="104857560"></beans:property> <!-- 1024 * 1024 * 2 bytes 2MB --> <beans:property name="maxUploadSizePerFile" value="2097152"></beans:property> <beans:property name="uploadTempDir" value="file://Users/tongbook/upload/tmp"></beans:property> <beans:property name="maxInMemorySize" value="10485756"></beans:property> </beans:bean>
exUpload 페이지 이동
SampleController.java
@GetMapping("/exUpload") public void exUpload() { log.info("/exUpload...."); }
exUpload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="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>
업로드한 파일 정보 확인
SampleController.java
@PostMapping("/exUploadPost") public void exUploadPost(ArrayList<MultipartFile> files) { log.info("/sample/exUploadPost...."); files.forEach(file -> { log.info("--------------------"); log.info("name: " + file.getOriginalFilename()); log.info("size: " + file.getSize()); }); }
Controller의 Exception 처리
- @ExceptionHandler와 @ControllerAdvice를 이용한 처리
- @ResponseEntity를 이용하는 예외 메시지 구성
@ControllerAdvice
AOP(Aspect-Oriented-Programming)을 이용한 방식
CommonExceptionAdvice.java
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 클래스 타입 지정 가능
예제의 경우 Exception.class를 지정하였으므로 모든 예외에 대한 처리가 except()만을 이용해서 처리할 수 있음servlet-context.xml
<context:component-scan base-package="org.zerock.exception" />
error_page.jsp
<%@ 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 PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; 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>
404 에러 페이지
스프링 MVC의 모든 요청은 DispatcherServlet을 이용해서 처리되므로 404에러도 같이 처리할 수 있도록 web.xml을 수정
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> <!-- 404에러 페이지 설정 --> <init-param> <param-name>throwExceptionIfNoHandlerFound</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
CommonExceptionAdvice.java
@ExceptionHandler(NoHandlerFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public String handle404(NoHandlerFoundException ex) { return "custom404"; }
custom404.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-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>해당 URL은 존재하지 않습니다.</h1> </body> </html>
'개발 서적 > 코드로 배우는 스프링 웹 프로젝트' 카테고리의 다른 글
6장. 파일 업로드 처리 (0) | 2022.01.22 |
---|---|
5장. AOP와 트랜잭션 (0) | 2022.01.22 |
4장. REST 방식과 Ajax를 이용하는 댓글 처리 (0) | 2022.01.22 |
3장. 기본적인 웹 게시물 관리물 관리 (0) | 2022.01.22 |
1장. 스프링 개발 환경 구축 (0) | 2022.01.21 |