Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는...

48
Chapter 17. Web Applications with Spring MVC Chapter 17. Web Applications with Spring MVC Chapter 17. Web Applications with Spring MVC Chapter 17. Web Applications with Spring MVC * * * * 개요 개요 개요 개요 1. MVC구조 : model1 과 model2 를 포함한 MVC구조에 대해서 언급한다. 2. Spring MVC : MVC구조의 컴포넌트를 Spring웹 애플리케이션내에서 구현하는 방법에 대해 언급한다. 3. controller : 컨트롤러를 Spring내에서 구현하는 방법과 특정 요청을 다루는 컨트롤러를 구분하는 방법을 언급한다. 4. 테마(Themes)와 로케일 : 국제화와 테마를 설정하는 방법을 언급한다. * MVC * MVC * MVC * MVC는 무엇인가 무엇인가 무엇인가 무엇인가.? .? .? .? MVC는 Model View Controller의 줄임말이다. 이 패턴의 목적은 사용자요청에 작동하고 데이터를 조작하고 표현할 필요가 있는 애플리케이션의 구현을 단순화하는데 있다. 이 패턴에는 다음과 같은 3 가지의 구별된 컴포넌트가 존재한다. + Model : 사용자가 보게되는 데이터를 표현한다. 대개의 경우, Model은 자바빈으로 구성된다. + View : model를 표현한다. 웹 애플리케이션의 경우 대개 HTML로 표현이 된다. + Controller : 사용자 요청에 반응하고 처리하며 적당한 model을 빌드하고 표현하기 위한 view로 이것을 전달하는 로직의 일부이다. 대부분의 자바 웹 애플리케이션의 경우 controller는 servlet이다. 다음의 그림은 MVC의 type1 을 나타내는 그림이다.

Transcript of Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는...

Page 1: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

Chapter 17. Web Applications with Spring MVCChapter 17. Web Applications with Spring MVCChapter 17. Web Applications with Spring MVCChapter 17. Web Applications with Spring MVC

* * * * 개요개요개요개요

1. MVC구조 : model1 과 model2 를 포함한 MVC구조에 대해서 언급한다.

2. Spring MVC : MVC구조의 컴포넌트를 Spring웹 애플리케이션내에서 구현하는 방법에 대해

언급한다.

3. controller : 컨트롤러를 Spring내에서 구현하는 방법과 특정 요청을 다루는 컨트롤러를 구분하는

방법을 언급한다.

4. 테마(Themes)와 로케일 : 국제화와 테마를 설정하는 방법을 언급한다.

* MVC* MVC* MVC* MVC는는는는 무엇인가무엇인가무엇인가무엇인가.?.?.?.?

MVC는 Model View Controller의 줄임말이다. 이 패턴의 목적은 사용자요청에 작동하고 데이터를

조작하고 표현할 필요가 있는 애플리케이션의 구현을 단순화하는데 있다. 이 패턴에는 다음과 같은

3 가지의 구별된 컴포넌트가 존재한다.

+ Model : 사용자가 보게되는 데이터를 표현한다. 대개의 경우, Model은 자바빈으로 구성된다.

+ View : model를 표현한다. 웹 애플리케이션의 경우 대개 HTML로 표현이 된다.

+ Controller : 사용자 요청에 반응하고 처리하며 적당한 model을 빌드하고 표현하기 위한 view로

이것을 전달하는 로직의 일부이다. 대부분의 자바 웹 애플리케이션의 경우 controller는 servlet이다.

다음의 그림은 MVC의 type1 을 나타내는 그림이다.

Page 2: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과

표현(presentation)모두를 포함한다. 클라이언트는 JSP로 요청을 보내고, 페이지내 로직은 그 다음

빌드되고 model을 표현한다. presentation레이어와 control레이어의 구분이 명확하지 않다. JSP가

수행할 필요가 있는 로직의 양이 많기 때문에 type1 의 방식은 조만간 없어지게 될 것이다.

model 2 는 좀더 큰 애플리케이션내에서도 관리가 가능하도록 해준다. model1 에서는 JSP페이지가

view도 되고 controller도 되지만, model2 에서는 controller를 위한 JSP를 분리한다. 사용자의 요청을

중간에서 처리하는 controller는 model을 준비하고, 표현을 위한 view로 model을 전달한다.

JSP페이지는 요청을 처리하기 위한 로직을 더 이상 포함하지 않는다. 이것은 단순히 controller에

의해 준비된 model을 표시할 뿐이다.

우리는 model1 에서는 view와 controller로 JSP를 사용하고 model2 에서는 view로 JSP를 사용한다.

하지만 이것은 view가 JSP페이지에만 제한되지는 않기 때문에 올바르지 않다. Spring MVC구조는

model2 MVC의 구현이다. 게다가 view는 모델을 표현하고 클라이언트로 결과물을 반환할 수 있는

어떠한 것도 될 수가 있다.

다음의 그림은 MVC의 type2 를 나타내는 그림이다.

* Introducing Spring MVC* Introducing Spring MVC* Introducing Spring MVC* Introducing Spring MVC

Spring MVC는 MVC model2 패턴을 사용하는 유연한 애플리케이션을 빌드하도록 지원한다. model은

데이터를 가지는 간단한 Map이다. View는 데이터를 표현하는 인터페이스이고, controller는

Controller인터페이스의 구현물이다.

소개와소개와소개와소개와 개요개요개요개요

웹 애플리케이션을 위한 MVC구조의 Spring구현물은 DispatcherServlet에 기반을 두고 있다. 이

서블릿은 요청을 처리하고 요청을 다루기 위한 적당한 controller를 호출한다.

Page 3: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

DispatcherServlet은 들어오는 요청을 받아서 어떤 Controller가 요청을 다룰지 판단한다. Spring

controller는 핸들링메소드로부터 ModelAndView클래스를 반환한다. ModelAndView인스턴스는 view와

model에 대한 참조를 가진다. model은 view가 표현할 자바빈즈를 가지는 간단한 Map인스턴스이다.

View는 인터페이스이고, 클라이언트가 해석할 수 있는 어떠한 것이라도 될 수 있도록 해준다.

구현구현구현구현

우리가 Spring을 사용한 웹애플리케이션을 생성하기를 원한다면, 다음처럼 web.xml에

DispatcherServlet을 명시하고 명시된 url-pattern을 위한 맵핑을 셋팅해야만 한다.

<?xml version="1.0" encoding="ISO-8859-1"?>

<!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>Pro Spring Chapter 17 Sample application</display-name>

<description>dtto</description>

<servlet>

<servlet-name>ch171819</servlet-name>

<servlet-class>

org.springframework.web.servlet.DispatcherServlet

</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>ch17</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name> ch171819</servlet-name>

<url-pattern>*.tile</url-pattern>

</servlet-mapping>

</web-app>

Page 4: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

이 web.xml파일은 *.html이나 *.tile로 들어오는 모든 요청을 위해 맵핑된 DispatcherServlet의

ch171819 서블릿을 정의한다.

* * * * 핸들러핸들러핸들러핸들러 맵핑맵핑맵핑맵핑 사용하기사용하기사용하기사용하기

Spring내에서 URL맵핑이 어떻게 작동을 하는지 알아보자. 당신은 특정 요청을 위해 호출될

controller가 무엇인지 Spring에게 알리기 위한 URL맵핑을 알아볼 필요가 있다.

Spring은 호출하는 controller를 알아내기 위해 HandlerMapping구현물을 사용하고 그 구현물은

아래와 같다.

HandlerMappingHandlerMappingHandlerMappingHandlerMapping 상세설명상세설명상세설명상세설명

BeanNameUrlHandlerMapping bean이름은 URL에 의해 인식된다. 만약 URL이

/product/index.html이라면, controller id는 /product/index.html로

셋팅될 맵핑을 다룬다. 이 맵핑은 요청내 와일드카드를 지원하지

않기 때문에 다소 작은 규모의 애플리케이션에 적합하다.

SimpleUrlHandlerMapping 이 핸들러는 controller가 요청을 다루도록 명시하는 요청(전체이름과

와일드카드를 사용하여)을 명시하도록 허용한다.

* * * * 핸들러핸들러핸들러핸들러 Interceptors Interceptors Interceptors Interceptors사용하기사용하기사용하기사용하기

interceptors는 각각의 맵핑을 위해 호출되는 interceptor의 목록을 명시할수 있기 때문에 맵핑과

관련이 있다. HandlerIntereptor 구현물은 적당한 controller이 처리를 하기 전이나 후에 각각의 요청을

처리할수 있다. 당신은 HandlerInterceptor 인터페이스를 구현할수도 있고 모든

HandlerInterceptor메소드를 위한 디폴트 구현물을 제공하는 HandlerInterceptorAdapter를 확장할수도

있다. 다음은 그 예이다.

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class BigBrotherHandlerInterceptor extends HandlerInterceptorAdapter {

Page 5: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object

handler,

ModelAndView modelAndView) throws Exception {

// process the request

}

}

