실습 내용
1. maven 프로젝트 생성
- maven-archetype-webapp 선택
2. pom.xml에 해당 코드 추가
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
3. org.eclipse.wst.common.project.facet.core.xml 수정
- Navigator -> .settings에 해당 파일 존재
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<fixed facet="wst.jsdt.web"/>
<installed facet="jst.web" version="3.1"/> // 여기 수정
<installed facet="wst.jsdt.web" version="1.0"/>
<installed facet="java" version="1.8"/>
</faceted-project>
- properties -> project Facets에서 dynamic web module이 3.1인지 확인한다.
4. DispatcherServlet을 FrontController로 설정하기
- web.xml 파일에 설정
- javax.servlet.ServletContainerInitializer 사용 - 비교적 덜 사용함
- 서블릿 3.0 스펙 이상에서 web.xml파일을 대신해서 사용할 수 있다.
- org.springframework.web.WebApplicationInitializer 인터페이스를 구현해서 사용
web.xml파일에서 DispatcherServlet 설정하기
xml spring 설정 읽어들이도록 DispathcerServlet설정
- servlet-class가 내가 실제로 동작시킬 클래스를 의미하기 때문에 Spring이 제공하는 클래스 명을 넣어준다.
- DispatcherServlet은 Spring이 제공하고 있기 때문에 실제로 내가 무슨 일을 하고 싶은지에 대한 내용까지는 알 수 없다. 그런 것에 대한 설정을 init-parm으로 할 수 있다.
- 나중에 해당 xml 파일에 실제로 어떤 일들을 해야 될 건지 작성한다.
Java config spring 설정 읽어들이도록 DispathcerServlet설정
- xml이 아닌 자바 config를 이용하여 사용하는 방법이다.
- init-param 부분에 xml 파일이 아닌 자바 클래스 이름이 들어가 있다.
- 즉, xml 파일이 아닌 자바 config 파일을 읽어오고 있다.
- 서블릿 web.xml 설명
- URL 패턴이 /으로 되어있다. 이 부분 때문에 특정한 하나의 요청만 받아들이는 것이 아닌, 모든 요청을 받을 수 있게 된다.
WebApplicationInitializer를 구현해서 설정하기
- Spring MVC는 ServletContainerInitializer를 구현하고 있는 SpringServletContainerInitializer를 제공한다.
- SpringServletContainerInitializer는 WebApplicationInitializer 구현체를 찾아 인스턴스를 만들고 해당 인스턴스의 OnStartup 메소드를 호출하여 초기화한다.
- 즉, Spring MVC는 해당 인터페이스를 구현한 구현체를 찾고 해당 객체의 onStartup 메서드를 이용해 초기화를 하기 때문에 발생될 수 있다.
- 이 방법의 단점은 처음 웹 애플리케이션이 구동되는 시간이 오래 걸릴 수 있다.
- 수업에서는 다루지 않는다.
web.xml 방법의 실습 코드
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</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>
- servlet-class는 이것을 FrontController로 한다는 의미이다.
- 첫번째 pram은 Bean 공장 역할하는 ApplicationContext이다.
- 두번째 pram은 이 뒤에 만드는 WebMvcContextConfiguration.java에서 Dispatcher가 실행될 때 설정들을 읽기 위함이다.
5. Spring MVC 설정
- 앞에서 DispatcherServlet에 대한 설정을 web.xml 등에서 하고, 읽어들어야할 설정은 별도로 한다.
- 해당 설정 파일을 읽어들여서 내부적으로 ApplicationContext를 생성한다.
- kr.or.connect.webmvc.config.WebMvcContextConfiguration
@Configuration
- org.springframework.context.annotation의 @Configuration과 @Bean 코드를 이용하여 스프링 컨테이너에 새로운 빈 객체를 제공할 수 있다.
- config 파일이라는 것을 알려준다.
@EnableWebMvc
- DispatcherServlet의 RequestMappingHandlerMapping, RequestMappingHandlerAdapter, ExceptionHandlerExceptionResolver, MessageConverter 등 Web에 필요한 빈들을 대부분 자동으로 설정해준다.
- xml로 설정의 <mvc:annotation-driven/> 와 동일하다. - 수업에서는 사용하지 않는다.
- 기본 설정 이외의 설정이 필요하다면 WebMvcConfigurerAdapter 를 상속받도록 Java config class를 작성한 후, 필요한 메소드를 오버라이딩 하도록 한다.
소스코드
실습 코드
- 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;
}
}
- basesPackages를 지정해주지 않으면 어느 패키지부터 찾아내야 할지를 몰라서
수행이 안될 수도 있다.
addResourceHandlers
- web.xml 파일에서 설정할 때 url-patern에 /을 넣어서 모든 요청을 이 서블릿이 실행하도록 했다.
- 들어오는 모든 요청 중에는 컨트롤러의 URL이 매핑되어 있는 요청 외에도 CSS, 이미지, 자바스크립트 등도 들어온다.
- 초창기에는 ??.do, ??.x 방식으로 요청을 받아들였지만, 외관상 선호하지 않게되었다.
- 따라서 /js/**, /img**, /css/** 이렇게 시작되는 URL 요청은 디렉토리를 따로 만든 후 해당 디렉토리에서 찾도록 설정해준다.
- 이 부분이 없다면 모두 컨트롤러가 가진 RequestMaping에서 그것들을 찾으려고 하면서 오류를 발생시킨다.
- 파라미터로 전달받은 DefaultServletHandlerConfigurer 객체의 enable이라는 메서드를 호출함으로써 DefalutServletHandler를 사용하도록 한다.
- 매핑정보가 없는 URL 요청은 Spring의 DefaultServletHttpRequestHandler가 처리하도록 한다.
- Spring의 DefaultServletHttpRequestHandler는 WAS의 Defaultservlet에게 해당 일을 넘긴다.
- 그러면 WAS는 DefalutServlet이 static한 자원을 읽어서 보여준다.
addViewControllers
- 특정 URL에 대한 처리를 컨트롤러 클래스를 작성하지 않고 매핑할 수 있도록 한다.
- 여기서는 요청 자체가 /로 들어오면 main이라고 하는 이름의 뷰로 보여주도록 한다.
getInternalResourceViewResolver
- 실제 main이라는 이름만으로는 뷰 정보를 찾아낼 수 없다. 뷰 정보는 해당 메서드에서 설정된 형태로 뷰를 사용한다.
- resolver의 setPrefix, setSuffix로 main을 /WEB-INF/views/main.jsp로 설정하여 해당 파일을 보여달라고 한다.
@ComponentScan
- @ComponentScan을 이용하면 Controller, Service, Repository, Component 어노테이션이 붙은 클래스를 찾아 스프링 컨테이너가 관리하게 된다.
- DefaultAnnotationHandlerMapping과 RequestMappingHandlerMapping구현체는 다른 핸드러 매핑보다 훨씬 더 정교한 작업을 수행한다. 이 두 개의 구현체는 애노테이션을 사용해 매핑 관계를 찾는 매우 강력한 기능을 가지고 있다. 이들 구현체는 스프링 컨테이너 즉 애플리케이션 컨텍스트에 있는 요청 처리 빈에서 RequestMapping애노테이션을 클래스나 메소드에서 찾아 HandlerMapping객체를 생성하게 된다.
- HandlerMapping은 서버로 들어온 요청을 어느 핸들러(컨트롤러)로 전달할지 결정하는 역할을 수행한다.
- DefaultAnnotationHandlerMapping은 DispatcherServlet이 기본으로 등록하는 기본 핸들러 맵핑 객체이고, RequestMappingHandlerMapping은 더 강력하고 유연하지만 사용하려면 명시적으로 설정해야 한다.
6. Controller(Handler) 클래스 작성하기
- @Controller를 클래스 위에 붙인다.
- 맵핑을 위해 @RequestMapping을 클래스나 메소드에서 사용한다.
- 요청이 들어왔을 때 어떤 URL로 들어온 요청인지를 알아내서 실제로 처리해야되는 컨트롤러가 뭔지, 그 컨트롤러에서 구현하고 있는 메서드가 뭔지를 알아내기 위해 필요하다.
@RequestMapping
- Http 요청과 이를 다루기 위한 Controller의 메소드를 연결하는 어노테이션
- 즉, 어떤 요청이 들어왔을 때 누가 실행될 것인지 알려주는 것이다.
Http Method 와 연결하는 방법
- @RequestMapping(value="/users", method=RequestMethod.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")
- @RequestMapping(method = RequestMethod.GET, consumes = "application/json")
- @RequestMapping(method = RequestMethod.GET, produces = "application/json")
실습 코드
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를 붙여줘야 한다.
- 파라미터
@RequestParam(name = "value1", required = true) int value1
부분의 의미는 @RequestParam에서 name이 value1값으로 들어오는데, 해당 메서드 내에서 int형 value1이라는 이름으로 사용한다는 뜻이다.
- HttpServletRequest는 종속되는 문제가 있기 때문에 Spring이 제공하는 ModelMap이라는 객체에 넣어준다.
- Spring이 이 부분을 알아서 request scope에 매핑해준다. 그러면 그냥 가져다가 사용하면 된다.
- 다양한 타입이 존재하므로 ModelAndView 등 알맞는 것을 사용하면 된다.
- "key", value 형태로 넣어준다.
@RequestMapping
- 실습 1번은 get, 실습 2번은 post이므로 각각 @GetMapping, @PostMapping을 사용한다.
- 두 어노테이션은 각각 /plusform, /plus을 URL에 붙여서 입력했을 때 실행된다.
- 메서드 부분에는 view name을 어떻게 반환받을 지 작성한다.
- ModelAndView라는 객체를 리턴할 수 있고, String 타입으로 간단하게 뷰 이름만 리턴할 수 있다.
- 이 예제는 간단하므로 String을 사용한다.
- 메서드 이름은 아무거나 해도 상관없지만 가급적 연관된 것으로 한다.
- 리턴된 뷰 이름은 WebMvcContextConfiguration.java의 @Bean에서 /WEB-INF/views/뷰이름.jsp로 변환된다.
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 객체
- HttpServletRequest, 세션과 같은 것들을 사용하려면, 해당 부분들을 선언하면 된다.
Spring MVC가 지원하는 메소드 인수 애노테이션
@RequestParam
@RequestHeader
@RequestBody
@RequestPart
@ModelAttribute
@PathVariable
@CookieValue
@RequestParam
- Mapping된 메소드의 Argument에 붙일 수 있는 어노테이션
- @RequestParam의 name에는 http parameter의 name과 맵핑된다.
- 여기서 HTML form 태그인 input의 name과 매핑된다.
- @RequestParam의 required는 필수인지 아닌지 판단한다.
@ModelAttibute
- @RequestParam은 값을 하나하나 직접 넘겨주었지만, dto에 담아서 한번에 넘기는 방법도 있다.
- 해당 파라미터 부분에 @ModelAtrribute dto클래스명 dto변수명을 넣어주면 된다.
- 실습2
@Controller
public class UserController {
@RequestMapping(path="/regist", method=RequestMethod.POST)
public String regist(@ModelAttribute User user) {
System.out.println(user);
return "regist";
}
}
@PathVariable
- URL path에서 '?변수명=값'의 형태로 값을 넘겨오는 경우가 있다. 그럴 때 값을 받기 위해 사용한다.
- @RequestMapping의 path에 변수명을 입력받기 위한 place holder가 필요하다.
- place holder의 이름과 PathVariable의 name이 같으면 mapping 된다.
- 즉, 사용자가 넣어준 이름과 메서드 파라미터에 넣은 변수명이 일치해야한다.
- required 속성은 default true 임
- 요청 정보의 헤더 정보를 읽어들 일 때 사용
- @RequestHeader(name="헤더명") String 변수명
- @PathVariable, @RequestHeader 실습3
@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();
model.addAttribute("id", id);
model.addAttribute("userAgent", userAgent);
model.addAttribute("path", path);
return "goodsById";
}
}
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<?>
기타 리턴 타입
7. view
실습 코드
- /WEB-INF/views 위치에 실습 1번의 plusform jsp 파일과 실습 2번의 plusResult jsp 파일을 생성한다.
- plusForm.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 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>
<%@ 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>${value1 } 더하기 ${value2 }(은/는) ${result }입니다.
</body>
</html>
index.jsp
- 웹 어플리케이션은 주소가 없으면 index로 시작하는 파일을 찾도록 기본 설정 되어있다.
EL 인식이 안될 경우
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
// 해당 코드로 변경
<?xml version="1.0" encoding="UTF-8"?>
- 위의 코드로 되어있다면 3.1 버전이 아닌 2.3 서블릿 버전을 인식하게되어 EL이 인식이 안된다.
- 아래 코드로 수정한 다음, 서버 삭제 후 실행한다.
- 그래도 안된다면 project 메뉴 -> clean을 실행해본다.
- eclipse가 내부적으로 잘못 가지고 있는 설정들을 정리해준다.
한글이 깨질 경우
- web.xml의 web-app 태그 안에 추가 후 maven update
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>