Spring MVC를 이용한 웹 페이지 작성 실습

실습 내용

  • 웹 브라우저에서 http://localhost:8080/mvcexam/plusform 이라고 요청을 보 내면 서버는 웹 브라우저에게 2개의 값을 입력받을 수 있는 입력 창과 버튼이 있는 화면을 출력한다.
  • 웹 브라우저에 2개의 값을 입력하고 버튼을 클릭하면 http://localhost:8080/mvcexam/plus URL로 2개의 입력값이 POST방식으로 서버에게 전달한다. 서버는 2개의 값을 더한 후, 그 결과 값을 JSP에게 request scope으로 전달하여 출력한다.

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>
    <!-- Servlet JSP JSTL -->
    <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로 설정하기

  • 세가지 방법이 있다.
  1. web.xml 파일에 설정
  2. javax.servlet.ServletContainerInitializer 사용 - 비교적 덜 사용함
  • 서블릿 3.0 스펙 이상에서 web.xml파일을 대신해서 사용할 수 있다.
  1. 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 방법의 실습 코드

  • 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);
  }

  // default servlet handler를 사용하게 합니다.
  @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에서 그것들을 찾으려고 하면서 오류를 발생시킨다.

configureDefaultServletHandling

  • 파라미터로 전달받은 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")

Content-Type Header 와 연결하는 방법

  • @RequestMapping(method = RequestMethod.GET, consumes = "application/json")

Accept Header 와 연결하는 방법

  • @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

  • 요청 정보의 헤더 정보를 읽어들 일 때 사용
  • @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>
  • plusResult.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>${value1 } 더하기 ${value2 }(은/는) ${result }입니다.
</body>
</html>

index.jsp

  • 웹 어플리케이션은 주소가 없으면 index로 시작하는 파일을 찾도록 기본 설정 되어있다.

EL 인식이 안될 경우

  • 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" >

// 해당 코드로 변경
<?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>

좋은 웹페이지 즐겨찾기