스프링 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>6.2.8</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="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
version="6.1"
metadata-complete="true">
<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 jakarta.servlet.http.HttpServletRequest;
import jakarta.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을 방문한다.
참고