이러한 interceptor의 실질적인 구현물은 요청 파라미터를 처리하고 그 결과를 로그에 남길것이다.

interceptor를 사용하기 위해서, 우리는 다음처럼 Spring애플리케이션 컨텍스트파일에 URL맵핑과

interceptor bean정의를 생성한다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="bigBrotherHandlerInterceptor" class="BigBrotherHandlerInterceptor" />

<bean id="publicUrlMapping"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="interceptors">

<list>

<ref local="bigBrotherHandlerInterceptor" />

</list>

</property>

<property name="mappings">

<props>

<prop key="/index.html">indexController</prop>

<prop key="/product/index.html">productController</prop>

<prop key="/product/view.html">productController</prop>

<prop key="/product/edit.html">productFormController</prop>

</props>

</property>

</bean>

</beans>

Page 6: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

당신은 당신이 하고 싶은 만큼 많은 수의 HandlerMapping과 HandlerInterceptor bean들을 명시할 수

있다.

* controller* controller* controller* controller와와와와 작동하기작동하기작동하기작동하기

controller는 요청을 처리하고, 요청에 기반하는 model을 빌드하고 표현을 위한 view로 model을

전달하는 모든 작업을 수행한다. Spring의 dispatcherServlet은 클라이언트로부터 요청을 받아서 더

많은 처리를 위해 요청을 위임하는 HandlerAdapter구현물을 사용한다. 당신은 전달해야만 하는

요청의 묶음을 변경하기 위해 HandlerAdapter를 구현할수 있다.

DispatcherServlet은 사용하길 바라는 HandlerAdapter을 명시하도록 하는 List

handlerAdapter프라퍼티를 가진다. HandlerAdapter구현물이 순서대로 호출되기 위해, 당신은

HandlerAdapter사이의 위치를 표시하기 위해 당신의 HandlerAdapter내 Ordered 인터페이스를

구현할수 있다.

DispatcherServlet의 handlerAdapters프라퍼티의 값이 null이라면, DispatcherServlet은

SimpleControllerHandlerAdapter를 사용한다. 우리는 추가적인 HandlerAdapter구현물을 제공하지

않을것이기 때문에, 우리의 애플리케이션은 SimpleControllerHandlerAdapter을 사용할 것이다.

SimpleControllerHandlerAdapter의 handle()메소드는 ((Controller) handler).handleRequest(request,

response),를 호출하기 때문에, Spring handler bean은 Controller인터페이스를 구현해야만 하는

controller처럼 작동한다. 이 접근법은 편리한 수퍼클래스중 하나를 사용하여 자신만의 구현물을

사용하는 것을 쉽게 만든다. Controller인터페이스는 당신의 웹 애플리케이션에서만 사용될수 있다는

의미로 HttpServletRequest와 HttpServletResponse에 의존한다. Controller인터페이스의 가장

기본적인 구현물을 살펴보자.

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.Controller;

public class IndexController implements Controller {

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse

response) throws Exception {

response.getWriter().println("Hello, world");

Page 7: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

return null;

}

}

우리가 구현할 필요가 있는 메소드는 오직 ModelAndView handerRequest(HttpServletRequest,

HttpServletResponse) 이다. 우리는 표현될 view가 없고 응답의 출력을 위해 쓰여진 출력은

처분(commit)되었으며 클라이언트에 반환된다는 의미로 ModelAndView로 null을 반환한다. Spring은

이미 유용한 많은 수의 수퍼클래스를 제공하기 때문에 Controller인터페이스를 구현하는 것은 의미가

없다.

AbstractControllerAbstractControllerAbstractControllerAbstractController

AbstractController 는 요청을 처리하기 위한 handleRequestInternal 메소드를 구현하도록 강요하는 인

터페이스를 간단하게 포장(warpper)한다. AbstractController 는 요청과 응답을 제어하기 위한 추가적

인 프라퍼티를 셋팅하는 것을 허용하는 WebContentGenerator 클래스를 확장하기 때문에, 부분적으로

는 사실이다.

추가적으로, WebContentGenerator 는 ApplicationContextAware 를 구현하는

ApplicationObjectSupport 를 확장하는 WebApplicationObjectSupport 를 확장한다. 반면에, Controller

인터페이스를 구현하는 것보다 AbstractController 로부터 당신의 controller 를 확장하는 것이 직접적

으로 ServletContext, WebApplicationContext, ApplicationContext, Log, 그리고

MessageSourceAccessor 에 접근하도록 해준다.

프라퍼티프라퍼티프라퍼티프라퍼티 설명설명설명설명 디폴트디폴트디폴트디폴트 값값값값

supportedMethods 지원되고 허용되는 HTTP 메소드 GET, POST

requiresSession HttpSession 인스턴스가 요청을 처리하는데

요구되는지에 대한 명시

false

useExpiresHeader HTTP 1.0 expires 헤더를 사용할지에 대한 명시 true

useCacheControlHeader HTTP 1.1 cache-control 헤더를 사용할지에 대한

명시

true

cacheSeconds 명시된 초(second)동안 생성된 결과물을 캐시할지를

클라이언트에 지시

–1

synchronizeOnSession handleRequestInternal 를 호출하기 전에

HttpSession 의 인스턴스를 통기화할지에 대한 명시.

false

Page 8: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

프라퍼티프라퍼티프라퍼티프라퍼티 설명설명설명설명 디폴트디폴트디폴트디폴트 값값값값

이것은 같은 클라이언트로부터 요청 핸들링에

재진입하는데 유용하다.

예를 들면, 위 값중에 cacheSeconds 프라퍼티를 10 으로 셋팅하고 클라이언트에서 페이지를 새로고

침한다면 결과로써, 우리는 매번 10 마다 서버로부터 새로운 결과물을 가져온다.

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.AbstractController;

public class IndexController extends AbstractController {

protected ModelAndView handleRequestInternal(HttpServletRequest request,

HttpServletResponse response)

throws Exception {

setCacheSeconds(10);

response.getWriter().println("Hello, world at " + System.currentTimeMillis());

return null;

}

}

ParameterizableViewControllerParameterizableViewControllerParameterizableViewControllerParameterizableViewController

이것은 AbstractController 의 간단한 하위클래스이다. 이것은 viewName 프라퍼티내 셋팅된 이름을 가

진 새로운 model 을 반환하기 위한 handleRequestInternal 메소드를 구현한다. model 로 삽입된 데이

터는 없고, 이것의 이름을 사용하여 view 를 간단히 표시하기 위해 이 controller 을 사용할것이다. 다

음은 ParameterizableViewController 의 기능을 보여주기 위해 생성된 ParameterizableIndexController

를 보여준다.

import org.springframework.web.servlet.mvc.ParameterizableViewController;

public class ParameterizableIndexController extends ParameterizableViewController {

Page 9: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

}

다음의 xml 파일에서, 우리는 애플리케이션 컨텍스트 파일로 parameterizableIndexController bean 을

추가하고, publicUrlMapping bean 으로 참조를 추가하는 것처럼 product-index 에 viewName 프라퍼티

를 셋팅한다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="publicUrlMapping"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="interceptors">

<list>

<ref local="bigBrotherHandlerInterceptor" />

</list>

</property>

<property name="mappings">

<props>

<prop key="/index.html">indexController</prop>

<prop key="/pindex.html">

parameterizableIndexController

</prop>

<prop key="/product/index.html">productController</prop>

<prop key="/product/view.html">productController</prop>

<prop key="/product/edit.html">

productFormController

</prop>

<prop key="/product/image.html">

productImageFormController

</prop>

</props>

</property>

</bean>

Page 10: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<bean id="parameterizableIndexController"

class="ParameterizableIndexController">

<property name="viewName">

<value>products-index</value>

</property>

</bean>

</beans>

MultiActionControllerMultiActionControllerMultiActionControllerMultiActionController

이것 또한 모든 프라퍼티와 메소드에 접근하는 의미를 가지는 AbstractController 의 하위클래스이다.

이것은 당신이 필요한 public ModelAndView(HttpServletRequest, HttpServletResponse) throws

Exception 의 많은 구현물을 제공한다. 당신은 MultiActionController 의 하위클래스내의 메소드를 구

현하기 위해 선택할수 있거나 이러한 메소드를 구현하는 위임객체를 명시할수 있다. 후자의 경우,

MultiActionController 는 위임객체의 메소드를 호출한다. AbstractController 에 추가적인 두개의 프라퍼

티인 delete 와 methodNameResolver 는 어떠한 객체의 메소드가 각각의 요청에 대해 호출되는지를

MultiActionController 에게 알리기 위해 사용된다. 만약 delete 프라퍼티가 null 이라는 디폴트값으로 남

겨진다면, 메소드는 검색하고(look up) MultiActionController 하위 클래스의 메소드를 호출한다. 만약

