편리한 웹 개발을 지원하기 위해 추가적으로, Spring은 JSR-168 포틀릿 개발을 지원한다. 가능한한 많이, 포틀릿 MVC 프레임워크는 웹 MVC 프레임워크를 대표하는 이미지이고 같이 참조하는 view추상화와 통합 기술을 사용한다. 이 장을 계속 보기 전에 Chapter 13, 웹 MVC framework 와 Chapter 14, 통합 뷰 기술들를 먼저보라.
![]() | Note |
---|---|
Spring MVC의 개념이 Spring 포틀릿 MVc와 같을때, 여기엔 JSR-168 포틀릿의 유일한 워크플로우에 의해 생성되는 몇가지 눈에 띄는 차이점이 있다는 것을 명심하라. |
서블릿 워크플로우와는 다른 포트릿 워크플로우내 가장 핵심적인 방법은 포틀릿에 대한 요청이 두가지 다른 단계(action과 render단계)를 가진다. action단계는 데이터베이스내 변경사항처럼 오직 한번만 실행되고 "backend" 변경이나 action이 발생하는 곳에서 실행된다. render단계는 표시가 갱신되는 매번 사용자에게 표시되는 것을 만든다. 여기서의 중대한 점은 하나의 종합적인 요청을 위한 것이다. action단계는 오직 한번만 실행되지만 render단계는 여러번 실행된다. 이것은 시스템의 영속적인 상태와 사용자에게 보여주는 것을 생성하는 활동을 변경하는 활동(activities)간의 명백한 구분을 제공(요구)한다.
포틀릿 요청의 두 단계는 JSR-168 개발의 실제 강력함중의 하나이다. 예를 들어, 동적인 검색 결과는 사용자가 검색을 명시적으로 다시 수행하지 않고 대개 업데이트할수 있다. 대부분의 다른 포틀릿 MVC 프레임워크는 개발자로부터 두 단계를 완벽하게 숨기도록 시도한다. 그리고 가능한한 전통적인 서블릿 개발처럼 보이도록 한다. — 우리는 이 접근법이 포틀릿을 사용하는 중요한 이익중 하나를 제거한다고 생각한다. 그래서 두 단계의 분리는 Spring 포틀릿 MVC 프레임워크도처에 유지했다. 이 접근법의 중요한 표현은 MVC 클래스의 서블릿 버전이 요청을 다루는 두개의 메소드(action단계를 위한 메소드와 render단계를 위한 메소드)를 가지는 것이다. 예를 들어, AbstractController 의 서블릿 버전은 handleRequestInternal(..) 메소드를 가진다. AbstractController의 포틀릿 버전은 handleActionRequestInternal(..) 메소드와 handleRenderRequestInternal(..) 메소드를 가진다.
프레임워크는 웹 프레임워크내 DispatcherServlet가 하는것처럼 설정가능한 핸들러 맵핑과 view해석(resolution)을 가지고 요청을 핸들러에 분배하는 DispatcherPortlet에 대해 디자인되었다. 파일 업로드는 같은 방법으로 제공된다.
로케일 해석과 테마 해석은 포틀릿 MVC 에서 제공되지 않는다. - 이러한 영역은 포틀릿/포틀릿-컨테이너의 범위에 있고 Spring레벨에서는 적절하지 않다. 어쨌든, 로케일(메시지의 국제화와 같은)에 의존하는 Spring내 모든 기법은 DispatcherPortlet이 DispatcherServlet와 같은 방법으로 현재 로케일을 나타내기 때문에 적당하게 기능화될것이다.
디폴트 핸들러는 두개의 메소드만을 제공하는 여전히 매우 간단한 Controller 인터페이스이다.
void handleActionRequest(request,response)
ModelAndView handleRenderRequest(request,response)
프레임워크 AbstractController, SimpleFormController 등등과 같이 대부분 같은 컨트롤러 구현물 구조를 포함한다. 데이타 바인딩, command 객체 사용법, 모델 핸들링, 그리고 view해석은 서블릿 프레임워크에서 모두 같다.
서블릿 프레임워크의 모든 view표현 기능은 ViewRendererServlet라고 명명된 특별한 다리 역활의 서블릿을 통해 직접 사용된다. 이 서블릿을 사용하여, 포틀릿 요청은 서블릿 요청으로 변환되고 view는 전체적으로 대개의 서블릿 내부구조를 사용하여 표시될수 있다. 이것은 JSP, Velocity등등 존재하는 모든 표시자(renderer)가 포틀릿내 사용될수 있다는 것을 의미한다.
Spring 포틀릿 MVC는 생명주기가 현재 HTTP요청또는 HTTP Session에 범위화되는 bean을 지원한다. 이것은 Spring 포틀릿 MVC자체의 특별한 기능일뿐 아니라 Spring 포틀릿 MVC가 사용하는 WebApplicationContext 컨테이너이다. bean범위는 Section 3.5.3, “The other scopes”에서 상세히 언급된다.
![]() | Note |
---|---|
Spring배포판은 Spring 포틀릿 MVC프레임워크의 모든 기능과 함수를 보여주는 완벽한 Spring 포틀릿 MVC샘플 애플리케이션을 가진다. 'petportal' 애플리케이션은 Spring 배포판의 samples/petportal' 디렉토리에서 찾을수 있다. |
포틀릿 MVC는 요청-지향의 웹 MVC프레임워크이다. 요청을 컨트롤러에 분배하는 포틀릿에 대해 디자인되었고 포틀릿 애플리케이션의 개발 기능을 제공한다. Spring의 DispatcherPortlet는 어쨌든, 그것보다 더 많은 것을 한다. 이것은 Spring의 ApplicationContext와 완전하게 통합되었고 Spring이 가지는 다른 모든 기능을 사용할수 있도록 해준다.
보통의 포틀릿처럼, DispatcherPortlet이 웹 애플리케이션의 portlet.xml내 선언되었다.
<portlet> <portlet-name>sample</portlet-name> <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>view</portlet-mode> </supports> <portlet-info> <title>Sample Portlet</title> </portlet-info> </portlet>
DispatcherPortlet이 현재 설정될 필요가 있다.
포틀릿 MVC프레임워크에서, 각각의 DispatcherPortlet은 가장 상위의 WebApplicationContext내 이미 정의된 모든 bean을 상속하는 자체적인 WebApplicationContext를 가진다. 이렇게 상속된 bean은 포틀릿 특성의 scope에서 오버라이드될수 있고 새로운 scope 특성을 가진 bean은 주어진 포틀릿 인스턴스에 로컬로 정의될수 있다.
DispatcherPortlet 의 초기화중 프레임워크는 웹 애플리케이션내 WEB-INF 디렉토리내 [portlet-name]-portlet.xml 라는 이름의 파일을 찾을것이고 거기에 정의된 bean을 생성할것이다(전역 scope에서 같은 이름을 가지고 정의된 bean의 정의를 오버라이드한다.).
DispatcherPortlet 에 의해 사용된 설정 위치는 포틀릿 초기화 파라미터를 통해 변경될수 있다(상세한 사항은 아래에서 보라).
Spring DispatcherPortlet은 요청을 처리하고 적절한 view로 표시하는 것을 가능하게 하기 위해 몇가지 특별한 bean을 가진다. 여기엔 Spring 프레임워크에 포함된 bean이 있고 WebApplicationContext 에서 설정될수 있다. 각각의 bean은 아래에서 좀더 상세하게 언급된다. 우리는 지금 그것들을 언급할것이다. DispatcherPortlet에 대해 좀더 얘기해보자. 대부분이 bean을 위해, 디폴트가 제공되기 때문에 당신은 설정에 대해 걱정할 필요가 없다.
Table 16.1. WebApplicationContext 내 특별한 bean
표시 | 설명 |
---|---|
핸들러 팹핑 | (Section 16.5, “핸들러 맵핑”) 어떤 규칙에 일치한다면 수행될 전처리(pre-) 와 후처리(post-) 프로세서와 컨트롤러의 목록(예를 들어, 일치되는 포틀릿은 컨트롤러와 함께 명시된다.) |
컨트롤러 | (Section 16.4, “컨트롤러”) MVC 의 일부처럼 실질적인 기능(적어도 기능에 접근하는)을 제공하는 bean |
view 해석자(resolver) | (Section 16.6, “View와 View해석하기”) view이름을 view정의에 해석하는 능력 |
multipart 해석자(resolver) | (Section 16.7, “Multipart (파일 업로드) 지원”) HTML폼으로부터 파일 업로드를 처리하는 기능을 제공 |
핸들러 예외 해석자(resolver) | (Section 16.8, “예외 다루기”) view에 대한 예외를 맵핑하거나 좀더 복잡한 예외 핸들링 코드를 구현하는 기능을 제공 |
DispatcherPortlet이 사용하기 위해 셋업되고 요청이 특정 DispatcherPortlet를 위해 들어올때, 이것은 요청을 처리하기 시작한다. 아래 목록은 DispatcherPortlet에 의해 다루어진다는 것을 통해 요청을 완벽하게 처리하는 것을 언급한다.
PortletRequest.getLocale()에 의해 반환되는 로케일은 요청을 처리(view를 표시하고 데이터를 준비하는 등등)할때 사용할 로케일을 프로세스가 해석하여 요청에 반영한다.
multipart 해석자가 명시되고 이것이 ActionRequest 라면, 요청은 multipart를 위해 검사되고 만약 발견된다면, 프로세스내 다른 요소에 의해 처리되기 위해 MultipartActionRequest에 포장된다(multipart 핸들링에 대한 더 많은 정보를 위해 Section 16.7, “Multipart (파일 업로드) 지원” 를 보라)
적절한 핸들러가 검색되었다. 핸들러가 발견된다면, 핸들러에 관련된 수행체인(execution chain - 전처리 프로세서, 후처리 프로세서, 컨트롤러)은 모델을 준비하기 위해 수행될것이다.
모델이 반환되면, view를 표시된다, view해석자를 사용하는 것은 WebApplicationContext로 설정된다. 만약 모델이 반환되지 않는다면(예를 들어, 보안적인 이유로, 전처리 또는 후처리 프로세서가 요청을 가로챌수 있다), 요청이 이미 완수된 이래 어떠한 view도 표시되지 않는다.
요청의 처리중 던져지는 예외는 WebApplicationContext내 선언된 예외 해석자 핸들러를 가진다. 이러한 예외 해석자를 사용하여 당신은 예외가 던져지는 것과 같은 경우 사용자정의 행위를 정의할수 있다.
당신은 portlet.xml파일내 컨텍스트 파라미터나 포틀릿 init 파라미터를 추가하여 Spring의 DispatcherPortlet를 사용자정의 할수 있다. 가능한 값은 아래와 같다.
Table 16.2. DispatcherPortlet 초기화 파라미터
파라미터 | 설명 |
---|---|
contextClass | WebApplicationContext를 구현한 클래스는 포틀릿에 의해 사용되는 컨텍스트를 인스턴스화하기 위해 사용될것이다. 파라미터가 명시되지 않는다면, XmlPortletApplicationContext가 사용될것이다. |
contextConfigLocation | 컨텍스트가 발견되는 위치를 표시하기 위해 컨텍스트 인스턴스에 전달(contextClass에 의해 명시되는)되는 문자열. 문자열은 잠재적으로 다중 컨텍스트(이 경우 다중 컨텍스트 위치, 두개로 정의된 bean, 마지막것이 우선권을 가진다)를 지원하기 위해 다중 문자열로 나누어진다(구분자로 콤마를 사용). |
namespace | WebApplicationContext의 명명공간. 디폴트는 [portlet-name]-portlet 이다. |
viewRendererUrl | DispatcherPortlet의 URL은 ViewRendererServlet(Section 16.3, “ViewRendererServlet”를 보라) 인스턴스에 접근할수 있다. |
포틀릿 MVC내 랜더링 처리는 웹 MVC보다는 좀더 복잡하다. Spring 웹 MVC로부터 모든 View기술을 다시 사용하기 위해, PortletRequest / PortletResponse를 HttpServletRequest / HttpServletResponse로 변환하고 View의 render 메소드를 호출한다. 이것을 하기 위해, DispatcherPortlet은 이러한 목적을 위해 존재하는 특별한 서블릿인 ViewRendererServlet을 사용한다.
DispatcherPortlet 랜더링을 위해, 다음처럼 웹 애플리케이션을 위한 web.xml 파일내 ViewRendererServlet 인스턴스를 명시해야만 한다.
<servlet> <servlet-name>ViewRendererServlet</servlet-name> <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ViewRendererServlet</servlet-name> <url-pattern>/WEB-INF/servlet/view</url-pattern> </servlet-mapping>
실제 랜더링을 수행하기 위해, DispatcherPortlet은 다음을 수행한다.
DispatcherServlet이 사용하는 WEB_APPLICATION_CONTEXT_ATTRIBUTE key와 같은 속성처럼 WebApplicationContext을 요청으로 바인드한다.
Model 과 View 객체를 ViewRendererServlet에 사용가능하도록 만들기 위해 요청에 바인드한다.
PortletRequestDispatcher를 생성하고 ViewRendererServlet에 맵핑되는 /WEB- INF/servlet/view URL을 포함한다.
ViewRendererServlet 은 적절한 인자를 가진 View의 render 메소드를 호출할수 있다.
ViewRendererServlet 을 위한 실제 URL은 DispatcherPortlet의 viewRendererUrl 설정 파라미터를 사용하여 변경될수 있다.
포틀릿 MVC내 컨트롤러는 웹 MVC컨트롤러와 매우 유사하다. 포팅 코드는 간단할것이다.
포틀릿 MVC 컨트롤러 구조를 위한 기본은 아래에 목록화된 org.springframework.web.portlet.mvc.Controller 인터페이스이다.
public interface Controller { /** * Process the render request and return a ModelAndView object which the * DispatcherPortlet will render. */ ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response) throws Exception; /** * Process the action request. There is nothing to return. */ void handleActionRequest(ActionRequest request, ActionResponse response) throws Exception; }
당신이 볼수 있는것처럼, 포틀릿 Controller 인터페이스는 포틀릿 요청(action요청과 render요청)의 두 단계를 다루는 두개의 메소드를 요구한다. action단계는 action요청을 다룰수 있고 render단계는 render단계를 다루고 적절한 모델과 view를 반환한다. Controller인터페이스가 추상적인 반면에, Spring 포틀릿 MVC는 당신이 필요한 많은 기능을 포함하는 많은 컨트롤러를 제공하고 Spring 웹 MVC의 컨트롤러와 매우 유사하다. Controller 인터페이스는 모든 컨트롤러에서 요구되는 가장 공통적인 기능( - action요청을 다루고 render요청을 다루고 모델과 view를 반환하는)을 정의한다.
물론, Controller 인터페이스는 충분하지 않다. 기초적인 구조를 제공하기 위해, 모든 Spring 포틀릿 MVC의 Controller는 Spring의 ApplicationContext에 접근하고 캐시를 제어하는 클래스인 AbstractController로부터 상속된다.
Table 16.3. AbstractController에 의해 제공되는 기능
파라미터 | 설명 |
---|---|
requireSession | 이 Controller 가 작동하기 위한 session을 필요로 할지에 대한 표시. 이 기능은 모든 컨트롤러에 제공된다. 컨트롤러가 요청을 받을때 session이 존재하지 않는다면, 사용자는 SessionRequiredException을 사용하여 정보를 볼수 있다. |
synchronizeSession | 사용자 session에서 동기화되는 컨트롤러에 의해 다루어지길 원한다면 이 파라미터를 사용하라. 좀더 다양하게 설정하기 위해, 컨트롤러를 확장하는 것은 이 변수를 명시한다면 사용자의 session에서 동기화될 handleRenderRequestInternal(..)과 handleActionRequestInternal(..) 메소드를 오버라이드할것이다. |
renderWhenMinimized | 포틀릿이 최소화된 상태에서 view을 실제로 표시하기 위한 컨트롤러를 원한다면, 이 값을 true로 셋팅하라. 디폴트에 의해, 이것은 false로 셋팅되기 때문에 최소화된 상태의 포틀릿은 어떠한 컨텐츠로 표시하지 않는다. |
cacheSeconds | 컨트롤러가 포틀릿을 위해 정의된 디폴트 캐시 만료를 오버라이드하기 원한다면, 여기에 양수의 값을 명시하라. 디폴트에 의해 이것은 디폴트 캐시를 변경하지 않는 -1로 셋팅된다. 0으로 셋팅하면 결코 캐시되지 않을것이다. |
requireSession 과 cacheSeconds 프라퍼티는 실제로 PortletContentGenerator(AbstractController의 수퍼클래그인)의 일부이지만 완전한 설명을 위해 여기에 포함시켰다.
컨트롤러를 위한 기본클래스처럼 AbstractController를 사용(다른 많은 컨트롤러가 당신을 위해 많은 작업을 이미 구현하고 있기 때문에, 이를 추천하지는 않는다)할때, 당신은 로직을 구현하고 ModelAndView 객체(이 경우 handleRenderRequestInternal)를 반환하는 handleActionRequestInternal(ActionRequest, ActionResponse) 메소드나 handleRenderRequestInternal(RenderRequest, RenderResponse) 메소드를 오버라이드해야만 한다.
handleActionRequestInternal(..) 과 handleRenderRequestInternal(..)의 디폴트 구현물은 PortletException을 던진다. 이것은 JSR-168 스펙 API의 GenericPortlet의 행위와 일치한다. 그래서 당신의 컨트롤러가 다루고자 하는 메소드를 오버라이드할 필요가 있다.
이것은 웹 애플리케이션 컨텍스트에서 클래스와 선언으로 구성된 간단한 예제이다.
package samples; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import org.springframework.web.portlet.mvc.AbstractController; import org.springframework.web.portlet.ModelAndView; public class SampleController extends AbstractController { public ModelAndView handleRenderRequestInternal( RenderRequest request, RenderResponse response) throws Exception { ModelAndView mav = new ModelAndView("foo"); mav.addObject("message", "Hello World!"); return mav; } } <bean id="sampleController" class="samples.SampleController"> <property name="cacheSeconds" value="120"/> </bean>
위 클래스와 웹 애플리케이션 컨텍스트내 선언은 간단한 컨트롤러 작업을 하기 위해 핸들러 맵핑을 셋업(Section 16.5, “핸들러 맵핑”를 보라.)하는 것외에 필요한 모든것이다.
비록 AbstractController를 확장할수 있다고 하나, Spring 포틀릿 MVC는 간단한 MVC애플리케이션내에서 공통적으로 사용되는 기능을 제공하는 많은 수의 구현물을 제공한다.
ParameterizableViewController 는 웹 애플리케이션 컨텍스트(하드코딩된 view명에 필요하지 않은)내 반환될 view명을 명시할수 있다는 것을 제외하고 위 예제와 기본적으로 같다.
PortletModeNameViewController 는 view명으로 포틀릿의 현재 모드를 사용한다. 그래서 당신의 포틀릿이 View모드(이를테면, PortletMode.VIEW)라면, view명으로 "view"를 사용한다.
Spring 포틀릿 MVC는 Spring 웹 MVC처럼 command 컨트롤러의 구조와 정확하게 같다. 컨트롤러는 데이터 객체와 상호작용하기 위한 방법을 제공하고 PortletRequest로부터 명시된 데이터 객체에 파라미터를 동적으로 바인드한다. 당신의 데이터 객체는 프레임워크 특유의 인터페이스를 구현하지 않아서 당신이 원한다면 퍼시스턴트 객체를 직접 다룰수 있다. 컨트롤러로 할수 있는 것의 개요를 알기 위해 command컨트롤러가 사용가능한 것인지 시험해보자.
AbstractCommandController - 자체적인 컨트롤러를 생성하기 위해 사용할수 있는 command컨트롤러. 요청 파라미터를 당신이 명시하는 데이터 객체에 바인딩하는 능력이 있다. 이 클래스는 폼 기능을 제공하지 않는다. 이것은 어쨌든 유효성 체크 기능을 제공하고 요청으로부터 파라미터를 다루는 command객체로 하는 것을 컨트롤러에 명시하도록 한다.
AbstractFormController - 폼 서브밋 지원을 제공하는 추상 컨트롤러. 이 컨트롤러를 사용하여 당신은 폼을 모델화할수 있고 컨트롤러내 가져오는 command객체를 사용하여 그것들을 활성화한다. 사용자가 폼을 채운후에, AbstractFormController 는 필드를 바인드(bind)하고 유효성체크하고 적절한 action을 가지는 컨트롤러에 객체를 넘긴다. 지원되는 기능은 유효하지 않은 폼 서브밋(resubmission), 유효성 체크, 일반적인 폼 워크플로우. 당신은 폼 표현을 위해 사용되는 view와 성공여부를 판단하기 위한 메소드를 구현한다. 폼이 필요하다면 이 컨트롤러를 사용하라. 하지만 view를 명시하기를 원하지 않는다면 당신은 애플리케이션 컨텍스트내 사용자를 보여줄것이다.
SimpleFormController - 관련 command객체로 폼을 생성할때 좀더 많은 지원을 제공하는 AbstractFormController. SimpleFormController는 command객체, 폼을 위한 viewname, 폼 서브밋이 성공했을때 사용자에게 보여주길 원하는 페이지를 위한 viewname 그리고 기타등등을 명시하도록 한다.
AbstractWizardFormController – 여러개의 페이지를 통해 command객체의 내용을 편집하기 위한 마법사-스타일의 인터페이스를 제공하는 AbstractFormController. 다중 사용자 action인 종료(finish), 취소(cancel), 또는 페이지 변경, view로부터 요청 파라미터로 쉽게 명시하는 모든것을 지원한다.
이러한 command 컨트롤러는 굉장히 강력하다. 하지만 그것들을 효과적으로 사용하기 위해서 작동하는 방법을 상세하게 이해해야만 한다. 이 전체적인 구조를 위해 JavaDoc를 주의깊게 보고 사용하기 전에 몇가지 샘플 구현물을 보라.
새로운 컨트롤러를 개발하는 대신에, 존재하는 포틀릿을 사용하고 DispatcherPortlet으로부터 요청을 맵핑하는 것이 가능하다. PortletWrappingController을 사용하여, 다음의 Controller처럼 Portlet을 인스턴스화할수 있다.
<bean id="wrappingController" class="org.springframework.web.portlet.mvc.PortletWrappingController"> <property name="portletClass" value="sample.MyPortlet"/> <property name="portletName" value="my-portlet"/> <property name="initParameters"> <value> config=/WEB-INF/my-portlet-config.xml </value> </property> </bean>
당신이 이러한 포틀릿으로 가는 전처리-프로세스와 후처리-프로세스를 위한 인터셉터를 사용할수 있기 때문에 매우 가치있을수 있다. JSR-168이 어떠한 종류의 필터 기법도 지원하지 않으므로, 다루기 쉽다. 예를들어, 이것은 MyFaces JSF 포틀릿에서 Hibernate OpenSessionInViewInterceptor를 포장하기 위해 사용될수 있다.
핸들러 맵핑을 사용하여 당신은 포틀릿 요청을 적절한 핸들러로 맵핑할수 있다. 여기엔 예를 들어, PortletModeHandlerMapping과 같이 당신이 특별히 사용할수 있는 몇가지 핸들러 맵핑이 있다. 하지만 HandlerMapping의 일반적인 개념부터 살펴보자.
노트 : 우리는 "컨트롤러" 대신에 여기서 "핸들러"의 개념을 사용한다. DispatcherPortlet은 Spring 포틀릿 MVC 자체의 컨트롤러보다는 요청을 처리하기 위한 방법으로 사용되도록 디자인되었다. 핸들러는 포틀릿 요청을 다룰수 있는 객체이다. 컨트롤러는 핸들러의 예이고, 물론 디폴트이다. DispatcherPortlet로 다른 프레임워크를 사용하기 위해, HandlerAdapter의 관련 구현물이 필요한 모든것이다.
기본 HandlerMapping이 제공하는 기능은 요청에 일치하는 핸들러를 포함해야만 하고 요청에 적용되는 핸들러 인터셉터의 목록을 포함하는 HandlerExecutionChain의 전달이다. 요청이 들어오면, DispatcherPortlet은 요청을 조사하고 적절한 HandlerExecutionChain을 가져오도록 핸들러 맵핑을 다룰것이다. DispatcherPortlet은 체인내 핸들러와 인터셉터를 수행할것이다. 이러한 개념은 Spring 웹 MVC와 정확하게 같다.
선택적으로 인터셉터(실제 핸들러가 수행되지 전이나 후에 또는 전후 모두 실행되는)를 포함할수 있는 설정가능한 핸들러 맵핑의 개념은 굉장히 강력하다. 지원되는 많은 기능은 사용자 정의된 HandlerMapping에 포함될수 있다. 핸들러를 선택하는 사용자 정의 핸들러는 들어오는 요청의 포틀릿 모드에 기초할 뿐 아니라 요청에 관련된 session의 명시된 상태에도 기초를 둔다.
Spring 웹 MVC에서, 핸들러 맵핑은 URL에 공통적으로 기반한다. 포틀릿내 URL과 같은 것이 없다면, 맵핑을 제어하기 위한 다른 기법을 사용해야만 한다. 두개의 가장 공통적인 것은 포틀릿 모드와 요청 파라미터이다. 하지만 포틀릿 요청에 대해 사용가능한 것은 사용자 정의 핸들러 맵핑에서 사용될수 있다.
이 부분의 나머지는 Spring 포틀릿 MVC의 가장 공통적으로 사용되는 핸들러 맵핑중 세가지를 언급한다. 그것들은 모두 AbstractHandlerMapping를 확장하고 다음의 프라퍼티를 공유한다.
interceptors: 사용하는 인터셉터의 목록. HandlerInterceptor는 Section 16.5.4, “HandlerInterceptor 추가하기”에서 다루어진다.
defaultHandler: 핸들러 맵핑이 결과적으로 이루어지지 않았을때 사용하기 위한 디폴트 핸들러.
order: order 프라퍼티(org.springframework.core.Ordered 인터페이스를 보라)의 값에 기초를 둠. Spring은 컨텍스트내 사용가능한 핸들러 맵핑을 모두 정렬하고 첫번째 일치되는 핸들러에 적용할것이다.
lazyInitHandlers: 싱글톤 핸들러의 늦은(lazy) 초기화를 허용(prototype 핸들러는 언제나 늦게 초기화된다). 디폴트 값은 false. 이 프라퍼티는 세가지의 구체적인(concrete) 핸들러내 직접 구현된다.
포틀릿의 현재 모드(이를테면, ‘view’, ‘edit’, ‘help’)에 기초하여 들어오는 요청을 맵핑하는 간단한 핸들러 맵핑. 예를 들면:
<bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping"> <property name="portletModeMap"> <map> <entry key="view" value-ref="viewHandler"/> <entry key="edit" value-ref="editHandler"/> <entry key="help" value-ref="helpHandler"/> </map> </property> </bean>
포틀릿 모드의 변경없이 여러개의 컨트롤러를 탐색할 필요가 있다면, 가장 간단한 방법은 맵핑을 제어하는 key처럼 사용되는 요청 파라미터를 가지는 것이다.
ParameterHandlerMapping 은 맵핑을 제어하기 위해 명시된 요청 파라미터의 값을 사용. 파라미터의 디폴트명은 'action'이지만, 'parameterName' 프라퍼티를 사용하여 변경될수 있다.
이 맵핑을 위한 bean설정은 다음과 비슷할것이다.
<bean id="parameterHandlerMapping" class="org.springframework.web.portlet.handler.ParameterHandlerMapping”> <property name="parameterMap"> <map> <entry key="add" value-ref="addItemHandler"/> <entry key="edit" value-ref="editItemHandler"/> <entry key="delete" value-ref="deleteItemHandler"/> </map> </property> </bean>
가장 강력하게 내장된 핸들러 맵핑. PortletModeParameterHandlerMapping은 이전의 두개와 각각의 포틀릿 모드에서 다른 탐색기법을 허용하는 기능을 모두 가진다.
파라미터의 디폴트명은 "action" 이지만 parameterName 프라퍼티를 사용하여 변경될수 있다.
디폴트에 의해, 같은 파라미터 값은 두개의 다른 포틀릿 모드에서 사용되지 않는다. 포털자체가 포틀릿 모드를 변경한다면, 요청은 맵핑내에서 더이상 유효하지 않을것이다. 이 행위는 allowDupParameters 프라퍼티를 true로 셋팅하여 변경될수 있다. 어쨌든 이것이 추천되지는 않는다.
이 맵핑을 위한 bean설정은 다음과 비슷할것이다.
<bean id="portletModeParameterHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping"> <property name="portletModeParameterMap"> <map> <entry key="view"> <!-- 'view' portlet mode --> <map> <entry key="add" value-ref="addItemHandler"/> <entry key="edit" value-ref="editItemHandler"/> <entry key="delete" value-ref="deleteItemHandler"/> </map> </entry> <entry key="edit"> <!-- 'edit' portlet mode --> <map> <entry key="prefs" value-ref="prefsHandler"/> <entry key="resetPrefs" value-ref="resetPrefsHandler"/> </map> </entry> </map> </property> </bean>
이 맵핑은 각각의 모드를 위한 디폴트와 전체 디폴트를 제공할수 있는 PortletModeHandlerMapping앞에서 연결될수 있다.
Spring의 핸들러 맵핑 기법은 당신이 어떤 요청에 명시된 기능을 적용하길 원할때 굉장히 유용한 핸들러 인터셉터의 개념을 가진다. 다시 말해 Spring 포틀릿 MVC는 웹 MVC와 같은 방법으로 이러한 개념을 구현한다.
핸들러 맵핑내 위치한 인터셉터는 org.springframework.web.portlet 패키지의 HandlerInterceptor를 구현해야만 한다. 서블릿 버전처럼, 이 인터페이스는 세개의 메소드를 정의한다. 하나는 실제 핸들러가 수행되기 전에 호출되고(preHandle), 하나는 핸들러가 수행된 후에 호출될것이고(postHandle), 다른 하나는 요청이 종료된후에 호출된다(afterCompletion). 이 세개의 메소드는 모든 전- 그리고 후- 처리를 하기 위해 충분한 유연성을 제공해야만 한다.
preHandle 메소드는 boolean값을 반환한다. 당신은 수행체인의 처리를 중지하거나 지속하기 위해 이 메소드를 사용할수 있다. 이 메소드가 true를 반환할때, 핸들러 수행 체인은 지속될것이다. false를 반환할때, DispatcherPortlet은 인터셉터 자체가 요청을 다루고(이를테면, 적절한 view를 표시하는) 수행 체인내 다른 인터셉터와 실제 핸들러의 수행을 지속하지 않는다.
postHandle 메소드는 오직 RenderRequest에서 호출된다. preHandle 와 afterCompletion 메소드는 ActionRequest 와 RenderRequest 모두에서 호출된다. 하나의 요청 타입을 위한 메소드에서 로직을 수행할 필요가 있다면, 처리하기 전에 어떤 요청인지 체크해야만 한다.
서블릿 패키지처럼, 포틀릿 패키지는 HandlerInterceptorAdapter라고 불리는 구체적인 HandlerInterceptor 구현물을 가진다. 이 클래스는 모든 메소드의 빈 버전을 가져서 당신은 이 클래스로부터 상속할수 있고 당신이 필요할때 하나 또는 두개의 메소드를 구현할수 있다.
포틀릿 패키지는 ParameterHandlerMapping 과 PortletModeParameterHandlerMapping으로 직접 사용될수 있는 ParameterMappingInterceptor 라는 이름의 구체적인 인터셉터를 가진다. 이 인터셉터는 ActionRequest에서 직후의 RenderRequest로 포워드되기 위한 맵핑을 제어하기 위해 사용되는 파라미터를 야기할것이다. 이것은 RenderRequest가 ActionRequest와 같은 핸들러로 맵핑되는 것을 도울것이다. 이것은 인터셉터의 preHandle 메소드에서 수행된다. 그래서 RenderRequest가 맵핑될 위치를 변경하기 위한 핸들러내 파라미터 값을 변경할수 있다.
이 인터셉터가 ActionResponse의 setRenderParameter를 호출하는 것은 이 인터셉터를 사용할때 당신의 핸들러내 sendRedirect 를 호출할수 없다는 것을 의미하는 것을 알라. 외부로 리다이렉트할 필요가 있다면, 당신은 맵핑 파라미터를 수동으로 포워드하거나 이것을 다루기 위해 다른 인터셉터를 작성할 필요가 있을것이다.
이전에 언급된것처럼, Spring 포틀릿 MVC는 Spring 웹 MVC의 모든 view기술을 직접 재사용한다. 이것은 다양한 View 구현물 자체 뿐 아니라 ViewResolver 구현물도 포함한다. 좀더 많은 정보를 위해, Chapter 14, 통합 뷰 기술들 와 Section 13.5, “view와 view결정하기” 를 참조하라.
View 와 ViewResolver 구현물을 사용하는 몇가지 항목은 언급할 가치가 있다.
대부분의 포탈은 HTML조각이 되는 포틀릿을 표시하는 결과를 기대한다. JSP/JSTL, Velocity, FreeMarker, 그리고 XSLT 는 모두 공통적이다. 하지만 다른 문서 타입을 반환하는 view와는 달리 포틀릿 컨텍스트내에서는 같다.
포틀릿에서 HTTP 리다이렉트와 같은 것은 없다(ActionResponse의 sendRedirect(..)메소드는 포털내 존재하지 위해 사용될수 없다). 그래서 RedirectView와 'redirect:' 접두사의 사용은 포틀릿 MVC에서 정확하게 작동하지 않을것이다.
포틀릿 MVC에서 'forward:' 접두사의 사용은 가능하다. 웹 애플리케이션내 다른 자원에 접근하는 상대적인 URL을 사용할수 없고 절대적인 URL을 사용해야만 할 것이라는 것을 의미한다.
또한, JSP개발을 위해, 새로운 Spring Taglib와 새로운 Spring 폼 Taglib 모두 서블릿 view내 작동하는 것과 같은 방법으로 포틀릿 view에서 작동한다.
Spring 포틀릿 MVC는 웹 MVC가 하는것처럼 포틀릿 애플리케이션내 파일 업로드를 다루기 위한 내장된 multipart지원을 가진다. multipart지원을 위한 디자인은 org.springframework.web.portlet.multipart 패키지내 정의된 플러그인 성격의 PortletMultipartResolver 객체로 작동한다. Spring은 Commons FileUpload을 사용하기 위한 PortletMultipartResolver를 제공한다.
디폴트에 의해, Spring 포틀릿 MVC에 의해 수행되는 multipart핸들링은 없다. 몇몇 개발자는 multipart를 자체적으로 다루길 원할것이다. 당신은 웹 애플리케이션 컨텍스트에 multipart해석자를 추가하여 자체적으로 가능할것이다. DispatcherPortlet은 multipart를 포함하는지 보기 위해 각각의 요청을 조사할것이다. multipart가 발견되지 않는다면, 요청은 기대하는데로 계속될것이다. 어쨌든, 요청내 multipart가 발견된다면, 컨텍스트에 선언된 PortletMultipartResolver 가 사용될것이다. 그리고 난후, 요청내 multipart속성은 다른 속성처럼 처리될것이다.
다음의 예제는 CommonsPortletMultipartResolver를 사용하는 방법을 보여준다.
<bean id="portletMultipartResolver"
class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="100000"/>
</bean>
물론, 당신은 multipart해석자가 작동하도록 하기 위해 classpath에 적절한 jar를 둘 필요가 있다. CommonsMultipartResolver의 경우, 당신은 commons-fileupload.jar 를 사용할 필요가 있다. Commons FileUpload는 적어도 1.1버전을 사용해야 JSR-168 포틀릿 애플리케이션을 지원한다.
당신은 multipart 요청을 다루기 위해 포틀릿 MVC를 셋팅하는 방법을 보았다. 이것을 실제 사용하는 방법을 보자. DispatcherPortlet가 multipart요청을 감지했을때, 당신의 컨텍스트내 선언된 해석자를 활성화하고 요청을 다룬다. 해석자가 그리고 나서 하는 것은 현재 ActionRequest를 multipart파일 업로드를 지원하는 MultipartActionRequest로 포장하는 것이다. MultipartActionRequest를 사용하여, 당신은 요청에 포함된 multipart에 대한 정보를 얻고 컨트롤러내 multipart파일 자체에 접근할수 있다.
당신은 ActionRequest의 일부처럼 multipart 파일 업로드을 받을뿐 아니라 RenderRequest 의 일부처럼 받을수도 있다.
PortletMultipartResolver가 작업을 마친후에, 요청은 다른것처럼 처리될것이다. 이것을 사용하기 위해, 당신은 업로드 필드를 가진 폼을 생성한다. 그리고 나서 Spring에 파일을 당신의 폼에 바인드하도록 한다. 실제로 사용자가 파일을 업로드 하기 위해, 우리는 (JSP/HTML)폼을생성해야만 한다.
<h1>Please upload a file</h1> <form method="post" action="<portlet:actionURL/>" enctype="multipart/form-data"> <input type="file" name="file"/> <input type="submit"/> </form>
당신이 볼수 있는것처럼, 우리는 bean의 프라퍼티가 byte[]를 고정한후 “file” 이라는 이름의 필드를 생성한다. 게다가 multipart필드를 인코딩하는 방법을 브라우저에 알려주기 위해 필요한 인코딩 속성(enctype="multipart/form-data")을 추가했다(이것을 잊지말라..!!).
string이나 원시타입으로 자동 변환할수 없는 다른 프라퍼티처럼, 당신 객체에 바이너리 데이터를 두는것이 가능하도록 하기 위해, 당신은 PortletRequestDataBinder를 가진 사용자 정의 편집기를 등록해야만 한다. 여기엔 파일을 다루고 객체의 결과를 셋팅하기 위해 사용가능한 두개의 편집기가 있다. 파일을 문자열로 변환하는 능력을 가진 StringMultipartFileEditor와 파일을 byte배열로 변환하는 ByteArrayMultipartFileEditor가 있다. 이것은 CustomDateEditor가 하는것처럼 작동한다.
폼을 사용하여 파일을 업로드하기 위해, 해석자, bean을 처리할 컨트롤러를 위한 맵핑, 그리고 컨트롤러 자체를 선언하라.
<bean id="portletMultipartResolver" class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver"/> <bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping"> <property name="portletModeMap"> <map> <entry key="view" value-ref="fileUploadController"/> </map> </property> </bean> <bean id="fileUploadController" class="examples.FileUploadController"> <property name="commandClass" value="examples.FileUploadBean"/> <property name="formView" value="fileuploadform"/> <property name="successView" value="confirmation"/> </bean>
컨트롤러와 file 프라퍼티를 가지는 실제 클래스를 생성하라.
public class FileUploadController extends SimpleFormController { public void onSubmitAction( ActionRequest request, ActionResponse response, Object command, BindException errors) throws Exception { // cast the bean FileUploadBean bean = (FileUploadBean) command; // let's see if there's content there byte[] file = bean.getFile(); if (file == null) { // hmm, that's strange, the user did not upload anything } // do something with the file here } protected void initBinder( PortletRequest request, PortletRequestDataBinder binder) throws Exception { // to actually be able to convert Multipart instance to byte[] // we have to register a custom editor binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor()); // now Spring knows how to handle multipart object and convert } } public class FileUploadBean { private byte[] file; public void setFile(byte[] file) { this.file = file; } public byte[] getFile() { return file; } }
당신이 볼수 있는것처럼, FileUploadBean은 파일을 가지는 byte[]타입의 프라퍼티를 가진다. 컨트롤러는 해석자가 bean에 의해 명시된 프라퍼티를 찾는 multipart객체로 변환하는 방법을 알리기 위해 사용자 정의 편집기를 등록한다. 이 예제에서, 어떤것도 bean자체의 byte[] 프라퍼티로 하지 않는다. 하지만 실제로 당신은 원하는 것(데이터베이스내 저장하거나 누군가에게 메일을 보내거나, 기타등등)이 무엇이든 할수 있다.
파일이 (폼 지원)객체의 String타입의 프라퍼티에 직접 바운드하는 동일한 예제는 다음과 비슷할것이다.
public class FileUploadController extends SimpleFormController { public void onSubmitAction( ActionRequest request, ActionResponse response, Object command, BindException errors) throws Exception { // cast the bean FileUploadBean bean = (FileUploadBean) command; // let's see if there's content there String file = bean.getFile(); if (file == null) { // hmm, that's strange, the user did not upload anything } // do something with the file here } protected void initBinder( PortletRequest request, PortletRequestDataBinder binder) throws Exception { // to actually be able to convert Multipart instance to a String // we have to register a custom editor binder.registerCustomEditor(String.class, new StringMultipartFileEditor()); // now Spring knows how to handle multipart objects and convert } } public class FileUploadBean { private String file; public void setFile(String file) { this.file = file; } public String getFile() { return file; } }
물론, 마지막 예제는 일반적인 텍스트 파일(이미지 파일의 업로드의 경우 잘 작동하지 않을것이다.)의 업로드이다.
세번째(그리고 마지막) 옵션은 (폼 지원) 객체의 클래스에 선언된 MultipartFile 프라퍼티에 직접 바인드하는 것이다. 이 경우 수행될 타입 변환이 없기 때문에 어떠한 사용자 정의 프라퍼티 편집기를 등록할 필요가 없다.
public class FileUploadController extends SimpleFormController { public void onSubmitAction( ActionRequest request, ActionResponse response, Object command, BindException errors) throws Exception { // cast the bean FileUploadBean bean = (FileUploadBean) command; // let's see if there's content there MultipartFile file = bean.getFile(); if (file == null) { // hmm, that's strange, the user did not upload anything } // do something with the file here } } public class FileUploadBean { private MultipartFile file; public void setFile(MultipartFile file) { this.file = file; } public MultipartFile getFile() { return file; } }
웹 MVC처럼, 포틀릿 M/VC는 당신의 요청이 요청에 일치하는 핸들러에 의해 처리되는 동안 발생하는 기대되지 않은 예외를 쉽게 처리하기 위한 HandlerExceptionResolver를 제공한다. 포틀릿 MVC는 던져지는 예외의 클래스명을 가지고 view명에 이것을 맵핑하는 것을 가능하게 하는 구체적인 SimpleMappingExceptionResolver를 제공한다.
Spring 포틀릿 MVC애플리케이션을 디플로이하는 처리는 JSR-168 포틀릿 애플리케이션을 디플로이 하는것과 다르지 않다.
대개, 포털/포틀릿-컨테이너는 서블릿-컨테이너내 웹 애플리케이션으로 수행되고 다른 포틀릿은 서블릿-컨테이너내 다른 웹 애플리케이션으로 수행된다. 포틀릿 웹 애플리케이션에 대한 호출을 만드는 포틀릿-컨테이너 웹 애플리케이션을 위해, portlet.xml파일에 정의된 포틀릿 서비스에 접근하는 잘 알려진 서블릿에 대해 호출을 만들어야만 한다.
JSR-168스펙은 이 방법을 정확하게 명시하지 않았다. 그래서 각각의 포틀릿-컨테이너는 포틀릿 웹 애플리케이션 자체에 변경된 몇가지 “deployment process” 를 포함하고 포틀릿-컨테이너내 포틀릿을 등록하는 자체적인 기법을 가진다.
적어도, 포틀릿 웹 애플리케이션내 web.xml 파일은 포틀릿-컨테이너가 호출할 서블릿을 삽입하기 위해 변경되었다. 몇가지 경우 하나의 서블릿은 웹 애플리케이션내 모든 포틀릿을 서비스할것이다. 다른 경우 각각의 포틀릿을 위한 서블릿 인스턴스가 있을것이다.
몇가지 포틀릿-컨테이너는 라이브러리와(또는) 설정파일을 웹 애플리케이션으로 삽입할것이다. 포틀릿-컨테이너는 웹 애플리케이션에서 사용가능한 포틀릿 JSP 태그 라이브러리의 구현물을 만들어야만 한다.
마지막 라인은 목표 포털에 필요한 배치를 이해하고 다음의 제공하는 자동 배치 처리를 확인하라.
당신의 포틀릿을 배치했을때, web.xml 파일을 다시 보라. 몇가지 예전 포털은 ViewRendererServlet의 정의에 문제를 발생시키는 것으로 알려져있고 포틀릿을 표시하는데 문제가 있다.