<3. Spring MVC 실습>
http://www.edwith.org/boostcourse-web
전에 했던 실습 순서를 참고하여, Maven 프로젝트 생성 후 pom.xml에 라이브러리를 추가하고, Servlet 버전을 3.1로 올려준다.
Spring MVC를 사용하기 위해 아래와 같은 순서로 설정을 추가한 후 실습을 진행한다.
- DispatcherServlet을 FrontController로 설정해 주기.
세 가지 방법이 존재한다.
web.xml 파일에 설정 (가장 많이 사용되는 방법이다.)
javax.servlet.ServletContainerInitializer 사용
- 서블릿 3.0 스펙 이상에서 web.xml파일을 대신해서 사용할 수 있다. => 이번 실습에서는 사용하지 않는다.
org.springframework.web.WebApplicationInitializer 인터페이스를 구현해서 사용
- web.xml파일에서 DispatcherServlet 설정하기
- xml을 이용하는 방법
servlet-name은 servlet mapping이 가지고 있는 servlet-name과 일치하면 된다.
DispatcherServlet을 이용할 것이므로 반드시 패키지 명을 포함해서 클래스명을 아래와 같이 적어준다.
WebMVCContextConfig.xml 파일 안에 어떤 일을 할 것인지 알려줄 것이다.
- 자바 config를 이용하는 방법. 이 방법으로 실습을 진행한다.
init-param 부분에 xml이 아니라 자바 config 파일을 넣어준 것을 알 수 있다.
여기서 알아야 할 것은, 요청이 들어올 경우 Servlet이었을 경우에 url-pattern에 원하는 URL 주소를 넣고 이 부분에 요청이 들어오면 실제 servlet-name과 같은 name으로 매핑되어있는 servlet-class를 실행한다는 것!
/ 라고만 되어있으면 모든 요청을 다 받겠다는 뜻이다.
- WebApplicationInitializer를 구현해서 설정하는 방법
Spring MVC는 ServletContainerInitializer를 구현하고 있는 SpringServletContainerInitializer를 제공한다.
SpringServletContainerInitializer는 WebApplicationInitializer 구현체를 찾아 인스턴스를 만들고 해당 인스턴스의 onStartup 메소드를 호출하여 초기화한다.
아래와 같이 설정하지만, 이번 실습에서는 다루지 않는다.
- Spring MVC 설정
DispatcherServlet에 대한 설정은 Web.xml에서 하고 읽어들여야 할 설정은 별도로 자바 Config에 설정한다.
위 어노테이션에 관한 설명이다.
@Configuration
@EnableWebMvc
DispatcherServlet의 RequestMappingHandlerMapping, RequestMappingHandlerAdapter, ExceptionHandlerExceptionResolver, MessageConverter 등 Web에 필요한 빈들을 대부분 자동으로 설정해준다.
xml로 절정했을 경우 <mvc:annotation-driven/> 와 동일하다.
기본 설정 이외의 설정이 필요하다면 WebMvcConfigurerAdapter 를 상속받도록 Java config class를 작성한 후, 필요한 메소드를 오버라이딩 하도록 한다.
(+) 추가로, WebMvcConfigurationSupport 소스코드 살펴보기
@ComponentScan
ComponentScan 애노테이션을 이용하면 Controller, Service, Repository, Component 애노테이션이 붙은 클래스를 찾아 스프링 컨테이너가 관리하게 된다.
DefaultAnnotationHandlerMapping과 RequestMappingHandlerMapping구현체는 다른 핸드러 매핑보다 훨씬 더 정교한 작업을 수행한다. 이 두 개의 구현체는 애노테이션을 사용해 매핑 관계를 찾는 매우 강력한 기능을 가지고 있다. 이들 구현체는 스프링 컨테이너 즉 애플리케이션 컨텍스트에 있는 요청 처리 빈에서 RequestMapping 애노테이션을 클래스나 메소드에서 찾아 HandlerMapping객체를 생성하게 된다.
HandlerMapping은 서버로 들어온 요청을 어느 핸들러로 전달할지 결정하는 역할을 수행한다.
DefaultAnnotationHandlerMapping은 DispatcherServlet이 기본으로 등록하는 기본 핸들러 맵핑 객체
RequestMappingHandlerMapping은 더 강력하고 유연하지만 사용하려면 명시적으로 설정해야 한다.
WebMvcConfigurerAdapter
org.springframework.web.servlet.config.annotation. WebMvcConfigurerAdapter
@EnableWebMvc 를 이용하면 기본적인 설정이 모두 자동으로 되지만, 기본 설정 이외의 설정이 필요할 경우 해당 클래스를 상속 받은 후, 메소드를 오버라이딩 하여 구현한다.
- Controller(Handler) 클래스 작성하기
- @RequestMapping
Http 요청과 이를 다루기 위한 Controller 의 메소드를 연결하는 어노테이션
Spring은 아래와 같은 방법으로 RequestMapping로 요청하는 것을 제공한다.
Http Method와 연결하는 방법
- @RequestMapping(value="/users", method=RequestMethod.POST)
=>/users로 들어왔을 때 실행한다. POST 방식으로 들어왔을 때.
- From Spring 4.3 version 부터는 아래와 같은 방법으로 사용한다.
@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
Http 특정 해더와 연결하는 방법
- @RequestMapping(method = RequestMethod.GET, headers = "content-type=application/json")
Http Parameter와 연결하는 방법
- @RequestMapping(method = RequestMethod.GET, params = "type=raw")
Content-Type Header 와 연결하는 방법
- @RequestMapping(method = RequestMethod.GET, consumes = "application/json")
Accept Header와 연결하는 방법
- @RequestMapping(method = RequestMethod.GET, produces = "application/json")
실습
WebMvcContextConfiguration.java
package kr.or.connect.mvcexam.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "kr.or.connect.mvcexam.controller" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(31556926);
registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
System.out.println("addViewControllers가 호출됩니다. ");
registry.addViewController("/").setViewName("main");
}
@Bean
public InternalResourceViewResolver getInternalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<display-name>Spring JavaConfig Sample</display-name>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>kr.or.connect.mvcexam.config.WebMvcContextConfiguration</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
main.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>main page~~~!!</h1>
</body>
</html>
- Controller작성 실습 1/3
웹 브라우저에서 http://localhost:8080/mvcexam/plusform 이라고 요청을 보 내면 서버는 웹 브라우저에게 2개의 값을 입력받을 수 있는 입력 창과 버튼이 있는 화면을 출력한다.
웹 브라우저에 2개의 값을 입력하고 버튼을 클릭하면 http://localhost:8080/mvcexam/plus URL로 2개의 입력값이 POST방식으로 서버에게 전달한다. 서버는 2개의 값을 더한 후, 그 결과 값을 JSP에게 request scope으로 전달하여 출력한다.
* Spring MVC가 지원하는 Controller메소드 인수 타입
javax.servlet.ServletRequest
javax.servlet.http.HttpServletRequest
org.springframework.web.multipart.MultipartRequest
org.springframework.web.multipart.MultipartHttpServletRequest
javax.servlet.ServletResponse
javax.servlet.http.HttpServletResponse
javax.servlet.http.HttpSession
org.springframework.web.context.request.WebRequest
org.springframework.web.context.request.NativeWebRequest
java.util.Locale
java.io.InputStream
java.io.Reader
java.io.OutputStream
java.io.Writer
javax.security.Principal
java.util.Map
org.springframework.ui.Model
org.springframework.ui.ModelMap
org.springframework.web.multipart.MultipartFile
javax.servlet.http.Part
org.springframework.web.servlet.mvc.support.RedirectAttributes
org.springframework.validation.Errors
org.springframework.validation.BindingResult
org.springframework.web.bind.support.SessionStatus
org.springframework.web.util.UriComponentsBuilder
org.springframework.http.HttpEntity<?>
Command 또는 Form 객체
* Spring MVC가 지원하는 메소드 인수 애노테이션
@RequestParam
@RequestHeader
@RequestBody
@RequestPart
@ModelAttribute
@PathVariable
@CookieValue
@RequestParam
Mapping된 메소드의 Argument에 붙일 수 있는 어노테이션
@RequestParam의 name에는 http parameter의 name과 멥핑
@RequestParam의 required는 필수인지 아닌지 판단
@PathVariable
@RequestMapping의 path에 변수명을 입력받기 위한 place holder가 필요함
place holder의 이름과 PathVariable의 name 값과 같으면 mapping 됨
required 속성은 default true
@RequestHeader
Spring MVC가 지원하는 메소드 리턴 값
org.springframework.web.servlet.ModelAndView
org.springframework.ui.Model
java.util.Map
org.springframework.ui.ModelMap
org.springframework.web.servlet.View
java.lang.String
java.lang.Void
org.springframework.http.HttpEntity<?>
org.springframework.http.ResponseEntity<?>
기타 리턴 타입
* 실습 코드
plusForm.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>
<form method="post" action="plus">
value1 : <input type="text" name="value1"><br>
value2 : <input type="text" name="value2"><br>
<input type="submit" value="확인">
</form>
</body>
</html>
plusResult.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>
${value1} 더하기 ${value2} (은/는) ${result} 입니다.
</body>
</html>
PlusController.java
package kr.or.connect.mvcexam.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class PlusController {
@GetMapping(path = "/plusform")
public String plusform() {
return "plusForm";
}
@PostMapping(path = "/plus")
public String plus(@RequestParam(name = "value1", required = true) int value1,
@RequestParam(name = "value2", required = true) int value2, ModelMap modelMap) {
int result = value1 + value2;
modelMap.addAttribute("value1", value1);
modelMap.addAttribute("value2", value2);
modelMap.addAttribute("result", result);
return "plusResult";
}
}
- Controller작성 실습 2/3
http://localhost:8080/mvcexam/userform 으로 요청을 보내면 이름, email, 나이를 물어보는 폼이 보여진다.
폼에서 값을 입력하고 확인을 누르면 post방식으로 http://localhost:8080/mvcexam/regist 에 정보를 전달하게 된다.
regist에서는 입력받은 결과를 콘솔 화면에 출력한다.
- Controller작성 실습 3/3
http://localhost:8080/mvcexam/goods/{id} 으로 요청을 보낸다.
서버는 id를 콘솔에 출력하고, 사용자의 브라우저 정보를 콘솔에 출력한다.
서버는 HttpServletRequest를 이용해서 사용자가 요청한 PATH정보를 콘솔에 출력한다.
* 실습 코드
userform.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>
<form method="post" action="regist">
name : <input type="text" name="name"><br>
email : <input type="text" name="email"><br>
age : <input type="text" name="age"><br>
<input type="submit" value="확인">
</body>
</html>
UserController.java
package kr.or.connect.mvcexam.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import kr.or.connect.mvcexam.dto.User;
@Controller
public class UserController {
@RequestMapping(path="/userform", method=RequestMethod.GET)
public String userform() {
return "userform";
}
@RequestMapping(path="/regist", method=RequestMethod.POST)
public String regist(@ModelAttribute User user) {
System.out.println("사용자가 입력한 user 정보입니다. 해당 정보를 이용하는 코드가 와야합니다.");
System.out.println(user);
return "regist";
}
}
User.java
package kr.or.connect.mvcexam.dto;
public class User {
private String name;
private String email;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", email=" + email + ", age=" + age + "]";
}
}
regist.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>
<h2>등록되었습니다.</h2>
</body>
</html>
goodsById.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>
id : ${id } <br>
user_agent : ${userAgent }<br>
path : ${path }<br>
</body>
</html>
GoodsController.java
package kr.or.connect.mvcexam.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
@Controller
public class GoodsController {
@GetMapping("/goods/{id}")
public String getGoodsById(@PathVariable(name="id") int id,
@RequestHeader(value="User-Agent", defaultValue="myBrowser") String userAgent,
HttpServletRequest request,
ModelMap model
) {
String path = request.getServletPath();
System.out.println("id : " + id);
System.out.println("user_agent : " + userAgent);
System.out.println("path : " + path);
model.addAttribute("id", id);
model.addAttribute("userAgent", userAgent);
model.addAttribute("path", path);
return "goodsById";
}
}