delegate 가 null 이 아니라면, 메소드를 delete 의 것을 호출한다.

methodNameResolver 는 MethodNameResolver 의 구현물로 셋팅되어야만 한다.

MethodNameResolver 의 3 가지 구현물은 아래와 같다.

구현물구현물구현물구현물 설명설명설명설명

InternalPathMethodNameResolver 메소드 이름은 path 의 마지막 부분으로부터 확장자를 제외하고

얻어진다. 이 resolver path 가 사용될 때, /servlet/foo.html 은

public ModelAndView foo(HttpServletRequest,

HttpServletResponse)처럼 선언된 메소드로 맵핑된다. 이것은

MultiActionController 내 사용되는 디폴트 구현물이다.

ParameterMethodNameResolver 메소드 이름은 특정 요청 파라미터로부터 얻어진다. 디폴트

파라미터 이름은 action 이다. 당신은 컨텍스트 파일에서 파라미터

이름을 변경할수 있다.

PropertiesMethodNameResolver 메소드 이름은 외부 프라퍼티 파일로부터 해석된다. 당신은

/test.html=handleTest 처럼 정확하게 맵핑을 명시할수도 있고, /*=

handleAllTs 처럼 와일드카드를 사용할수도 있다.

Page 11: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

당신의 애플리케이션에 위의 것중에 적합한 것이 없다면, 당신은 자신만의 MethodNameResolver 를

구현할수 있다. 다음의 소스는 MultiActionController 하위클래스의 가장 간단한 구현물을 보여준다.

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

public class ProductController extends MultiActionController {

public ModelAndView view(HttpServletRequest request, HttpServletResponse response)

throws Exception {

response.getOutputStream().print("Viewing product " +

request.getParameter("productId"));

return null;

}

}

위 ProductController 는 오직 view()라는 하나의 메소드만을 추가했다. 만약 path /product/* 가 컨테

이너에 맵핑되고, 요청이 /product/view.html?productId=10 이라면, 브라어저내 출력은 Viewing

product 10 을 보게될것이다.

디폴트에 의해 MultiActionController 는 methodNameResolver 처럼 InternalPathMethodNameResolver

를 사용하고, delegate 프라퍼티는 null 이 된다. 왜냐하면 view()메소드는 ProductController 의 메소드

가 호출되기 때문이다. 다른 methodNameResolver 가 설정되는 방법을 보자.

디폴트에 의해, ParameterMethodNameResolver 는 메소드이름을 얻기 위한 것으로부터 파라미터 이

름 같은 action 을 사용한다. 우리는 아래와 같이 paramName 프라퍼티를 셋팅하여 변경할수 있다. 우

리는 paramName 파라미터가 호출될 메소드의 이름을 위해 defaultMethodName 프라퍼티를 셋팅하여

요청내 존재하지 않을 때 호출되는 메소드이름을 명시할수 있다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

Page 12: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<!-- other beans -->

<bean id="productController" class="ProductController">

<property name="methodNameResolver">

<ref local="productMethodNameResolver" />

</property>

</bean>

<bean id="productMethodNameResolver"

class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">

<property name="paramName">

<value>method</value>

</property>

<property name="defaultMethodName">

<value>view</value>

</property>

</property>

</bean>

</beans>

만약 우리가 /product/a.html 로 요청을 보내고, 메소드 파라미터를 명시하지 않는다면,

ProductController.view 가 호출된다. 우리는 /product/a.html?method=view 로 요청을 보낸다면 같은

행위를 하게 될것이다. 만약 우리가 /product/a.html?method=foo 로 요청을 보낸다면, 우리는 public

ModelAndView foo(HttpServletRequest, HttpServletResponse) 메소드가 ProductController 내 구현되

지 않아서 에러메시지를 보게될것이다.

마지막으로 PropertiesMethodNameResolver 는 InternalPathMethodNameResolver 와는 달리 요청

URI 에 의존한다. 우리는 Spring 컨텍스트 파일에 메소드이름을 명시할수 있다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

Page 13: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<!-- other beans -->

<bean id="productController" class="product.ProductController">

<property name="methodNameResolver">

<ref local="productMethodNameResolver" />

</property>

</bean>

<bean id="productMethodNameResolver"

class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">

<property name="mappings">

<props>

<prop key="/product/view.html">view</prop>

<prop key="/product/v*.html">view</prop>

</props>

</property>

</bean>

</beans>

위 코드는 PropertiesMethodNameResolver 를 사용하는 방법을 보여준다. 우리는 mappings 프라퍼티

를 설정할 필요가 있고 mapping 의 목록과 그것들의 핸들러 메소드를 추가할 필요가 있다. 위 소스는

ProductController 내 public ModelAndView view(HttpServletRequest, HttpServletResponse) 로

/product/v*.html 처럼 /product/view.html 을 선언했다. 이 MethodNameResolver 의 이득은 맵핑 문

자열내 와일드카드를 사용할수 있다. 이러한 controller 는 매우 유용하다. 하지만 우리가 사용자에 의

해 서브밋된 입력을 처리한다면, 우리는 서브밋된 값을 얻고 에러 메시지를 처리하는데 많은 코드를

작성해야만 한다. Spring 은 다양한 command controller 를 제공하여 이 처리를 단순화한다.

* Views, Locales, and Themes* Views, Locales, and Themes* Views, Locales, and Themes* Views, Locales, and Themes

Using Views ProgrammaticallyUsing Views ProgrammaticallyUsing Views ProgrammaticallyUsing Views Programmatically

이 예제에서, 우리는 View 를 구현하고 AbstractController.handleRequestInternal()의 결과인

ModelAndView 클래스로 이 구현물을 반환한다. 우리의 view 는 View 인터페이스로부터 오직 하나

메소드인 render(Map, HttpServletRequest, HttpServletResponse) 를 구현해야만 한다. 아래의 코드

Page 14: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

에서 생성한 View 구현물은 model 로부터 텍스트파일로 모든 데이터를 출력하고 클라이언트로 표시

하기 위한 응답 헤더를 셋팅한다.

import java.io.PrintWriter;

import java.util.Iterator;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.View;

public class PlainTextView implements View {

public void render(Map model, HttpServletRequest request, HttpServletResponse response)

throws Exception {

response.setContentType("text/plain");

response.addHeader("Content-disposition", "attachment; filename=output.txt");

PrintWriter writer = response.getWriter();

for (Iterator k = model.keySet().iterator(); k.hasNext();) {

Object key = k.next();

writer.print(key);

writer.println(" contains:");

writer.println(model.get(key));

}

}

}

아래에서 우리는 사용자지정 view 를 반환하기 위해 IndexController 를 변경한다.

public class IndexController extends AbstractController {

Page 15: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

protected ModelAndView handleRequestInternal(HttpServletRequest request,

HttpServletResponse response)

throws Exception {

setCacheSeconds(10);

Map model = new HashMap();

model.put("Greeting", "Hello World");

model.put("Server time", new Date());

return new ModelAndView(new PlainTextView(), model);

}

}

/index.html 경로로 요청을 보내보자. IndexController.handleRequestInternal 메소드는 호출되고 이것

은 PlainTextView 의 인스턴스로 셋팅되는 View 를 가진 ModelAndView 의 인스턴스를 반환하며

model Map 는 Greeting 와 Server time 키를 포함한다. PlainTextView 의 render()메소드는 헤더정보를

셋팅한다.

Page 16: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

이 예제는 하나의 단점을 가진다. IndexController 내 코드는 각각의 요청에 대해 PlainTextView 의 인

스턴스를 생성한다. view 는 비상태유지 객체이기 때문에 이것은 다소 불필요하다. 다음에서 우리는

애플리케이션을 향상시키고 PlainTextView Spring bean 을 만든다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="plainTextView" class="PlainTextView" />

<!-- other beans as usual -->

</beans>

우리는 각각의 요청을 위해 PlainTextView 의 인스턴스를 인스턴스화하는 대신에 PlainTextView bean

을 사용하기 위해 아래와 같이 IndexController 의 handleRequestInternal() 메소드를 변경할 필요가

있다.

public class IndexController extends AbstractController {

protected ModelAndView handleRequestInternal(HttpServletRequest request,

HttpServletResponse response)

throws Exception {

setCacheSeconds(10);

Page 17: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

Map model = new HashMap();

model.put("Greeting", "Hello World");

model.put("Server time", new Date());

View view = (View) getApplicationContext().getBean("plainTextView");

return new ModelAndView(view, model);

}

}

위 소스는 이전의 것보다는 좋다. 각각의 요청은 PlainTextView bean 의 같은 인스턴스를 얻게 된다.

하지만 여전히 이상적이지는 않다. 전형적인 웹 애플리케이션은 굉장히 많은 수의 view 로 구성이 되

고, 이러한 방법으로 view 를 설정하는 것은 불편하다. 게다가 어떤 view 는 더 많은 설정을 요구한다.

JSP view 를 얻는 것은 예를 들면, JSP 페이지의 경로를 필요로 한다. 만약 우리가 Spring bean 처럼

손으로 일일이 모든 view 를 설정한다면, 우리는 분리된 bean 처럼 각각의 JSP 페이지를 설정해야만

한다. 우리가 view 를 설정하고 Spring 에 대해 작동하는 모든 것을 위임하는 좀더 쉬운 방법을 가진

다면 이것은 굉장히 좋은 상황이라고 할 수 있다. 이러한 요구사항을 만족시키는 것이 view resolver

이다.

Using ViewResolver ImplementationsUsing ViewResolver ImplementationsUsing ViewResolver ImplementationsUsing ViewResolver Implementations

ViewResolver 는 전략적인 인터페이스이다. Spring 은 이름과 로케일에 기반하여 적당한 view 를 찾고

인스턴스화하기 위해 사용한다. 다양한 view resolver 는 당신의 애플리케이션을 좀더 유지보수를 수비

게하도록 해주는 ViewResolver 인터페이스의 하나의 메소드인 View resolveViewName(String

viewName, Locale locale) throws Exception 를 모두 구현한다. locale 파라미터는 ViewResolver 가 다

른 클라이언트 로케일을 위한 view 를 반환할수 있도록 해준다.

구현구현구현구현물물물물 상세설명상세설명상세설명상세설명

BeanNameViewResolver 애플리케이션 컨텍스트내 설정된 bean 처럼 view 를 얻기 위해 시

도하는 간단한 ViewResolver 구현물이다. 당신은 view 정의를 고정

하는 다른 파일을 생성하는 것을 원하지 않는 좀더 작은 규모의

애플리케이션을 위해 유용하다는것을 알 수 있을것이다. 어쨌든,

이 resolver 는 여러가지 제한점을 가진다. 가장 골치아픈 것은 당

신이 애플리케이션 컨텍스트내 Spring bean 처럼 view 를 설정해야

만 한다는 것이다. 또한 이것은 국제화(internalization)를 지원하지

Page 18: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

구현구현구현구현물물물물 상세설명상세설명상세설명상세설명

않는다.

ResourceBundleViewResolver 이것은 좀더 복잡한 resolver 이다. 이 경우, view 정의는 각각 분리

된 설정파일에서 유지된다. 당신은 애플리케이션 컨텍스트 파일내

view bean 을 설정할 필요는 없다. 이 resolver 는 국제화를 지원한

다.

UrlBasedViewResolver 이 resolver 는 URL 에 기반하여 적당한 view 를 인스턴스화 한다.

당신은 접두사나 접미사를 가지는 URL 을 설정할 수 있다. 이

resolver 는 당신에게 BeanNameViewResolver 보다 좀더 view 에

대한 제어를 가능하게 해준다. 하지만 이것은 좀더 큰 애플리케이

션을 관리하는 것을 어렵게 할수 있고 국제화를 지원하지 않는다.

XmlViewResolver 이 view resolver 는 view 정의가 분리된 파일에서 유지되기 때문에

ResourceBundleViewResolver 와 유사하다. 하지만 국제화는 지원

하지 않는다.

우리는 Spring 내에서 사용가능한 ViewResolvers 와 그것들의 각각의 장점과 단점을 알아보았다. 가장

복잡한 기능을 제공하는 ResourceBundleViewResolver 에 대해서 알아보자. viewResolver bean 정의

를 포함하는 컨텍스트파일을 변경하는 것으로 시작해보자.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="viewResolver"

class="org.springframework.web.servlet.view.ResourceBundleViewResolver">

<property name="basename">

<value>views</value>

</property>

</bean>

<!-- etc -->

</beans>

이것은 Spring 이 모든 view 이름을 해석하기 위해 사용하는 viewResolver bean 을 소개한다. 클래스

는 ResourceBundleViewResolver 이고, 이것의 basename 프라퍼티는 views 이다. 이것은

Page 19: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

ViewResolver 가 클래스패스에서 views_<LID>.properties 파일을 찾는다는 것을 의미한다. 여기서 LID

는 로케일 구분자(EN, CS 그리고 기타 등등)를 말한다. 만약 resolver 가 views_<LID>.properties 파일

을 찾지 못한다면, 이것은 views.properties 파일을 열것이다. 이 resolver 내 국제화지원을 보여주기

위해, 우리는 views.properties 와 views_CS.properties 를 생성한다. properties 파일의 형식은 아래와

같이 viewname.class=class-name 와 viewname.url=view-url 이다.

#index

products-index.class=org.springframework.web.servlet.view.JstlView

products-index.url=/WEB-INF/views/product/index.jsp

이 파일을 유지하는 가장 좋은 방법은 디렉토리 구분자처럼 / 를 사용하여 애플리케이션의 논리적인

구조를 따라 유지보수하는 것이 가장 쉽다.

#index

user-edit.class=org.springframework.web.servlet.view.JstlView

user-edit.url=/WEB-INF/views/user /edit.jsp

유사하게도 우리는 /web-src/as-web/WEB-INF/views/product 내 index.jsp 와 index_CS.jsp 를 생성

한다. 마지막으로 우리는 Product 객체의 목록을 반환하기 위해 ProductController 를 변경하고 view

에서 이 목록을 보여준다.

public class ProductController extends MultiActionController {

private List products;

private Product createProduct(int productId, String name, Date expirationDate) {

Product product = new Product();

product.setProductId(productId);

product.setName(name);

product.setExpirationDate(expirationDate);

return product;

}

public ProductController() {

products = new ArrayList();

Page 20: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

Date today = new Date();

products.add(createProduct(1, "test", today));

products.add(createProduct(2, "Pro Spring Appes", today));

products.add(createProduct(3, "Pro Velocity", today));

products.add(createProduct(4, "Pro VS.NET", today));

}

public ModelAndView index(HttpServletRequest request, HttpServletResponse response) {

return new ModelAndView("products-index", "products", products);

}

// other methods omitted for clarity

}

당신이 보는 것처럼, 한가지의 변경사항은 index()메소드내에서 ModelAndView(View, ...) 대신에

ModelAndView(String, String, Object)가 호출된다는 것이다. 이 애플리케이션을 테스트하기 위해,

Czech 가 아닌 다른 언어를 브라우저에 셋팅하라. Spring 은 URL 을 /WEB-INF/product/index.jsp 로

셋팅하고 결과물을 표시하는 JstlView 타입의 product-index View 를 생성한다. 결과물은 다음과 같다.

만약 우리가 Czech 로 언어를 변한다면, 다음처럼 view resolver 는 JstlView 이고 URL 프라퍼티가

/WEB-INF/products/index_CS.jsp 를 가리키는 index_CS 의 인스턴스를 생성한다.

Page 21: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

view 를 일일이 수동으로 인스턴스화하는 것보다 view resolver 를 사용하는 것은 설정파일을 좀더 단

순화하는 이득을 준다. 하지만 이것은 애플리케이션의 메모리를 감소시킨다. 만약 우리가 Sprinb

bean 처럼 각각의 view 를 정의한다면, 이것은 애플리케이션 시작시 인스턴스화된다. 만약 우리가

view resolver 를 사용한다면, view 는 인스턴스화되고 첫번째 요청시 캐시된다.

Using Localized MessagesUsing Localized MessagesUsing Localized MessagesUsing Localized Messages

우리가 Sprnig 웹 애플리케이션내 로케일을 이야기하기 전에, 우리는 spring:message 태그를 사용하

여 표시될 메시지를 위한 실질적인 텍스트를 해석하는 방법을 봐야만 한다. Spring 이 찾는 디폴트

bean 이름은 messageSource 이다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="messageSource"

class="org.springframework.context.support.ResourceBundleMessageSource">

<property name="basename">

<value>messages</value>

</property>

</bean>

</beans>

Page 22: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

로케일 사용하기

Spring은 요청을 받아서 로케일을 가져오거나 셋팅하기 위해 이것의 메소드를 호출하기 위해

LocaleResolver 인터페이스를 사용한다. LocaleResolver의 다양한 구현물이 있다. 다음처럼 각각은 특

별한 사용법과 프라퍼티를 가진다.

구현물구현물구현물구현물 상세설명상세설명상세설명상세설명

AcceptHeaderLocaleResolver 이 로케일 resolver는 애플리케이션을 위해 사용자 에이전트에 의

해 보내어진 accept- language 헤더에 기반하여 로케일을 반환

한다. 만약 이 resolver가 사용된다면, 애플리케이션은 사용자가

선호하는 언어를 자동적으로 나타낼 것이다. 만약 당신이 다른 언

어로 변경하기를 원한다면 브라우저 셋팅을 변경해야 한다.

CookieLocaleResolver 이 로케일 resolver는 로케일을 확인하기 위해 클라이언트 머신의

쿠키를 사용한다. 이것은 브라우저 셋팅의 변경 없이 나타내고자

하는 언어를 명시하도록 해준다. 이 로케일 resolver를 사용하여,

우리는 사용자의 브라우저 쿠키 저장소를 사용하여 로케일 셋팅

을 저장할 수 있다.

FixedLocaleResolver 이것은 설정된 하나의 로케일만을 반환하는 LocaleResolver의 매

우 간단한 구현물이다.

SessionLocaleResolver 이 resolver는 CookieLocaleResolver처럼 작동한다. 하지만 로케

일 셋팅은 쿠키에 저장되지 않고 세션이 종료되면 사라진다.

Using ThemesUsing ThemesUsing ThemesUsing Themes

사용자 언어로 애플리케이션의 view를 제공하는데 추가적으로, 당신은 사용자의 경험을 향상시키기

위해 테마를 사용할 수 있다. 테마는 스타일시트와 내장된 이미지의 모음이다. Spring은 JSP페이지내

지원되는 테마가 가능하도록 하기 위해 사용할수 있는 태그 라이브러리를 제공한다. 테마의 사용을

보여주기 위해 사용될 디렉토리 구조는 아래와 같다.

Page 23: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

당신이 보는것처럼 우리는 테마 디렉토리를 추가하고 두개의 새로운 프라퍼티 파일

(cool.properties,default.properties)을 생성했다. 프라퍼티 파일의 내용은 아래와 같이 정적인 테마

자원의 위치를 명시한다.

css=/themes/cool/main.css

프라퍼티 파일내 key는 테마 resolver에 의해 드러나는 key를 명시한다. 그리고 프라퍼티의 value는

테마 자원의 위치를 명시한다. 우리는 Spring 태그 라이브러리를 사용하여 JSP페이지내 이 정의를 사

용할수 있다.

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%> <%@taglib prefix="spring"

uri="http://www.springframework.org/tags"%>

<html>

<head>

Page 24: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<c:set var="css">

<spring:theme code="css" />

</c:set>

<c:if test="${not empty css}">

<link rel="stylesheet" href="<c:url value="${css}" />" type="text/css" />

</c:if>

</head>

<body>

This page lists all available products:<br>

<c:forEach items="${products}" var="product">

<c:out value="${product.name}" />

<a href="view.html?productId=<c:out

value="${product.productId}" />">[View]</a>

&nbsp;

<a href="edit.html?productId=<c:out

value="${product.productId}" />">[Edit]</a>

&nbsp;<br><hr>

</c:forEach>

<br> <a href="edit.html">[Add]</a>

</body>

</html>

마지막으로, 우리는 Spring 애플리케이션 컨텍스트를 변경하고 themeResolver bean을 추가할 필요가

있다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="themeResolver"

class="org.springframework.web.servlet.theme.FixedThemeResolver">

<property name="defaultThemeName">

<value>cool</value>

</property>

</bean>

Page 25: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<!-- other beans as usual -->

</beans>

애플리케이션 컨텍스트 파일은 애플리케이션이 defaultThemeName가 cool로 셋팅되는

FixedThemeResolver를 사용하는 것을 명시한다. 그러므로 테마는 클래스패스의 가장 상위의

cool.properties파일로 부터 로드된다.

테마는 스타일시트가 아닌 이미지나 영상과 같은 어떠한 종류의 정적인 내용을 포함할 수 있다. 이것

은 또한 이미지가 다른 언어로 번역될 필요가 있는 텍스트를 포함하기 때문에 국제화를 지원해야만

한다. 테마 resolver내 국제화 지원은 ResourceBundleViewResolver내 국제화 지원과 완전히 동일하

게 작동한다. 테마 resolver는 theme_<LID>.properties 를 로드할려고 시도한다. 여기서 LID는 로케일

구분자(EN, CS 또는 기타등등)이다. 만일 LID를 가진 properties파일이 존재하지 않는다면, resolver는

LID없는 properties파일을 로드하기를 시도할것이다. ViewResolvers 와 LocaleResolvers처럼,

ThemeResolvers 에도 다음처럼 다양한 구현물이 있다.

테마테마테마테마 Resolver Resolver Resolver Resolver 상세설명상세설명상세설명상세설명

CookieThemeResolver 이것은 사용자별로 셋팅되는 테마를 허용하고 클라이언트 컴퓨터의

쿠키를 저장하여 테마 참조를 저장한다.

FixedThemeResolver 이 테마 resolver는 bean의 defaultThemeName프라퍼티에 셋팅된

하나의 고정된 테마를 반환한다.

SessionThemeResolver 이것은 사용자 세션별로 셋팅되는 것을 허용한다. 이 테마는 세션사

이에 유지되지는 않는다.

* Command Controller* Command Controller* Command Controller* Command Controller사용하기사용하기사용하기사용하기

전형적인 애플리케이션은 사용자로부터 데이터를 모으고 처리한다. Spring은 controller로 보내어진데

이터를 처리하는 command Controller를 제공한다. 우리가 다양한 command Controller에 대해 언급

하기 이전에 command Controller의 개념을 알아보자. command controller는 form 서브밋으로 부터

활성화되는 command 객체의 프라퍼티를 허용한다. command controller는 데이터 유효성체크를 단순

화하기 위한 Spring 태그 라이브러리로 작동한다. command controller는 모든 비즈니스 유효성체크를

수행하기에는 이상적이다. 유효성체크가 서버에서 발생할 때, 사용자가 유효성체크를 무시하는 것은

불가능하지만, 당신은 모든 유효성체크를 수행하기 위해 웹티어에 의존하지 않고 비즈니스티어에서

재입증할게 될것이다.

기술적인 측면에서, command controller구현물은 대개 도메인 객체인 호출되는 command객체를 나타

낸다. 우리의 애플리케이션에서 사용할수 있는 command controller를 살펴보자.

Page 26: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

+ + + + AbstractCommandControllerAbstractCommandControllerAbstractCommandControllerAbstractCommandController

AbstractController 처럼, 이것은 Controller인터페이스를 구현한다. 이 클래스는 실질적으로 HTML

form 서브밋을 다루기 위해 디자인된 것은 아니지만, 유효성체크와 데이터 바인딩을 위한 기본적인

지원을 제공한다. 당신은 자신만의 command controller를 구현하기 위해 이 클래스를 사용할수 있다.

+ + + + AbstractFormControllerAbstractFormControllerAbstractFormControllerAbstractFormController

AbstractFormController클래스는 AbstractCommandController를 확장하고 실질적으로 HTML form서브

밋을 다룰수 있다. 반면에, 이 command controller는 HttpServletRequest내 값을 처리하고 controller

의 command객체를 활성화한다. AbstractFormController 또한 중복 form 서브밋을 찾아내는 능력을

가진다. 그리고 Spring 컨텍스트 파일보다는 코드내 표현되는 view를 명시하도록 허용한다.

+ + + + SimpleFormControllerSimpleFormControllerSimpleFormControllerSimpleFormController

이것은 HTML form을 처리하기 위해 가장 공통적으로 사용되는 command controller이다. 이것은 또

한 사용하기 쉽게 디자인되었다. 당신은 initial(초기화) view와 success(성공) view를 표시하기 위한

view를 명시할수 있다. 그리고 당신은 서브밋된 데이터를 활성화할 필요가 있는 command객체를 셋

팅할수 있다.

+ + + + AbstractWizardFormControllerAbstractWizardFormControllerAbstractWizardFormControllerAbstractWizardFormController

이름이 제시하는 것처럼, 이 command controller는 페이지의 마법사 스타일 세트를 구현하기 위해 유

용하다. 이것은 또한 당신이 현재 HttpSession내 command객체를 유지하고 현재 페이지의 데이터가

유효한지와 마법사가 다음 페이지에도 계속될수 있는지를 체크하기 위한 validatePage() 메소드를 구

현할 필요가 있다는 것을 내포한다. 마지막으로 AbstractWizardFormController는 마법사가 처리하는

마지막 페이지를 처리하는 것을 표시하기 위한 processFinish() 메소드를 수행한다. 그리고 데이터는

유효하고 비즈니스 티어로 전달될수 있다.

Using Form ControllersUsing Form ControllersUsing Form ControllersUsing Form Controllers

지금 당신은 선택해야할 form controller가 무엇인지 안다. form controller가 어떻게 사용되는지 예제

를 통해서 보자. 우리는 가장 간단한 form controller로 시작하고 유효성체크와 사용자 정의 포맷터를

추가한다.

가장 간단한 controller 구현물은 onSubmit()메소드를 오버라이드하고 디폴트 생성자를 제공하는

SimpleFormController를 확장한다.

public class ProductFormController extends SimpleFormController {

Page 27: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

public ProductFormController() {

super();

setCommandClass(Product.class);

setFormView("products-edit");

}

protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,

Object command,

BindException errors) throws Exception {

System.out.println(command);

return new ModelAndView("products-index-r");

}

}

ProductFromController의 생성자는 command클래스가 Product.class임을 정의한다. 이것은 이

controller가 생성하는 객체가 Product의 인스턴스임을 의미한다.

다음으로, 우리는 사용자가 form을 서브밋할 때 호출되는 onSubmit()메소드를 오버라이드한다.

command객체는 이미 유효성체크를 하고 애플리케이션의 비즈니스 티어로 이것을 안전하게 전달한다.

onSubmit()메소드는 products/index.html 페이지로 리다이렉트하는 RedirectView인 products-index-

r view를 반환한다. 우리는 요청을 다루기 위한 ProductController.handleIndex() 메소드를 원하기 때

문에 이것이 필요하다.

마지막으로, setFormView()에 대한 호출은 form을 표시하기 위해 사용되는 view를 명시한다. 우리의

경우, JSP는 다음과 같다.

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%>

<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>

<html>

<head>

<c:set var="css"><spring:theme code="css"/></c:set>

<c:if test="${not empty css}">

<link rel="stylesheet"

href="<c:url value="${css}"/>" type="text/css" />

</c:if>

Page 28: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

</head>

<body>

<form action="edit.html" method="post">

<input type="hidden" name="productId"

value="<c:out value="${command.productId}"/>">

<table>

<tr>

<td>Name</td>

<td><spring:bind path="command.name">

<input name="name" value="<c:out value="${status.value}"/>">

<span class="error"><c:out value="${status.errorMessage}"/></span>

</spring:bind>

</td>

</tr>

<tr>

<td>Expiration Date</td>

<td><spring:bind path="command.expirationDate">

<input name="expirationDate"

value="<c:out value="${status.value}"/>">

<span class="error"><c:out value="${status.errorMessage}"/></span>

</spring:bind>

</td>

</tr>

<tr>

<td></td>

<td><input type="submit"></td>

</tr>

</table>

</form>

</body>

</html>

Spring이 제공하는 spring:bind태그는 가장 간단한 방법으로 form으로부터 값을 전달하도록 해주고

간단한 유효성체크를 제공한다. 첫번째, 우리는 path에 command객체이름인 값을 바인드하고 필드에

셋팅한다. spring:bind태크에서 Spring은 value필드가 spring:bind태그내 정의된 프라퍼티의값을 표현

하는 객체상태를 명시한다. status.errorMessage는 어떤 유효성 에러 메시지를 정의한다.

Page 29: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

마지막으로 우리는 애플리케이션 컨텍스트 파일의 productFormController bean을 변경할 필요가 있고

/product/edit.html에서 form controller를 맵핑할 필요가 있다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="publicUrlMapping"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/index.html">indexController</prop>

<prop key="/product/index.html">productController</prop>

<prop key="/product/view.html">productController</prop>

<prop key="/product/edit.html">

productFormController

</prop>

</props>

</property>

</bean>

<!-- Product -->

<bean id="productFormController" class="ProductFormController">

</bean>

<!-- other beans as usual -->

</beans>

당신이 보는것처럼, Spring애플리케이션 컨텍스트 파일내 새로운 정의를 추가하는 것은 아무것도 아니

다. 만약 우리가 http://localhost:8080/ch17/product/edit.html 를 본다면, 우리는 다음처럼 데이터를

넣기 위한 for을 가진 웹 페이지를 보게 될것이다.

Page 30: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

불행하게도, expirationDate 프라퍼티는 Date타입이고 Java 날짜 포맷은 조금 사용하기 어렵다. 당신

은 Date값을 위해 Sun Oct 24 19:20:00 BST 2004Sun Oct 24 19:20:00 BST 2004Sun Oct 24 19:20:00 BST 2004Sun Oct 24 19:20:00 BST 2004 형식을 기대할 수는 없다. 사용자를 위해 좀더 쉽

도록 하기 위해서, 우리는 최근 바인더(binder)를 가진 사용자정의 편집기를 등록한다. 이것을 하기

위해서, 우리는 initBinder()메소드를 오버라이드할것이다.

public class ProductFormController extends SimpleFormController {

// other methods omitted for clarity

protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)

throws Exception {

SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

dateFormat.setLenient(false);

binder.registerCustomEditor(Date.class, null, new CustomDateEditor(dateFormat,

false));

}

}

Page 31: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

새롭게 등록된 사용자정의 편집기는 모든 Date.class값이 적용되고 값은 dd/MM/yyyy 값으로 파싱된

다. “24/10/2004”는 유효한 Date값처럼 “Sun Oct 24 19:20:00 BST 2004” 대신에 적용된다. 여기엔

다른 중요한 것인 유효성체크라는 것을 놓쳤다. 우리는 이름이 없는 product를 추가하는 것을 사용자

에게 허용하길 원하지 않는다. 유효성체크를 구현하기 위한 가장 멋진 방법은 아래와 같이 Validator

인터페이스를 구현하는 것이다. Spring관리 bean처럼 ProductValidator를 등록하고 productValidator

bean에 ProductFormController의 validator프라퍼티를 셋팅한다.

import org.springframework.validation.Errors;

import org.springframework.validation.Validator;

public class ProductValidator implements Validator {

public boolean supports(Class clazz) {

return clazz.isAssignableFrom(Product.class);

}

public void validate(Object obj, Errors errors) {

Product product = (Product) obj;

if (product.getName() == null || product.getName().length() == 0) {

errors.rejectValue("name", "required", "");

}

}

}

Validator구현물은 요구되는 errorCode세트를 가진 유효성 에러를 추가한다. 이 코드는

messageSource bean을 사용하여 해석될 필요가 있는 메시지 자원을 구별한다. messageSource

bean은 외부화될 메시지 문자열을 허용하고 국제화를 잘 지원한다. 국제화된 메시지를 생성하기 위한

규칙은 국제화된 view와 테마를 생성하기 위한 규칙과 정확하게 동일하다. 우리는 마지막으로 애플리

케이션 컨텍스트 파일을 보여준다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

Page 32: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<bean id="messageSource"

class="org.springframework.context.support.ResourceBundleMessageSource">

<property name="basename">

<value>messages</value>

</property>

</bean>

<bean id="productValidator" class="ProductValidator" />

<!-- Product -->

<bean id="productFormController" class="ProductFormController">

<property name="validator">

<ref bean="productValidator" />

</property>

</bean>

<!-- other beans as usual -->

</beans>

우리가 애플리케이션을 빌드하고 다시 배치할 때 , product/edit.html 페이지로 가라. 그리고 유효한

종료 날짜를 제공하지만 이름은 채우지 않고 form을 서브밋을 시도해보라. 우리는 적당한 언어로 에

러메시지를 보게될것이다.

Page 33: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

당신은 사용자로부터 새로운 데이터를 얻는 방법을 안다. 하지만 전형적인 애플리케이션에서 당신은

편집을 잘 다룰것이다. 비즈니스 티어로부터 가져온 데이터를 포함하는 command객체를 준비하기 위

한 방법이어야만 한다. 이것은 객체의 구분자를 명시하는 요청 파라미터를 포함하는 edit페이지에 요

청하는 것을 의미한다. 객체는 다음 비즈니스 티어로 호출하여 로드되고 사용자에게 표시된다. 이것을

하기 위해 formBackingObject()메소드를 오버라이드하라.

public class ProductFormController extends SimpleFormController {

// other methods omitted for clarity

protected Object formBackingObject(HttpServletRequest request) throws Exception {

Product command = new Product();

int productId = RequestUtils.getIntParameter(request, "productId", 0);

if (productId != 0) {

// load the product

command.setProductId(productId);

command.setName("loaded");

}

Page 34: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

return command;

}

}

요청 파라미터 productId가 2인 것을 가지고 edit.html에 요청을 보낼 때, command객체의 이름 프라

퍼티는 로드되기 위해 셋팅된다. 물론, controller내 Product 객체의 인스턴스를 생성하는 대신에, 우

리는 productId에 의해 구별되는 객체를 전달하기 위한 비즈니스 티어를 사용한다. 다른 controller는

form서브밋과 유효성체크를 처리하기 위해 같은 규칙을 따른다. 그래서 당신은 나중에 그것들을 서술

할 필요가 없다. Spring샘플 애플리케이션은 다른 controller의 사용을 설명한다.

Exploring the AbstractWizardFormControllerExploring the AbstractWizardFormControllerExploring the AbstractWizardFormControllerExploring the AbstractWizardFormController

AbstractFormController의 매우 유용한 하위클래스는 페이지의 마법사 같은 형식의 시리즈를 구현하

도록 허용하는 AbstractWizardFormController이다. 이 controller구현물을 사용하는 방법을 보여주기

위해, 우리는 JSP페이지의 간단한 세트(step1.jsp, step2.jsp, finish.jsp)를 가지고 시작한다. 이러한

JSP페이지의 코드는 위 edit.jsp페이지내에서 사용된 코드와 크게 다르지 않다.

// step1.jsp

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%>

<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>

<html>

<head>

<c:set var="css"><spring:theme code="css"/></c:set>

<c:if test="${not empty css}"><link rel="stylesheet"

href="<c:url value="${css}"/>" type="text/css" /></c:if>

</head>

<body>

<form action="wizard.html?_target1" method="post">

<input type="hidden" name="_page" value="0">

<table>

<tr>

<td>Name</td>

<td><spring:bind path="command.name">

<input name="name" value="<c:out value="${status.value}"/>">

Page 35: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<span class="error"><c:out value="${status.errorMessage}"/></span>

</spring:bind>

</td>

</tr>

<tr>

<td></td>

<td><input type="submit" value="Next"></td>

</tr>

</table>

</form>

</body>

</html>

// step2.jsp

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%>

<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>

<html>

<head>

<c:set var="css"><spring:theme code="css"/></c:set>

<c:if test="${not empty css}"><link rel="stylesheet"

href="<c:url value="${css}"/>" type="text/css" /></c:if>

</head>

<body>

<form action="wizard.html?_target2" method="post">

<input type="hidden" name="_page" value="1">

<table>

<tr>

<td>Expiration Date</td>

<td><spring:bind path="command.expirationDate">

<input name="expirationDate"

value="<c:out value="${status.value}"/>">

<span class="error"><c:out value="${status.errorMessage}"/></span>

</spring:bind>

</td>

Page 36: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

</tr>

<tr>

<td></td>

<td><input type="submit" value="Next"></td>

</tr>

</table>

</form>

</body>

</html>

// finish.jsp

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%>

<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>

<html>

<head>

<c:set var="css"><spring:theme code="css"/></c:set>

<c:if test="${not empty css}"><link rel="stylesheet"

href="<c:url value="${css}"/>" type="text/css" /></c:if>

</head>

<body>

<form action="wizard.html?_finish" method="post">

<input type="hidden" name="_page" value="2">

<table>

<tr>

<td>Register now?</td>

<td><c:out value="${command}"/></td>

</tr>

<tr>

<td></td>

<td><input type="submit" value="Next"></td>

</tr>

</table>

</form>

</body>

</html>

Page 37: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

당신이 보는것처럼 step1.jsp와 step2.jsp 페이지는 Product도메인 객체의 인스턴스인 command객체

의 name과 expirationDate 프라퍼티를 간단하게 활성화한다.

지금 우리는 JSP페이지를 셋업하고, AbstractWizardFormController 가 마법사의 페이지 흐름을 제어

하기 위한 요청 파라미터를 사용하는 방법을 봐야만 한다. 이러한 파라미터는 아래와 같다.

파라미터파라미터파라미터파라미터 상세설명상세설명상세설명상세설명

_target<value> Value는 현재 페이지가 서브밋되고 유효할 때 이거나 allowDirtyForward또는

allowDirtyBack프라퍼티가 true로 셋팅될 때 controller가 가는 pages[] 프라퍼티내

인덱스를 명시하는 숫자이다.

_finish 이 파라미터가 명시되면, AbstractWizardFormController는 processFinish()메소드를

호출하고 세션으로부터 command객체를 제거한다.

_cancel 이 파라미터가 명시되면, AbstractWizardFormController는 processCancel()메소드를

호출하고 만약 오버라이드되지 않는다면, 세션으로부터 command객체를 제거한다.

만약 이 메소드를 오버라이드하기로 선택한다면, super()메소드를 호출하거나 세션

으로부터 수동으로 command객체를 제거하는 것을 잊지 말라.

_page 이 파라미터(언제나 <input type="hidden" name="_page" value=""> 처럼 명시되는)

는 pages[] 프라퍼티내 페이지의 인덱스를 명시한다.

지금 우리는 마법사 단계로부터 JSP페이지를 가졌고, 우리는 아래처럼 AbstractWizardFormController

의 하위클래스처럼 RegistrationController를 구현할 필요가 있다.

public class RegistrationController extends AbstractWizardFormController {

public RegistrationController() {

setPages(new String[] { "registration-step1", "registration-step2", "registration-

finish" });

setSessionForm(true);

setCommandClass(Product.class);

}

protected ModelAndView processFinish(HttpServletRequest request, HttpServletResponse

response, Object command,

BindException errors) throws Exception {

Product product = (Product) command;

Page 38: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

System.out.println("Register " + product);

return null;

}

protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)

throws Exception {

SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

dateFormat.setLenient(false);

binder.registerCustomEditor(Date.class, null, new CustomDateEditor(dateFormat,

false));

}

protected void validatePage(Object command, Errors errors, int page, boolean finish) {

getValidator().validate(command, errors);

}

}

위 코드는 AbstractWizardFormController 하위 클래스의 가장 간단한 구현물을 표현한다. 기술적으로,

우리가 구현해야만 하는 모든 것은 processFinish()메소드이다. 하지만 우리의 경우, 우리는 Date클래

스를 위해 사용자정의 편집기를 등록할 필요가 있다. 마지막으로, 우리는 Product.class를 위한

commandClass프라퍼티를 셋팅하길 원한다. 우리는 bean정의에서 pages와 sessionForm프라퍼티를

셋팅할수 있다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="publicUrlMapping"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="interceptors">

<list>

<ref local="bigBrotherHandlerInterceptor" />

</list>

</property>

Page 39: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<property name="mappings">

<props>

<!-- other props omitted -->

<prop key="/registration/wizard.html">

registrationController

</prop>

</props>

</property>

</bean>

<bean id="registrationController" class="RegistrationController">

<property name="validator">

<ref bean="productValidator" />

</property>

</bean>

</beans>

우리는 step1.jsp, step2.jsp 그리고 finish.jsp페이지를 위해 맵핑을 생성하지는 않았다. 우리는

/registration/wizard.html 를 위한 하나의 맵핑을 생성하는 대신에, registrationController bean에 의해

다루어진다. 우리는 또한 productValidator bean을 위한 registrationController의 validator프라퍼티를

셋팅한다. 우리는 각각의 페이지를 체크할수 있는 것을 보여주기 위해 validatePage()메소드내

validator프라퍼티를 사용한다. 우리가 선택한 구현물은 AbstractWizardFormController내 디폴트 구현

물과 같지만, 만약 우리가 원한다면, 우리는 다음 페이지로 이동하는 것을 사용자에게 허용한다.

AbstractWizardFormController는 processFinish()메소드를 호출하기 전에 유효성체크를 수행한다.

우리가 제공하는 AbstractWizardFormController의 설명은 매우 간단하다. 하지만 당신의 애플리케이

션내 AbstractWizardFormController 하위클래스를 사용하기 위해 결정한다면 시작점으로는 매우좋다.

File UploadFile UploadFile UploadFile Upload

Spring은 MultipartResolver 인터페이스의 구현물을 통해 파일업로드를 다룬다. 특히, Spring은 COS

FileUpload와 Commons FileUpload를 위한 지원을 가진다. 디폴트로 명시되는 multipartResolver

bean은 없다. 그래서 당신이 Commons나 COS구현물이나 당신 자신만의 구현물을 사용하길 원한다

면, 당신은 다음처럼 Spring애플리케이션 컨텍스트내 multipartResolver bean을 선언해야만 한다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¢¯

"http://www.springframework.org/dtd/spring-beans.dtd">

Page 40: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<beans>

<bean id="multipartResolver"

class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

<property name="maxUploadSize">

<value>100000</value>

</property>

</bean>

<bean id="multipartResolver"

class="org.springframework.web.multipart.cos.CosMultipartResolver">

<property name="maxUploadSize">

<value>100000</value>

</property>

</bean>

<!-- other beans as usual -->

</beans>

당신은 하나의 multipartResolver bean을 선택할수 있다는 것을 잊지 말라. 이것은 당신이 bean을 선

언할 때 사용할 하나를 선택해야만 한다는 것을 의미한다. multipartResolver bean이 설정되었을 때,

Spring은 form데이터를 byte[] 배열로 변형하기 위해 multipart/form-data 로 인코딩된 요청을 다루

는 방법을 안다. 새롭게 설정된 multipartResolver 작업을 보여주기 위해, 우리는 ProductImageForm

과 ProductImageFormController 클래스를 생성할것이다. 두번째것이 이미지 이름과 내용을 위한 프

라퍼티를 포함하는 동안 첫번째것은 SimpleFormController를 확장하고 이미지 업로드를 다룬다.

public class ProductImageForm {

private String name;

private byte[] contents;

public byte[] getContents() {

return contents;

}

public void setContents(byte[] contents) {

Page 41: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

this.contents = contents;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

이것은 name와 contents프라퍼티를 나타내는 간단한 자바빈이다.

import java.net.BindException;

public class ProductImageFormController extends SimpleFormController {

public ProductImageFormController() {

super();

setCommandClass(ProductImageForm.class);

setFormView("products-image");

}

protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,

Object command,

BindException errors) throws Exception {

ProductImageForm form = (ProductImageForm) command;

System.out.println(form.getName());

byte[] contents = form.getContents();

for (int i = 0; i < contents.length; i++) {

System.out.print(contents[i]);

}

return new ModelAndView("products-index-r");

Page 42: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

}

protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)

throws Exception {

binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());

}

}

ByteArrayMultipartResolver 클래스는 멀티파트 스트림(multipart stream)의 내용을 파싱하고 이것

을 byte[] 배열로 반환하기 위해 애플리케이션 컨텍스트파일로 부터 multipartResolver bean을 사

용한다. 파일 업로드를 위한 JSP페이지를 코딩할때는 주의하라. 가장 흔한 에러는 form요소의

enctype 속성을 잊어버리는 것이다.

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%>

<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>

<html>

<head>

<c:set var="css"><spring:theme code="css"/></c:set>

<c:if test="${not empty css}"><link rel="stylesheet"

href="<c:url value="${css}"/>" type="text/css" /></c:if>

</head>

<body>

<form action="image.html" method="post" enctype="multipart/form-data">

<table>

<tr>

<td>Name</td>

<td><spring:bind path="command.name">

<input name="name" value="<c:out value="${status.value}"/>">

<span class="error"><c:out value="${status.errorMessage}"/></span>

</spring:bind>

</td>

</tr>

<tr>

Page 43: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<td>Image</td>

<td><spring:bind path="command.contents">

<input name="contents" type="file">

<span class="error"><c:out value="${status.errorMessage}"/></span>

</spring:bind>

</td>

</tr>

<tr>

<td></td>

<td><input type="submit"></td>

</tr>

</table>

</form>

</body>

</html>

당신이 보는것처럼, JSP페이지는 enctype 속성을 제외하고는 표준적인 HTML페이지이다. 우리는

views.properties파일내 view처럼 JSP페이지를 명시하는 것을 잊어서는 안된다.

* * * * Using Spring MVC in the Sample ApplicationUsing Spring MVC in the Sample ApplicationUsing Spring MVC in the Sample ApplicationUsing Spring MVC in the Sample Application

Page 44: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

우리는 URL맵핑을 언급하며 시작할것이다. 요청 URL을 애플리케이션내 controller로 맵핑하기 위해,

우리는 하나의 간단한 SimpleUrlHandlerMapping bean을 사용한다. 이 bean은 적당한 controller를 위

해 모든 *.html 과 *.tile 요청을 맵핑한다. bean정의는 아래와 같다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="urlMapping"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<!-- Index -->

<prop key="/index.html">indexController</prop>

<!-- Entry -->

<prop key="/entry/view.html">entryController</prop>

<prop key="/entry/delete.html">entryController</prop>

<prop key="/entry/edit.html">editEntryFormController</prop>

<!-- Comment -->

<prop key="/comment/index.html">commentController</prop>

<prop key="/comment/delete.html">commentController</prop>

<prop key="/comment/view.html">commentController</prop>

<prop key="/comment/edit.html">editCommentFormController</prop>

<!-- Audit -->

<prop key="/admin/audit/*">auditController</prop>

<!-- Tiles -->

<prop key="/tiles/menu.html">menuTileController</prop>

<!-- Users -->

<prop key="/admin/users/index.html">usersController</prop>

<!-- Login -->

<prop key="/security/login.html">loginController</prop>

<!-- Attachments -->

<prop key="/attachment/*">attachmentController</prop>

</props>

</property>

Page 45: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

</bean>

</beans>

지역화된 유효성체크 메시지와 일반적인 메시지를 지원하기 위해, 우리는

ResourceBundleMessageSource bean을 설정한다. 우리는 애플리케이션의 생명주기내 메시지를 리로

드할 필요가 없기 때문에, ReloadableResourceBundleMessageSource 가 아닌 이 구현물을 선택한다.

messageSource bean의 정의는 아래에서 보여진다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="messageSource"

class="org.springframework.context.support.ResourceBundleMessageSource">

<property name="basename">

<value>messages</value>

</property>

</bean>

</beans>

여기서, 우리는 두개의 메시지 프라퍼티 파일을 생성했다. 하나는 Czech언어를 저장하는

messages_cs.properties이고, 다른 하나는 영어로 된 텍스트를 포함하거나 검색을 실패한 프라퍼티

파일을 표현하는 messages.properties이다.

그리고 유효성 체크를 위해 CommentValidator 와 EntryValidator를 구현한다.

import org.springframework.validation.Errors;

import org.springframework.validation.Validator;

public class CommentValidator implements Validator {

public boolean supports(Class clazz) {

return Comment.class.isAssignableFrom(clazz);

// return clazz.isAssignableFrom(Comment.class);

}

public void validate(Object obj, Errors errors) {

Page 46: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

Comment comment = (Comment) obj;

if (comment.getSubject() == null || comment.getSubject().length() == 0) {

errors.rejectValue("subject", "required", null, "required");

}

if (comment.getBody() == null || comment.getBody().length() == 0) {

errors.rejectValue("body", "required", null, "required");

}

}

}

우리는 첨부의 업로드를 지원하기 위한 Comment도메인 클래스의 하위클래스를 생성해야만 하기 때

문에 Comment.class.isAssignableForm(clazz)를 사용하여 supports()를 구현한다. 우리는 대개의

Spring bean처럼 validators를 정의한다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="commentValidator" class="CommentValidator" />

<bean id="editCommentFormController"

class="EditCommentFormController">

<property name="blogManager">

<ref bean="blogManager" />

</property>

<property name="validator">

<ref local="commentValidator" />

</property>

</bean>

<bean id="entryValidator" class="EntryValidator" />

<bean id="editEntryFormController"

class="EditEntryFormController">

<property name="blogManager">

<ref bean="blogManager" />

</property>

Page 47: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

<property name="validator">

<ref local="entryValidator" />

</property>

</bean>

</beans>

여기서 우리는 blogManager프라퍼티에 접근하는 하위클래스를 주는 편리한 클래스의 세트를 생성한

다. 우리가 구현하는 편리한 클래스는 AbstractBlogManagerController,

AbstractBlogManagerFormController 그리고 AbstractBlogManagerMultiactionController 이다. 이러한

추상 클래스는 blogManager프라퍼티가 BlogManager구현물의 인스턴스로 셋팅되도록 하기 위해

InitializingBean 인터페이스를 구현한다. 게다가 AbstractBlogManagerController는 AbstractController

의 하위클래스가 아니다. 이것은 Controller 인터페이스만을 구현하고 그래서 좀더 가볍다.

AbstractBlogManagerMultiactionController의 사용법은 다음과 같다.

import java.util.HashMap;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.bind.RequestUtils;

import org.springframework.web.servlet.ModelAndView;

public class EntryController extends AbstractBlogManagerMultiactionController {

public ModelAndView handleView(HttpServletRequest request, HttpServletResponse response)

throws Exception {

int entryId = RequestUtils.getRequiredIntParameter(request, "entryId");

Entry e = getBlogManager().getEntry(entryId);

Map model = new HashMap();

model.put("entry", e);

return new ModelAndView("entry-view", model);

}

public ModelAndView handleDelete(HttpServletRequest request, HttpServletResponse response)

throws Exception {

Page 48: Ch 17. Web Application with Spring MVC · 2015-01-22 · 이 타입에서 JSP페이지는 애플리케이션의 핵심이 된다. 그것은 컨트롤 로직과 표현(presentation)모두를

boolean confirm = RequestUtils.getIntParameter(request, "confirm", 0) == 1;

int entryId = RequestUtils.getRequiredIntParameter(request, "entryId");

if (confirm) {

getBlogManager().deleteEntry(entryId,

SessionSecurityManager.getUser(request));

return new ModelAndView("entry-deleted");

} else {

Map model = new HashMap();

model.put("entry", getBlogManager().getEntry(entryId));

return new ModelAndView("entry-delete", model);

}

}

}

요구되는 프라퍼티가 셋팅되는 것을 확인하기 위해 InitializingBean 인터페이스를 구현할 필요는 없다.

사실, afterPropertiesSet()이 final로 구현되었다. 우리는 표준적인 웹 애플리케이션내에서 기대하는 방

법으로 보안을 구현하지는 않았다. 대신 우리는 사용자 확인을 하기 위해 가장 기본적인 지원을 수행

하는 정적 메소드를 포함하는 SessionSecurityManager 를 생성한다. 보안코드를 확장하기 위해, 당신

은 HandlerInterceptor 을 구현할수 있다. 사용자 로그인 없이 접근가능한 URL중 몇몇을 허용하기 위

해, 당신은 두개의 bean으로 urlMapping bean을 분리했다. 하나는 제안을 가지지 않는 맵핑을 포함

하고, 다른것은 HandlerInterceptor 구현물과 로그인한 사용자를 요구하는 맵핑을 포함한다.