스프링 MVC
guestbook 디렉터리 구조를 Spring MVC에 맞게 아래와 같이 바꾼다.
webapp/resources/
webapp/resources/는 웹 애플리케이션의 정적 요소를 위해 만들었다. 웹 애플리케이션에서 정적인 요소란 이미지, 스타일 시트, 자바스크립트, HTML 파일을 말한다.
webapp/WEB-INF/views/
webapp/WEB-INF/views/는 jsp 파일을 위해 만들었다. 그래서 guestbook.jsp 파일을 webapp/에서 webapp/WEB-INF/views/로 옮겼다.
스프링 MVC를 적용하기 위해선 다음 과정이 필요하다.
- pom.xml에 스프링 의존성 추가
- web.xml에 디스패처 서블릿 설정을 추가
- [비영어권 사이트는 web.xml에 인코딩 필터 추가]
- 디스패처 서블릿 이름을 기반으로 스프링 설정 파일 생성 (예, 디스패처 서블릿 이름-servlet.xml)
pom.xml
<properties> <!-- omit --> <spring.version>5.3.33</spring.version> </properties> <!-- omit --> <!-- [START Spring_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>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- [END Spring_Dependencies] --> <!-- omit -->
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- [START Objectify] --> <!-- omit --> <!-- [END Objectify] --> <servlet> <servlet-name>guestbook</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>guestbook</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
- 다국어 사이트를 위해 요청 문자열을 UTF-8로 인코딩하는 필터를 추가
- 디스패처 서블릿을 guestbook이란 이름으로 추가했고 '/'로 매핑
- welcome-file-list 제거
디스패처 서블릿 이름이 guestbook 이므로 스프링 설정 파일을 guestbook-servlet.xml란 이름으로 web.xml 파일이 있는 위치에 생성한다.
guestbook-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:resources mapping="/resources/**" location="/resources/" /><!--1.정적 콘텐츠 요청 처리--> <mvc:annotation-driven /><!--2.애너테이션 기반으로 스프링 구동--> <context:component-scan base-package="net.java_school.guestbook" /><!--3.컴포넌트 스캔--> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--4.뷰 리졸버--> <property name="viewClass"> <value>org.springframework.web.servlet.view.JstlView</value> </property> <property name="prefix"> <value>/WEB-INF/views/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> </beans>
- webapp/resources 나 그 하위 디렉터리에 있는 정적 콘텐츠 (스타일 시트, 이미지, 자바스크립트 등등)에 대한 요청을 처리하기 위한 설정이다.
- 애너테이션 기반으로 스프링을 구동하기 위한 설정이다.
- 지정한 패키지에서 컴포넌트를 스캔해서 빈 컨테이너에 등록하게 하는 설정이다.
- 뷰 리졸버는 컨트롤러에서 온 문자열을 해석하여 뷰를 결정한다. 컨트롤러가 "guestbook/guestbook"를 리턴한다면, 위에서 설정한 뷰 리졸버는 prifix와 suffix를 사용해서 뷰를 /WEB-INF/views/guestbook/guestbook.jsp라고 해석한다.
방명록(Guestbook)을 전담하는 컨트롤러를 생성한다.
GuestbookController.java
package net.java_school.guestbook; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserService; import com.google.appengine.api.users.UserServiceFactory; import com.googlecode.objectify.ObjectifyService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Controller public class GuestbookController { @GetMapping("/guestbook")//1."/guestbook" 요청 핸들러 public String guestbook(String guestbookName, Model model) { model.addAttribute("guestbookName", guestbookName); return "guestbook/guestbook"; } @PostMapping("/guestbook/sign")//2."/guestbook/sign" 요청 핸들러 public String sign(HttpServletRequest req, HttpServletResponse resp) { Greeting greeting; UserService userService = UserServiceFactory.getUserService(); User user = userService.getCurrentUser(); String guestbookName = req.getParameter("guestbookName"); String content = req.getParameter("content"); if (user != null) { greeting = new Greeting(guestbookName, content, user.getUserId(), user.getEmail()); } else { greeting = new Greeting(guestbookName, content); } // Use Objectify to save the greeting and now() is used to make the call synchronously as we // will immediately get a new page using redirect and we want the data to be present. ObjectifyService.ofy().save().entity(greeting).now(); return "redirect:/guestbook/?guestbookName=" + guestbookName; } }
- guestbook() 메소드는 GET 방식의 "/guestbook" 요청을 담당한다.
- sign() 메소드는 POST 방식의 "/guestbook/sign" 요청을 담당한다. sign() 메소드 내용은 SignGuestbookServlet.java 서블릿 코드 그대로다.
컨트롤러가 제대로 동작하기 위해서 서블릿인 GuestbookServlet.java와 SignGuestbookServlet.java와 src/test/java/ 디렉터리에 있는 테스트를 위한 자바 클래스를 모두 삭제한다.
guestbook.jsp 수정
스타일 시트 파일 위치를 변경했으므로 guestbook.jsp에서 스타일 시트 경로를 수정한다.
<link type="text/css" rel="stylesheet" href="/resources/stylesheets/main.css"/>
guestbook.jsp에서 폼 action 속성을 GuestbookController의 요청 핸들러에 맞게 수정한다.
<form action="/guestbook/sign" method="post">
<form action="/guestbook" method="get">
guestbook.jsp의 위치를 변경했으므로, guestbook.jsp에서 createLogoutURL()와 createLoginURL() 아규먼트를 그대로 두면 두 메소드는 /WEB-INF/views/guestbook/guestbook.jsp를 반환한다. 결국 404 에러를 발생한다. 두 메소드의 아규먼트를 request.getRequestURI()에서 "/guestbook/?guestbookName=" + guestbookName으로 수정한다.
<p>Hello, ${fn:escapeXml(user.nickname)}! (You can <a href="<%= userService.createLogoutURL("/guestbook/?guestbookName=" + guestbookName) %>">sign out</a>.)</p> <% } else { %> <p>Hello! <a href="<%= userService.createLoginURL("/guestbook/?guestbookName=" + guestbookName) %>">Sign in</a> to include your name with greetings you post.</p> <% } %>
자바 8 환경에서 앱을 실행하려면, appengine-web.xml에 다음 설정이 있어야 한다.
<runtime>java8</runtime>
로컬 테스트
mvn clean mvn appengine:run
http://localhost:8080/guestbook을 방문한다.
서버 테스트
mvn appengine:deploy
http://your-app-id.appspot.com/guestbook을 방문한다.
참고