Spring의 웹 MVC framework는 설정가능한 핸들러 맵핑들, view해석, 로케일과 파일 업로드를 위한 지원같은 테마해석과 함께 핸들러에 요청을 할당하는 DispatcherServlet을 기반으로 디자인되었다. 디폴트 핸들러는 ModelAndView handleRequest(request,response)메소드를 제공하는 매우 간단한 Controller 인터페이스이다. 이것은 이미 애플리케이션 컨트롤러를 위해 사용될수 있지만 AbstractController, AbstractCommandController그리고 SimpleFormController같은 것들을 포함한 구현 구조를 포함한것을 더 선호할것이다. 애플리케이션 컨트롤러들은 전형적으로 이것들의 하위 클래스가 된다. 당신이 선호하는 빈 클래스를 선택하도록 하라. 만약에 당신이 form을 가지지 않는다면 당신은 폼 컨트롤러가 필요없다. 이것이 Struts와 가장 큰 차이점이다.
Spring Web MVC는 당신에게 command나 form객체로 어떤 객체를 사용하는 것을 허용한다. 여기서 프레임워크 특유의 인터페이스나 기본 클래스를 구현할 필요는 없다. Spring의 데이터 바인딩은 매우 유연하다. 예를 들면 이것은 시스템 에러가 아닌 애플리케이션에 의해서 평가될수 있는 유효성 에러와 같은 타입 불일치를 처리할수 있다. 이것이 의미하는 모든것은 당신이 비지니스 객체의 프라퍼티를 간단히 중복하거나 form객체내 타입화되지 않은 문자열을 유효하지 않은 서브밋을 다룰수 있도록 또는 문자열을 변환할 필요가 없다는 것이다. 대신, 비지니스 객체에 직접 바인딩하는 것을 종종 선호한다. Struts와의 가장 큰 차이점은 Action 와 ActionForm 같은 필수 기본 클래스를 내장하지 않는다는 것이다.
WebWork와 비교해서 Spring은 좀더 차별적인 객체 역할을 가진다. 이것은 Controller, 선택적으로 command나 form 객체, 그리고 view에 전달되는 모델의 개념을 지원한다. 모델은 command나 form객체를 일반적으로 포함하지만 또한 임의의 참조 데이터도 포함한다. 대신에 WebWork Action은 그러한 모든 역할을 하나의 객체로 조합한다. WebWork는 당신에게 당신의 폼 일부처럼 존재하는 비지니스 객체를 사용하도록 하지만 그것들을 만듦으로써 개별적인 Action클래스의 빈 프라퍼티가 된다. 마지막으로 요청을 다루는 같음 Action인스턴스는 view안의 평가와 폼 활성화를 위해서 사용된다. 게다가 참조 데이터는 Action의 빈 프라퍼티처럼 모델화 될 필요가 있다. 하나의 객체를 위해 어쩌면 너무 많은 역할이 있다.
Spring의 view해석은 매우 유연하다. Controller 구현물은 (ModelAndView를 위해 null을 반환하여)응답에 직접적으로 view를 작성할수 있다. 대개의 경우에 ModelAndView인스턴스는 view이름과 빈 이름과 관련 객체(참조 데이터를 포함하는 command또는 폼 같은)를 포함하는 모델 Map으로 구성된다. View이름 해석은 빈 이름, 프라퍼티 파일, 당신 자신의 ViewResolver구현물을 통해 쉽게 설정가능한 사항이다. 사실 모델(MVC에서 M)은 view기술의 완벽한 추상화를 허용하는 Map 인터페이스에 기반한다. 어떠한 표현자(renderer)는 JSP, Velocity 또는 다른 어떠한 표현 기술이든지 직접적으로 통합될수 있다. 모델 Map은 간단하게 JSP요청 속성또는 Velocity템플릿 모델과 같은 선호하는 형태로 변형된다.
몇몇 프로젝트가 다른 MVC구현물을 사용하는것을 선호하는데는 여러가지 이유가 있다. 많은 팀은 업무적인 능력과 도구로 그들의 존재하는 투자물에 영향을 끼치도록 기대한다. 추가적으로 Struts프레임워크를 위해 유효한 지식과 경험의 많은 부분이 있다. 게다가 당신이 Struts의 구조적인 돌풍과 함께 한다면 이것은 웹 레이어를 위한 가치있는 선택이 될수 있다. 같은 것은 WebWork와 다른 웹 MVC프레임워크에 적용한다.
만약 당신이 Spring의 웹 MVC를 사용하길 원하지 않지만 Spring이 제공하는 다른 솔루션에 영향을 끼치는 경향이라면 당신은 당신이 선택한 웹 MVC프레임워크와 Spring을 쉽게 통합할수 있다. 이것의 ContextLoaderListener을 통해 Spring 루트 애플리케이션 컨텍스트를 간단하게 시작하고 Struts또는 WebWork액션내로 부터 이것의 ServletContext속성을 통해 접근한다. 어떠한 "plugins"도 포함되지 않았고 전용 통합이 필요하지 않다는 것에 주의하라. 웹 레이어의 관점에서 당신은 라이브러리처럼 간단하게 항목점(entry point)처럼 루트 애플리케이션 컨텍스트 인스턴스와 함께 Spring을 사용할 것이다.
당신의 등록된 모든 빈즈와 Spring의 모든 서비스는 Spring의 웹 MVC이 없더라도 쉽게 될수 있다. Spring은 이러한 시나리오로 Struts나 WebWork와 경쟁하지 않는다. 이것은 빈 설정으로 부터 데이터 접근과 트랜잭션 관리에서 순수한 웹 MVC프레임워크가 하지 않는 많은 면을 담당한다. 그래서 만약 당신이 (예를 들어 JDBC나 Hibernate로 트랜잭션 추상화) 사용하길 원한다면 Spring미들티어 그리고/또는 데이터 접근 티어를 사용해서 당신의 애플리케이션을 가치있게 할수 있다.
Spring의 웹 모듈은 다음을 포함하는 유일한 웹 지원의 가치를 제공한다.
역할의 분명한 분리 - 컨트롤러, 유효성 체커, command 객체, 폼 객체, 모델 객체, DispatcherServlet, 핸들러 맵핑, view해석자, 등등. 각각의 역할은 객체를 특수화된 객체에 의해 충족될수 있다.
프레임워크와 자바빈즈같은 애플리케이션 클래스의 강력하고 일관적인 설정, 웹 컨트롤러에서 비지니스 객체와 유효성 체커와 같은 컨텍스트를 통한 쉬운 참조를 포함한다.
적합성, 침범적이지 않은, 모든것을 위해 하나의 컨트롤러로 부터 유도되는 대신에 주어진 시나리오를 위해 필요한(plain, command, 폼, 마법사, 다중 액션, 또는 사용자지정의 것) 컨트롤러 하위 클래스가 무엇이든지 사용하라.
재사용가능한 비지니스 코드 - 중복을 위해 필요한 것이 없다. 당신은 특정 프레임워크 기본 클래스를 확장하기 위해 그것들을 반영하는 대신에 command 폼객체처럼 존재하는 비지니스 객체를 사용할수 있다.
사용자 지정 가능한 바인딩과 유효성 체크 - 수동 파싱과 비지니스 객체로 변환하는 오직 문자열만을 사용하는 폼 객체 대신에 잘못된 값, 지역화된 날짜 그리고 숫자 바인딩, 등등 을 유지하는 애플리케이션 레벨의 유효성 에러처럼 타입 부적합
사용자 지정가능한 핸들러 맵핑과 view해석 - 핸들러 맵핑과 view해석 전략은 간단한 URL기반의 설정에서 정교한, 목적이 내포된 해석 전략까지 범위에 둔다. 이것은 특정 기술을 위임하는 몇몇 웹 MVC프레임워크 보다 좀더 유연하다.
유연한 모델 이전 - 모델은 어떠한 view기술과 함께 쉬운 통합을 지원하는 이름/값 Map을 통해 이전한다.
사용자 지정 가능한 로케일과 테마 해석, Spring 태그 라이브러리를 사용하거나 사용하지 않은 JSP를 위한 지원, JSTL을 위한 지원, 추가적인 연결자를 위한 필요성없이 Velocity를 위한 지원
Spring 태그 라이브러리처럼 알려진 간단하지만 강력한 JSP 태그 라이브러리는 데이터 바인딩과 테마와 같은 기능을 제공한다. 사용자 정의 태그는 마크업 코드의 개념에서 최대한의 유연성을 허용한다. 태그 라이브러리 기술자에 대한 정보를 위해, Appendix D, spring.tld를 보라.
Spring 2.0에서 소개된 JSP 폼 태그 라이브러리는 JSP페이지 작성을 좀더 쉽게 만들어준다. 태그 라이브러리 기술자에 대한 정보를 위해, Appendix E, spring-form.tld를 보라.
생명주기 bean은 현재 HTTP 요청이나 HTTP Session에 범위화된다. Spring MVC자체의 구체적인 기능이 아닐뿐 아니라 Spring MVC가 사용하는 WebApplicationContext 컨테이너의 기능도 아니다. 이러한 bean범위는 Section 3.5.3, “The other scopes”에서 상세히 언급되었다.
Spring의 웹 MVC프레임워크는 많은 다른 웹 MVC프레임워크와 비슷하고 요청 기반의 웹 MVC프레임워크이고 컨트롤러에 요청을 할당하는 서블릿에 의해 디자인되었고 웹 애플리케이션의 배치를 용이하게 하는 다른 기능을 제공한다. Spring의 DispatcherServlet은 어쨌든 그것보다 더 많은 기능을 수행한다. 이것은 Spring IoC컨테이너와 완벽하게 통합되고 당신이 Spring이 가지는 다른 기능을 사용하도록 허락한다.
Spring 웹 MVC DispatcherServlet의 요청 처리 워크플로우는 다음의 그림에서 볼수 있다. 패턴을 이해하는 독자는 DispatcherServlet이 “Front Controller” 디자인 패턴(이것은 Spring 웹 MVC가 다른 많은 웹 프레임워크와 공유하는 패턴이다.)의 표현이라는 것을 인식할것이다.
Spring 웹 MVC내 요청 처리 워크플로우
DispatcherServlet은 실제 Servlet(이것은 HttpServlet 기본 클래스로부터 상속된다.) 이고 웹 애플리케이션의 web.xml내 선언된다. 다룰 DispatcherServlet를 원하는 요청은 같은 web.xml 파일내 URL맵핑을 사용하여 맵핑될것이다. 이것은 표준 J2EE서블릿 설정이다. DispatcherServlet 선언과 맵핑의 예제는 아래에서 볼수 있다.
<web-app> <servlet> <servlet-name>example</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>example</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> </web-app>
위 예제에서 .form으로 끝나는 모든 요청은 'example' DispatcherServlet에 의해 다루어질것이다. 이것은 Spring 웹 MVC를 셋팅하는 첫번째 단계이다. Spring 웹 MVC 프레임워크에 의해 사용되는 다양한 bean(그리고 DispatcherServlet 자체)은 설정될 필요가 있다.
Section 3.9, “ApplicationContext”에서 설명되는 것처럼, Spring 내 ApplicationContext 인스턴스는 범위화 될수 있다. 웹 MVC 프레임워크내에서 각각의 DispatcherServlet은 가장 상위의 WebApplicationContext에 이미 정의된 모든 bean을 상속하는 자체적인 WebApplicationContext를 가진다. 정의된 상속 bean은 서블릿-특유의 범위에서 오버라이드될수 있다. 그리고 새로운 범위의 특성을 가지는 bean은 주어진 서블릿 인스턴스에 지역적으로 정의될수 있다.
Spring MVC내 컨텍스트 구조
DispatcherServlet의 초기화에서, 프레임워크는 당신의 웹 애플리케이션의 WEB-INF 디렉토리에 있는 [servlet-name]-servlet.xml라고 명명된 파일을 찾고 거기에 정의된 bean을 생성한다(전역 범위에서 같은 이름을 가지고 정의된 bean의 정의를 오버라이드한다.).
다음의 DispatcherServlet 서블릿 설정('web.xml' 파일내)을 보라.
<web-app> ... <servlet> <servlet-name>golfing</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>golfing</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
위 서블릿 설정 대신에, 당신은 애플리케이션내 '/WEB-INF/golfing-servlet.xml' 라고 불리는 파일을 가질 필요가 있을것이다. 이 파일은 Spring 웹 MVC 특유의 컴포넌트 모두 포함할것이다. 이 설정파일의 정확한 위치는 서블릿 초기화 파라미터를 통해 변경될수 있다.
WebApplicationContext는 웹 애플리케이션을 위해 필요한 몇몇 추가적인 특징을 가지는 명료한 ApplicationContext의 확장이다. 이것은 테마(Section 13.7, “테마(themes) 사용하기”를 보라)를 해석하는 기능면에서 대개의 ApplicationContext와 다르고 관련된 서블릿이 무엇인지 (ServletContext로의 링크를 가짐으로써) 안다. WebApplicationContext은 ServletContext내에 바운드 되고 당신이 언제나 WebApplicationContext을 룩업할수 있는 RequestContextUtils을 사용하는것이 이 경우에 필요하다.
Spring DispatcherServlet은 요청을 처리하고 선호하는 view들을 표현할수 있도록 하기 위해 이것을 사용하는 두개의 특별한 빈즈를 가진다. Spring프레임워크에 포함되고 WebApplicationContext내에 설정될수 있는 그러한 빈즈는 꼭 설정될 다른 빈즈들과 같다. 그러한 각각의 빈즈들은 밑에서 좀더 상세하게 설명된다. 지금 우리는 그것들을 설명할것이다. 그것들이 존재하고 DispatcherServlet에 대해서 계속적으로 얘기할수 있도록 하자. 대부분의 bean을 위해, 실용적인 디폴트는 그것들을 설정할 걱정을 하지 않도록 해준다.
Table 13.1. WebApplicationContext내 특별한 빈즈들
bean타입 | 설명 |
---|---|
컨트롤러 | Controllers는 MVC에서 'C' 부분을 나타내는 컴포넌트이다. |
핸들러 맵핑 | 핸들러 맵핑은 그것들이 어떠한 기준(예를 들면 컨트롤러와 함께 명시된 URL에 일치할때)에 일치할때 수행되어야 할 선-처리자, 후-처리자와 컨트롤러의 목록의 수행을 다룬다. |
view 해석자 | View 해석자는 view이름을 view로 해석하는 능력을 가진 컴포넌트이다. |
로케일 해석자 | locale 해석자는 국제화된 view를 제공할수 있도록 하기 위해 클라이언트가 사용하는 로케일을 해석하는 능력을 가진 컴포넌트이다. |
테마 해석자 | 테마 해석자는 예를 들어 개인화된 레이아웃을 제공하는 것처럼, 웹 애플리케이션이 사용할수 있는 테마를 해석하는 능력을 가진다. |
멀티파트 파일 해석자 | 멀티파트 파일 해석자는 HTML폼으로 부터 파일 업로드 처리를 위한 기능을 제공한다. |
핸들러 예외 해석자 | 핸들러 예외 해석자는 view에 예외를 맵핑하거나 좀더 복잡한 예외 핸들링 코드를 구현하는 기능을 제공한다. |
DispatcherServlet이 사용하기 위해 셋업되고 특정 DispatcherServlet을 위해 요청이 접수되면 언급된 DispatcherServlet이 요청을 처리하길 시작한다. 밑에서 서술된 리스트는 DispatcherServlet에 의해 핸들링 된다면 요청을 완벽하게 처리한다.
WebApplicationContext는 사용하기 위한 프로세스에서 컨트롤러와 다른 요소를 위해 순서대로 속성처럼 요청을 찾고 바운드 한다. 이것은 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE키 하위 디폴트에 의해 바운드된다.
로케일 해석자는 요청을 처리할때(view를 표현하고, 데이터를 준비하는 등등) 사용하기 위한 로케일을 프로세스가 해석하도록 요청에 바운드된다. 만약 당신이 해석자를 사용하지 않는다면 이것은 어떠한 영향도 끼치지 않는다. 그래서 당신이 로케일 해석이 필요하지 않다면 그것을 사용하지 않아도 된다.
테마 해석자는 view가 사용하기 위한 테마가 무엇인지 결정하는 요청에 바운드된다. 테마 해석자는 사용하지 않는다면 어떠한 영향도 끼치지 않는다. 그래서 당신이 테마를 사용할 필요가 없다면 당신은 그것을 무시할수 있다.
만약 멀티파트 해석자가 명시되었다면 요청은 멀티파트를 위해서 그리고 그들이 발견된다면 조사될것이다. 이것은 프로세스내에서 다른 요소에 의해 좀더 처리되기 위해 MultipartHttpServletRequest내에 포장된다. (멀티파트 핸들링에 대해서 더 많은 정보를 위해서 Section 13.8.2, “MultipartResolver 사용하기”를 보라.).
선호하는 핸들러는 검색되기 위한것이다. 만약 핸들러가 찾아졌다면 핸들러(선처리자, 후처리자, 컨트롤러)가 속한 수행 묶음이 모델을 준비하기 위해 수행될것이다.
모델이 반환된다면, view는 표현된다. 만약 어떠한 모델도 반환되지 않는다면(선 또는 후처리자가 요청을 가로챌수 있는, 예를 들면 보안적인 이유로) 이미 처리된 요청이후 표현되는 view는 없다.
요청 처리도중 던져질수 있는 예외는 WebApplicationContext내 선언된 어떠한 핸들러 예외 해석자에 의해 처리된다. 이러한 예외 해석자를 사용함으로써 당신은 이러한 예외가 던져지는 경우 사용자 정의 행위를 정의할수 있다.
Spring DispatcherServlet은 역시 서블릿 API에 의해 특성화된 last-modification-date를 반환하기 위한 지원을 가진다. 특정 요청을 위해 가장 마지막에 변경된 날짜를 알아내는 프로세스는 매우 간단하다. DispatcherServlet은 먼저 선호하는 핸들러 맵핑을 룩업할것이고 핸들러가 LastModified 인터페이스를 구현한것을 찾을지 테스트할것이다. 만약 찾게된다면 LastModified인터페이스의 long getLastModified(request)메소드가 클라이언트에 반환된다.
당신은 web.xml파일이나 서블릿 초기화 파라미터에 컨텍스트 파라미터를 추가함으로써 Spring의 DispatcherServlet을 사용자 정의화 할수 있다. 가능한 사항을 밑에서 보여준다.
Table 13.2. DispatcherServlet 초기화 파라미터
파라미터 | 설명 |
---|---|
contextClass | 서블릿에 의해 사용되는 컨텍스트를 초기화하기 위해 사용될 WebApplicationContext을 구현하는 클래스. 만약 이 파라미터가 명시되지 않는다면 XmlWebApplicationContext가 사용될 것이다. |
contextConfigLocation | 발견될수 있는 컨텍스트의 위치를 표시하기 위한 컨텍스트 인스턴스(contextClass에 의해 정의되는)로 전달되는 문자열. 이 문자열은 잠재적으로 다중(다중 컨텍스트의 경우 두개로 명시된 빈의 경우 후자가 상위 서열을 가진다.) 컨텍스트를 지원하기 위해 다중(콤마를 분리자로 사용하여) 문자열로 나누어진다. |
namespace | WebApplicationContext의 명명공간(namespace). 디폴트 값은 [server-name]-servlet이다. |
컨트롤러의 개념은 MVC디자인 패턴의 일부이다(좀더 상세하게는 MVC의 'C'이다). 컨트롤러는 서비스 인터페이스에 의해 정의된 애플리케이션 행위에 접근한다. 컨트롤러는 사용자 입력을 해석하고 사용자 입력을 view에 의해 사용자에게 표현될 실용적인 모델로 변형된다. Spring은 생성될 굉장히 넓은 범위의 다양한 종류의 컨트롤러를 가능하게 하는 추상화된 방법으로 컨트롤러의 개념을 구현했다. Spring은 폼 특성의 컨트롤러, command-기반 컨트롤러, 마법사 스타일의 로직을 수행하는 컨트롤러 그리고 더 다양한 방법을 포함한다.
Spring의 컨트롤러 구조의 기본은 아래에 있는 org.springframework.web.servlet.mvc.Controller인터페이스이다.
public interface Controller { /** * Process the request and return a ModelAndView object which the DispatcherServlet * will render. */ ModelAndView handleRequest( HttpServletRequest request, HttpServletResponse response) throws Exception; }
당신이 볼수 있는것처럼 Controller인터페이스는 요청을 처리하고 선호하는 모델및 view로 반환하는 하나의 메소드를 정의한다. 이러한 3개의 개념은 Spring MVC구현물(-ModelAndView 와 Controller)을 위한 기본이다. Controller인터페이스가 추상적인 동안 Spring은 필요한 많은 기능을 포함한 많은 Controller 구현물을 제공한다. Controller인터페이스는 모든 컨트롤러의 요구되는 가장 공통적인 기능(-요청을 처리하고 모델및 view를 반환하는)을 정의한다.
기본적인 내부구조를 제공하기 위해서 Spring의 모든 Controller는 캐시지원과 예를 들면 mimetype셋팅을 제공하는 클래스인 AbstractController로부터 상속되었다.
Table 13.3. AbstractController에 의해 제공되는 특징
특징 | 설명 |
---|---|
supportedMethods | 이 컨트롤러가 받아들이는 방식이 무엇인지 표시. 언제나 이것은 GET 과 POST로 셋팅되지만 당신은 지원하길 원하는 방식를 반영하기 위해 변경할수 있다. 만약 요청이 컨트롤러에 의해 지원되지 않는 방식으로 들어왔다면 클라이언트는 이것을 정보화(ServletException)을 던져서 진척되는) 할것이다. |
requiresSession | 컨트롤러가 작업을 하기위해 HTTP 세션을 필요로 하는지 하지 않는지를 표시. 이 기능은 모든 컨트롤러에 의해 제공된다. 만일 컨트롤러가 요청을 받을때 세션이 존재하지 않는다면 사용자는 던져지는 ServletException을 사용해서 정보화한다. |
synchronizeSession | 당신이 사용자의 HTTP 세션에서 동기화되는 컨트롤러에 의해 핸들링하길 원한다면 이것을 사용하라. |
cacheSeconds | 당신이 HTTP응답에서 캐시를 직업적으로 생성할 컨트롤러를 원한다면 여기에 양수값을 명시하라. 디폴트에 의해 이 프라퍼티의 값은 -1로 셋팅된다. 그래서 생성되는 응답에 어떤 캐싱도 포함되지 않을것이다. |
useExpiresHeader | HTTP 1.0호환 "Expires"헤더를 명시하기 위해 당신의 컨트롤러를 부분적으로 개량하라. 디폴트에 의해 이 프라퍼티의 값은 true이다. |
useCacheHeader | HTTP 1.1호환 "Cache-Control"헤더를 명시하기 위해 당신의 컨트롤러를 부분적으로 개량하라. 디폴트에 의해 이 프라퍼티의 값은 true이다. |
당신의 컨트롤러(당신을 위해 작업을 이미 수행하는 많은 다른 컨트롤러때문에 추천되지는 않는)를 위한 기본 클래스로 AbstractController를 사용할때 당신은 단지 당신의 로직을 구현하고 ModelAndView객체를 반환하는 handleRequestInternal(HttpServletRequest, HttpServletResponse)메소드만 오버라이드 해야 한다. 이것은 웹 애플리케이션 컨텍스트내 클래스와 선언을 구성하는 짧은 예제이다.
package samples; public class SampleController extends AbstractController { public ModelAndView handleRequestInternal( HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mav = new ModelAndView("hello"); mav.addObject("message", "Hello World!"); return mav; } }
<bean id="sampleController" class="samples.SampleController"> <property name="cacheSeconds" value="120"/> </bean>
위 클래스와 웹 애플리케이션 컨텍스트내 선언은 매우 간단히 작동중인 컨트롤러를 얻기 위해 당신이 핸들러 맵핑(Section 13.4, “Handler mappings”을 보라)을 셋업할 필요가 있는 모든것이다. 이 컨트롤러는 다시 체크하기 전에 2분동안 캐시하는것을 클라이언트에게 알리는 직접적인 캐시를 생성할것이다. 이 컨트롤러는 직접 작성된(흠.. 별로 좋지 않은) 코드의 view를 반환할것이다.
비록 당신이 AbstractController을 확장할수 있다고 하더라도 Spring은 간단한 MVC애플리케이션내 공통적으로 사용될 기능을 제공하는 명확한 많은 구현물을 제공한다. ParameterizableViewController는 웹 애플리케이션 컨텍스트내 반환될 view이름을 명시할수 있다(Java클래스내 viewname을 하드코딩할 필요를 제거하고)는 사실을 제외하면 위 예제와 기본적으로 같다.
UrlFilenameViewController는 URL을 조사하고 요청으로 부터 파일이름을 가져오고 viewname으로 그것을 사용한다. 예를 들어, http://www.springframework.org/index.html요청의 파일명은 index이다.
Spring은 모두 기능적으로 그룹화되는 한개의 컨트롤러로 다중 액션을 합치도록 하는 다중액션(multi-action) 컨트롤러를 제공한다. 다중액션 컨트롤러는 별도의 패키지인 org.springframework.web.servlet.mvc.multiaction로 되어 있다. 그리고 메소드이름으로 요청을 맵핑하는 능력을 가지고 올바른 메소드 이름을 호출한다. 다중액션 컨트롤러를 사용하는 것은 하나의 컨트롤러로 많은 공통적인 기능을 가질때 특별히 편리하다. 하지만 컨트롤러에 대해 다중 엔트리 포인트를 가지길 원한다. 예를 들면 행위의 일부를 고치기 위해
Table 13.4. MultiActionController에 의해 제공되는 특징
특징 | 설명 |
---|---|
delegate | MultiActionController을 위한 두가지 사용 시나리오가 있다. 당신이 MultiActionController를 세분화하고 하위 클래스에서 MethodNameResolver에 의해 해석될 메소드를 정의하거나 MethodNameResolver에 의해 해석될 메소드가 호출되는 위임(delegate)객체를 명시한다. 만약 당신이 이 시나리오를 선택한다면 당신은 협력자(collaborator)처럼 이 설정 파라미터를 사용해서 위임을 정의할 것이다. |
methodNameResolver | 아무튼 MultiActionController는 들어온 요청에 기반한 호출할 메소드를 해석할 필요가 있을것이다. 이 전략은 MethodNameResolver 인터페이스에 의해 정의된다. MultiActionController는 해석자를 제공할수 있는 프라퍼티를 나타낸다. |
다중액션 컨트롤러를 위해 명시한 메소드는 다음의 시그너처에 따를 필요가 있다.
// anyMeaningfulName can be replaced by any methodname
public [ModelAndView | Map | void] anyMeaningfulName(HttpServletRequest, HttpServletResponse [, Exception | AnyObject]);
메소드 오버로딩은 MultiActionController을 혼동시키기 때문에 허락되지 않는다. 게다가 당신은 명시한 메소드에 의해 던져질 예외 핸들링 능력을 가지는 exception handlers를 정의할수 있다.
(선택적) Exception 인자는 java.lang.Exception 이나 java.lang.RuntimeException의 하위클래스라면 어떤 예외가 될수 있다. (선택적) AnyObject 인자는 어떤 클래스가 될수 있다. 요청 파라미터는 편리한 소모를 위해 이 객체로 바운드 될것이다.
아래에서 유효한 MultiActionController 메소드 시그너처의 몇몇 예제를 보라.
표준 시그너처 (Controller 인터페이스 메소드를 반영한다.)
public ModelAndView doRequest(HttpServletRequest, HttpServletResponse)
이 시그너처는 요청에서 나오는 파라미터로 활성화(바운드)될 Login 인자를 받아들인다.
public ModelAndView doLogin(HttpServletRequest, HttpServletResponse, Login)
Exception 핸들링 메소드를 위한 시그너처
public ModelAndView processException(HttpServletRequest, HttpServletResponse, IllegalArgumentException)
이 시그너처는 void 반환타입을 가진다(아래의 Section 13.11, “설정에 대한 규칙”를 보라).
public void goHome(HttpServletRequest, HttpServletResponse)
이 시그너처는 Map 반환타입을 가진다. (아래의 Section 13.11, “설정에 대한 규칙”를 보라).
public Map doRequest(HttpServletRequest, HttpServletResponse)
MethodNameResolver는 들어온 요청에 기반하여 메소드 이름을 해석하는 책임을 진다. 아래에서 Spring이 특별히 제공하는 3개의 MethodNameResolver 구현물에 대한 상세를 보라.
ParameterMethodNameResolver - 요청 파라미터를 해석하고 메소드이름(http://www.sf.net/index.view?testParam=testIt는 호출될 testIt(HttpServletRequest, HttpServletResponse) 메소드로 결과를 낼것이다.)처럼 그것을 사용하는 능력. paramName 프라퍼티는 조사된 요청 파라미터를 명시한다.
InternalPathMethodNameResolver - 요청 경로로 부터 파일이름을 가져오고 메소드 이름처럼 그것을 사용한다. (http://www.sf.net/testing.view는 호출될 testing(HttpServletRequest, HttpServletResponse)메소드내 결과를 낼것이다.)
PropertiesMethodNameResolver - 요청 URL을 메소드 이름에 맵핑되는 사용자 정의 프라퍼티 객체를 사용한다. 프라퍼티가 /index/welcome.html=doIt을 포함하고 /index/welcome.html로 요청이 들어올때 doIt(HttpServletRequest, HttpServletResponse)메소드는 호출된다. 이 메소드 이름 해석자는 PathMatcher과 함께 작동한다. 프라퍼티가 /**/welcom?.html을 포함한다면 이것또한 작동할것이다.
여기에 두개의 예제가 있다. 첫번째 예제는 ParameterMethodNameResolver를 보여주고 포함된 파라미터 메소드를 가지는 URL로 요청을 받을 프라퍼티를 위임한다. 그리고 retrieveIndex에 셋팅한다.
<bean id="paramResolver" class="org....mvc.multiaction.ParameterMethodNameResolver">
<property name="paramName" value="method"/>
</bean>
<bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController">
<property name="methodNameResolver" ref="paramResolver"/>
<property name="delegate" ref="sampleDelegate"/>
</bean>
<bean id="sampleDelegate" class="samples.SampleDelegate"/>
## together with
public class SampleDelegate {
public ModelAndView retrieveIndex(HttpServletRequest req, HttpServletResponse resp) {
return new ModelAndView("index", "date", new Long(System.currentTimeMillis()));
}
}
위에서 보여진 위임을 사용할때, 우리는 정의된 메소드에 URL을 일치시키기 위해 PropertiesMethodNameResolver를 사용할수 있다.
<bean id="propsResolver" class="org....mvc.multiaction.PropertiesMethodNameResolver"> <property name="mappings"> <value> /index/welcome.html=retrieveIndex /**/notwelcome.html=retrieveIndex /*/user?.html=retrieveIndex </value> </property> </bean> <bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController"> <property name="methodNameResolver" ref="propsResolver"/> <property name="delegate" ref="sampleDelegate"/> </bean>
Spring의 command 컨트롤러는 Spring 웹 MVC패키지의 필수적인 부분이다. command 컨트롤러는 데이터 객체와 상호작동하기 위한 방법을 제공하고 HttpServletRequest에서 명시된 데이터 객체로 파라미터를 동적으로 바인드한다. 그것들은 Struts의 ActionForm과 유사한 역할을 수행하지만 Spring내에서 당신의 데이터 객체는 프레임워크 특정적인 인터페이스를 구현할 필요는 없다. 당신이 그것들로 무엇을 할수 있는지 개략적으로 살펴보기 위해 사용가능한 command 컨트롤러가 어떤것이 있는지 조사해보자.
AbstractCommandController - 당신 자신의 컨트롤러를 생성하기 위해 당신이 사용할수 있는 command 컨트롤러는 당신이 명시하는 데이터 객체로 요청 파라미터를 바인드하는 능력을 가진다. 이 클래스는 폼 기능을 제공하지 않는다. 이것은 어쨌든 유효성체크 기능을 제공하고 요청 파라미터값을 채우는 command 객체가 무엇인지 컨트롤러내 명시하자.
AbstractFormController - 추상 컨트롤러는 폼 서브밋 지원을 제공한다. 이 컨트롤러를 사용하면 당신은 폼을 만들고 당신이 컨트롤러내에서 가져온 command 객체를 사용하여 그것들을 형상화 할수 있다. 사용자가 폼을 채우고 난 뒤 AbstractFormController는 필드를 바인드하고 command객체를 유효성 체크를 하며 선호하는 액션을 가지기 위해 컨트롤러에 반환된 객체를 다룬다. 지원되는 기능 : 타당치 않은 폼 서브밋, 유효성체크, 그리고 일반적인 폼 워크플로우. 당신은 view가 폼 표현이나 성공을 위해 사용되는지 알아내기 위한 메소드를 구현한다. 만약 당신이 폼이 필요하다면 이 컨트롤러를 사용하라. 하지만 애플리케이션 컨텍스트내 사용자를 보여주기 위해 당신이 사용하는 view를 명시하길 원하지 않는다.
SimpleFormController - 유사한 command 객체를 가진 폼을 생성할때 명백한 폼 컨트롤러는 좀더 많은 지원을 제공한다. SimpleFormController는 당신이 command 객체, 폼을 위한 view이름, 폼 서브밋이 성공했을때 당신이 사용자에게 보여주기를 원하는 페이지를 위한 view이름을 명시하도록 한다.
AbstractWizardFormController - 클래스 이름이 제시하는것처럼 이것은 당신의 wizard 컨트롤러가 이것을 확장해야만 하는 추상 클래스이다. 이것은 당신이 validatePage(), processFinish() 그리고 processCancel()를 구현해야만 한다는 것을 의미한다.
당신은 아마 적어도 setPages() 와 setCommandName()를 호출하는 계약자(contractor)를 쓰길 원할것이다. 이 형태자(former)는 인자를 문자열 타입 배열현태로 가진다. 이 배열은 당신의 마법사를 이루는 view의 목록이다. 나중에 인자를 당신의 view내로부터 command 객체를 참조하기 위해 사용될 문자열처럼 가진다.
AbstractFormController의 어떠한 인스턴스처럼 당신은 당신의 폼으로 부터 데이터를 활성화할 자바빈인 command 객체를 사용하기 위해 요구된다. 당신은 command 객체의 클래스를 가진 생성자로부터 setCommandClass()을 호출하거나 formBackingObject()메소드를 구현하는 두가지 방법중에 하나로 이것을 할수 있다.
AbstractWizardFormController는 오버라이드할 많은 수의 메소드를 가진다. 물론 당신이 가장 유용하다고 볼수 있는 것은 당신이 Map의 폼내 당신의 view로 모델 데이터를 전달하기 위해 사용할수 있는 referenceData(..); 만약 당신의 마법사가 순서대로 페이지를 변경하거나 동적으로페이지를 생략할 필요가 있다면 getTargetPage(); 만약 당신이 내장된 바인딩과 유효성 체크 워크 플로우를 오버라이드하기를 원한다면 onBindAndValidate()이다.
마지막으로 이것은 현재 페이지에 유효성 체크가 실패한다면 마법사내에서 뒤로나 앞으로 움직이는 것을 사용자에게 허락하는 getTargetPage()로 부터 호출할수 있는 setAllowDirtyBack() 과 setAllowDirtyForward()를 가리킬 가치가 있다.
메소드의 모든 목록을 위해 AbstractWizardFormController를 위한 JavaDoc를 보라. Spring배포판내 포함된 jPetStore내 마법사의 예제가 구현(org.springframework.samples.jpetstore.web.spring.OrderFormController)되어 있다.
핸들러 맵핑을 사용하면 당신은 선호하는 핸들러로 들어온 웹 요청을 맵핑할수 있다. 여기에 당신이 특별히 사용할수 있는 몇몇 핸들러 맵핑이 있다. 예를 들면 SimpleUrlHandlerMapping 나 BeanNameUrlHandlerMapping이다. 하지만 먼저 HandlerMapping의 일반적인 개념을 알아보자.
기능적으로 기본 HandlerMapping은 들어온 요청에 매치하는 핸들러를 포함하고 요청에 적용되는 핸들러 입터셉터의 목록을 포함할수도 있는 HandlerExecutionChain의 전달을 제공한다. 요청이 들어왔을때 DispatcherServlet는 요청을 조사하도록 하는 핸들러 맵핑을 위해 이것을 다룰것이고 선호하는 HandlerExecutionChain을 생성할것이다. 그 다음 DispatcherServlet은 핸들러와 체인내 인터셉터를 수행할것이다.
임의로 인터셉터(실질적인 핸들러가 수행되기 전이나 후에 수행되는)를 포함할수 있는 설정가능한 핸들러 맵핑의 개념은 극히 강력하다. 많은 지원 기능은 사용자 정의 HandlerMapping에 내장될수 있다. 핸들러를 선택한 사용자 정의 핸들러 맵핑의 생각은 들어오는 요청의 URL에 기반할뿐 아니라 요청과 함께 속하는 세션의 특정 상태에도 기반을 둔다.
이 섹션은 Spring의 가장 공통적으로 사용되는 핸들러 맵핑 두가지를 서술한다. 그 둘은 AbstractHandlerMapping을 확장하고 다음의 프라퍼티 값을 공유한다.
interceptors: 사용하기 위한 인터셉터의 목록. HandlerInterceptor는 Section 13.4.3, “요청 가로채기 - HandlerInterceptors 인터페이스”에서 논의된다.
defaultHandler: 핸들러 맵핑이 적합한 핸들러를 매치시키는 결과를 내지 못할때 사용하기 위한 디폴트 핸들러.
order: order프라퍼티(org.springframework.core.Ordered인터페이스를 보라)의 값에 기반한다. Spring은 컨텍스트내에서 사용가능한 모든 핸들러 맵핑을 분류하고 첫번째 매치되는 핸들러를 적용한다.
alwaysUseFullPath: 이 프라퍼티가 true로 셋팅된다면 Spring은 적당한 핸들러를 찾기 위해 현재 서블릿 컨텍스트내 완전한 경로(full path)를 사용할것이다. 만약 이 프라퍼티가 false(이 값이 디폴트 값이다.)로 셋팅된다면 현재 서블릿 맵핑내 경로가 사용될것이다. 예를 들면 서블릿이 /testing/*을 사용해서 맵핑되어 있고 alwaysUseFullPath프라퍼티가 true로 셋팅되어 있다면 /testing/viewPage.html이 사용될것이다. 만약 프라퍼티가 false라면 /viewPage.html가 사용될것이다.
urlPathHelper: 이 프라퍼티를 사용하면 당신은 URL을 조사할때 사용되는 UrlPathHelper를 부분적으로 수정할수 있다. 일반적으로 당신은 디폴트 값을 변경하지 말아야 할것이다.
urlDecode: 이 프라퍼티를 위한 디폴트 값은 false이다. HttpServletRequest는 번역(decode)되지 않은 요청 URL과 URI를 반환한다. 만약 HandlerMapping이 적당한 핸들러를 찾기 위해 그것들을 사용하기 전에 그것들이 번역되길 원한다면 이것을 true로 셋팅해야 한다.(이것은 JDK1.4를 요구한다는것에 주의하라.). 번역 방식은 요청에 의해 명시된 인코딩이나 디폴트인 ISO-8859-1 인코딩 스키마를 사용한다.
lazyInitHandlers: singleton핸들러(프로토타입 핸들러는 언제나 늦은 초기화를 수행한다.)의 늦은(lazy)초기화를 허락한다. 디폴트 값은 false이다.
(주의: 마지막의 4개의 프라퍼티는 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping의 하위클래스에서만 사용가능하다.).
매우 간단하지만 굉장히 강력한 핸들러 맵핑은 들어오는 HTTP요청을 웹 애플리케이션 컨텍스트내 명시된 빈즈의 이름으로 맵핑하는 BeanNameUrlHandlerMapping이다. 우리는 사용자가 계정을 추가하는것이 가능하길 원하고 우리는 벌써 적당한 폼 컨트롤러(command와 폼 컨트롤러의 좀더 상세한 정보를 위해서 Section 13.3.4, “Command Controllers”을 보라)과 폼을 표현하는 JSP view(또는 Velocity템플릿)가 제공된다고 얘기해보자. BeanNameUrlHandlerMapping을 사용할때 우리는 다음처럼 적당한 form Controller를 위해 URL http://samples.com/editaccount.form을 HTTP요청에 맵핑할수 있다.
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <bean name="/editaccount.form" class="org.springframework.web.servlet.mvc.SimpleFormController"> <property name="formView" value="account"/> <property name="successView" value="account-created"/> <property name="commandName" value="account"/> <property name="commandClass" value="samples.Account"/> </bean> <beans>
URL /editaccount.form을 위한 들어온 모든 요청은 위 소스 목록의 FormController에 의해 다루어질것이다. 물론 우리는 .form으로 끝나는 모든 요청을 통하기 위해 web.xml에 servlet-mapping을 정확하게 명시해야 한다.
<web-app>
...
<servlet>
<servlet-name>sample</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- maps the sample dispatcher to *.form -->
<servlet-mapping>
<servlet-name>sample</servlet-name>
<url-pattern>*.form</url-pattern>
</servlet-mapping>
...
</web-app>
![]() | Note |
---|---|
주의: 만약 당신이 BeanNameUrlHandlerMapping을 사용하길 원한다면 당신은 웹 애플리케이션 컨텍스트내 이것을 반드시 명시할 필요가 없다(위에서 표시된것처럼). 디폴트에 의해 어떠한 핸들러 맵핑도 컨텍스트내에서 발견되지 않는다면 DispatcherServlet은 당신을 위해 BeanNameUrlHandlerMapping을 생성한다. |
한층 나아가서 그리고 좀더 강력한 핸들러 맵핑은 SimpleUrlHandlerMapping이다. 이 맵핑은 애플리케이션 컨텍스트내에서 설정가능하고 Ant스타일의 경로 매치 능력을 가진다. (org.springframework.util.PathMatcher를 위한 JavaDoc를 보라.). 여기에 예제가 있다.
<web-app> ... <servlet> <servlet-name>sample</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- maps the sample dispatcher to *.form --> <servlet-mapping> <servlet-name>sample</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> <!-- maps the sample dispatcher to *.html --> <servlet-mapping> <servlet-name>sample</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> ... </web-app>
위 web.xml설정의 일부는 같은 디스패처(dispatcher) 서블릿에 의해 다루어지기 위해 .html과 .form으로 끝나는 모든 요청을 할당한다.
<beans>
<!-- no 'id' required, HandlerMapping beans are automatically detected by the DispatcherServlet -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/*/account.form=editAccountFormController
/*/editaccount.form=editAccountFormController
/ex/view*.html=helpController
/**/help.html=helpController
</value>
</property>
</bean>
<bean id="helpController"
class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
<bean id="editAccountFormController"
class="org.springframework.web.servlet.mvc.SimpleFormController">
<property name="formView" value="account"/>
<property name="successView" value="account-created"/>
<property name="commandName" value="Account"/>
<property name="commandClass" value="samples.Account"/>
</bean>
<beans>
이 핸들러 맵핑은 어떤 디렉토리내 'help.html'을 UrlFilenameViewController(Section 13.3, “컨트롤러”에서 찾을수 있는 컨트롤러에 대해서 추가적인)인 'helpController'로 요청을 보낸다. 'ex'디렉토리내에서 'view'로 시작하고 '.html'로 끝나는 자원을 위한 요청은 'helpController'로 경로가 지정될것이다. 둘 이상의 맵핑은 'editAccountFormController'을 위해 명시된다.
Spring의 핸들러 맵핑 기법은 예를 들어 구매자를 체크하는 어떠한 요청을 위한 특정 기능을 적용하길 원할때 매우 유용할수있는 핸들러 인터셉터의 개념을 가진다.
핸들러 맵핑내 위치하는 인터셉터는 org.springframework.web.servlet패키지로부터 HandlerInterceptor를 구현해야만 한다. 이 인터페이스는 3개의 메소드를 선언한다. 하나는 실질적인 핸들러가 수행되기 전에 호출될것이고 하나는 핸들러가 수행된 후에 호출될것이다. 나머지 하나는 완전한 요청이 종료된후에 호출된다. 이 3가지 메소드는 선처리와 후처리의 모든 종류를 처리하기 위한 충분한 유연성을 제공할것이다.
preHandle(..) 메소드는 bolean값을 반환한다. 당신은 작업을 중간에 종료하거나(break) 수행묶음(chain)의 처리를 계속하기 위해 이 메소드를 사용할수 있다. 이 메소드가 true를 반환할때 핸들러 수행묶음은 계속될것이다. false를 반환할때는 DispatcherServlet이 요청을 처리할 인터셉터(그리고 예를 들면 적당한 view를 표현하는)를 추정하고 수행묶음내 다른 인터셉터와 실질적인 핸들러를 계속적으로 수행하지 않는다.
아래의 예제는 모든 요청을 가로채고 오전 9시와 오후 6시가 아니라면 사용자를 특정 페이지로 경로를 변경시키는 인터셉터를 제공한다.
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="officeHoursInterceptor"/> </list> </property> <property name="mappings"> <value> /*.form=editAccountFormController /*.view=editAccountFormController </value> </property> </bean> <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor"> <property name="openingTime" value="9"/> <property name="closingTime" value="18"/> </bean> <beans>
package samples; public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter { private int openingTime; private int closingTime; public void setOpeningTime(int openingTime) { this.openingTime = openingTime; } public void setClosingTime(int closingTime) { this.closingTime = closingTime; } public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Calendar cal = Calendar.getInstance(); int hour = cal.get(HOUR_OF_DAY); if (openingTime <= hour < closingTime) { return true; } else { response.sendRedirect("http://host.com/outsideOfficeHours.html"); return false; } } }
들어오는 어떠한 요청은 TimeBasedAccessInterceptor에 의해 차단당할것이고 현재 시각이 사무시간(office hours)밖이라면 사용자는 정적 html파일로 전환될것이다. 다시 말하면 예를 들어 그는 사무시간(office hours)내에서만 웹사이트에 접근할수 있다.
당신이 볼수 있는 것처럼 Spring은 당신이 HandlerInterceptor를 확장하기 쉽도록 만드는 어댑터(adapter) 클래스(HandlerInterceptorAdapter라는)를 가진다.
웹 애플리케이션을 위한 모든 MVC프레임워크는 view를 결정하는 방법을 제공한다. Spring은 특정 view기술을 위해 당신이 기록할 필요없이 브라우저내에서 모델을 표현할수 있도록 만들어주는 view결정자(resolvers)를 제공한다. 특별히 Spring은 JSP, Velocity 템플릿, XSLT view를 사용가능하도록 한다. 예를 들면 Chapter 14, 통합 뷰 기술들는 다양한 view기술과의 통합에 대한 상세사항을 가진다.
Spring이 view를 다루는 방법을 위한 중요한 두개의 인터페이스는 ViewResolver 와 View이다. ViewResolver는 view이름과 실제 view사이의 맵핑을 제공한다. View인터페이스는 준비된 요청을 할당하고 요청을 view기술중 하나에게 처리하도록 넘겨버린다.
Section 13.3, “컨트롤러”에서 논의된 것처럼 Spring 웹 MVC프레임워크내 모든 컨트롤러는 ModelAndView인스턴스를 반환한다. Spring내 view는 view이름에 의해 할당되고 view결정자에 의해 결정된다. Spring은 다수의 view결정자를 가진다. 우리는 그것들의 대부분의 목록을 볼것이며 두개의 예제를 제공한다.
Table 13.5. View 결정자(Resolvers)
ViewResolver | 설명 |
---|---|
AbstractCachingViewResolver | 캐싱 view를 다루는 추상 view결정자. 종종 view를 사용되기 전에 준비작업이 필요하다. 이 view결정자를 확장하는 것은 view의 캐싱을 제공한다. |
XmlViewResolver | Spring의 bean팩토리 처럼 같은 DTD를 가진 XML내 쓰여진 설정파일을 가져오는 ViewResolver의 구현물. 디폴트 설정 파일은 /WEB-INF/views.xml이다. |
ResourceBundleViewResolver | 번들 basename에 의해 명시된 ResourceBundle내 bean정의를 사용하는 ViewResolver의 구현물. 번들은 대개 클래스패스내 위치한 프라퍼티파일내 명시된다. 디폴트 파일명은 views.properties이다. |
UrlBasedViewResolver | 추가적인 맵핑 정의 없이 URL로 상징적인 view이름의 직접적인 결정을 허락하는 ViewResolver의 간단한 구현물. 이것은 당신의 상징적인 이름이 임의의 맵핑의 필요성이 없는 직접적인 방법으로 당신의 view자원의 이름을 대응한다면 적당하다. |
InternalResourceViewResolver | InternalResourceView(예를 들면 서블릿과 JSP)와 JstlView, TilesView와 같은 하위 클래스를 지원하는 UrlBasedViewResolver의 편리한 하위 클래스. 이 결정자에 의해 생성되는 모든 view를 위한 view클래스는 setViewClass를 통해 정의될수 있다. 상세사항을 위해 UrlBasedViewResolver's의 JavaDoc를 보라. |
VelocityViewResolver / FreeMarkerViewResolver | 직접적인 VelocityView(예를 들면 Velocity템플릿) 또는 FreeMarkerView와 그것들의 사용자 지정 하위 클래스를 지원하는 UrlBasedViewResolver의 편리한 하위 클래스. |
예제처럼 view기술로 JSP를 사용할때 당신은 UrlBasedViewResolver을 사용할수 있다. 이 view결정자는 view이름을 URL로 번역하고 view를 표현하기 위해 요청을 RequestDispatcher로 보낸다.
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
view이름으로 test를 반환할때 이 view결정자는 /WEB-INF/jsp/test.jsp로 요청을 보낼 RequestDispatcher로 요청을 보낼것이다.
웹 애플리케이션내 서로 다른 view기술을 혼합해서 사용할때 당신은 ResourceBundleViewResolver을 사용할수 있다.
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> <property name="defaultParentView" value="parentView"/> </bean>
ResourceBundleViewResolver는 basename에 의해 구별되는 ResourceBundle을 추정하고 각각의 view를 위해 이것은 결정하기 위한 제안된다. 이것은 view 클래스처럼 프라퍼티 [viewname].class의 값과 view url처럼 프라퍼티 [viewname].url의 값을 사용한다. 당신이 볼수 있는 것처럼 확장순서대로 프라퍼티 파일내 모든 view로부터 당신은 부모(parent) view를 구별할수 있다. 예들 들면 이 방법으로 당신은 디폴트 view클래스를 정의할수 있다.
캐싱부분 노트(note) - 결정될수 있는 AbstractCachingViewResolver 캐시 view인스턴스의 하위클래스. 이것은 어떤 view기술을 사용할때 성능을 향상시킨다. cache프라퍼티를 false로 셋팅함으로써 캐시를 끌수도 있다. 게다가 당신이 수행시 어떤 view를 재생할수 있는 요구사항(예를 들면 Velocity템플릿이 변경될 때)을 가진다면 당신은 removeFromCache(String viewName, Locale loc)메소드를 사용할수 있다.
Spring은 하나의 view결정자보다 많은 수의 결정자를 지원한다. 이것은 당신에게 결정자를 묶는것을 가능하게 한다. 예를 들면 어떤 상황에서 특정 view를 오버라이드한다. view결정자를 묶는것은 당신의 애플리케이션 컨텍스트에 하나 이상의 결정자를 추가하는것처럼 상당히 일관적이다. 만약 필요하다면 order를 정의하기 위해 order프라퍼티를 셋팅하라. 더 큰 order프라퍼티는 나중에 view결정자가 묶음내 위치시킬것이다.
다음의 예제에서 view결정자의 묶음은 두개의 결정자인 InternalResourceViewResolver(체인내에서 마지막 결정자(resolver)처럼 자동으로 위치가 정해지는)와 엑셀 view(InternalResourceViewResolver에 의해 지원되지 않는)를 정의하기 위한 XmlViewResolver로 이루어진다.
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="order" value="1"/>
<property name="location" value="/WEB-INF/views.xml"/>
</bean>
<!-- in views.xml -->
<beans>
<bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>
만약 특정 view결정자가 view결과를 내지 않을때 Spring은 다른 view결정자가 설정되었다면 보기 위해 컨텍스트를 조사할것이다. 만약 추가적인 view결정자가 있다면 이것은 그것들을 조사하기 위해 지속될것이다. 만약 그렇지 않다면 Exception을 던질것이다.
당신은 염두해 두고 있는것을 유지해야한다. view결정자의 규칙은 view결정자가 찾을수 없는 view를 표시하기 위해 null을 반환할수 있다는 것을 말한다. 어쨋든 모든 view결정자기 이것을 하지는 않는다. 이것은 결정자가 view가 존재하는지 존재하지 않는지 검사할수 없을때와 같은 몇몇 경우에 야기된다. 예를 들면 InternalResourceViewResolver는 내부적으로 RequestDispatcher를 사용하고 디스패치(dispatching)는 JSP가 존재할때 설정하는 유일한 방법이다. 이것은 한번에 한하여 수행될수 있다. VelocityViewResolver와 몇몇 다른것들을 위해 같은것이 묶인다. 만약 당신이 존재하지 않는 view를 보고하지 않은 view결정자를 처리한다면 view결정자를 위한 JavaDoc를 체크하라. 이 결과처럼 마지막이 아닌 다른 어느위치내 묶음안에 InternalResourceViewResolver을 두는것은 InternalResourceViewResolver이 언제나 view를 반환하기 때문에 완전하게 조사되지 않는 묶음이라는 결과를 만들어낼것이다.
이미 언급된것처럼, 컨트롤러는 대개 view결정자가 특정 view기술을 결정하는 논리적인 view명을 반환한다. Servlet/JSP 엔진을 통해 실질적으로 처리되는 JSP와 같은 view기술을 위해, 이것은 대개 Servlet API의 RequestDispatcher.forward() 나 RequestDispatcher.include()를 통해 내부 forward나 include를 이슈화하는것을 끝낼 InternalResourceViewResolver/InternalResourceView을 통해 다루어진다. Velocity, XSLT, 등등과 같은 다른 view기술을 위해, view자체는 응답 스트림에 컨텐츠를 생성한다.
때때로 view가 표현되기 전에 클라이언트로 HTTP redirect해서 돌아가는 것을 이슈화하는 것이 바람직하다. 이것은 하나의 컨트롤러가 POST된 데이터를 가지고 호출되고 응답이 다른 컨트롤러(폼서브밋으로부터 성공한 경우에)로 위임되었을때의 예제를 위해 바람직하다. 이 경우, 대개의 내부 forward는 다른 컨트롤러가 POST된 같은 데이터를 볼것이라는 것을 의미한다. 결과가 사용자가 폼 데이터의 이중 서브밋을 수행하는 것의 가능성을 제거할 것을 표시하기 전에 redirect하기 위한 다른 이유를 기대하는 것을 혼동할수 있다면 잠재적으로 문제의 소지가 있다. 브라우저는 초기 POST를 보낼것이다. 현재 페이지는 GET보다는 POST의 결과를 반영하지 않는다. 그래서 refresh를 통해 같은 데이터를 다시 POST할수 있는 방법은 없다. refresh는 결과페이지의 GET을 강제로 수행할뿐 아니라 초기 POST데이터를 다시 보내지는 않는다.
컨트롤러 응답의 결과처럼 redirect를 강제로 수행하기 위한 하나의 방법은 생성하기 위한 컨트롤러와 Spring RedirectView의 인스턴스를 반환하는 것이다. 이 경우, DispatcherServlet은 대개의 view결정 기법을 사용하지 않을것이지만 이미 주어진 view처럼 작동하기 위해 요청할것이다.
RedirectView는 HTTP redirect로 클라이언트 브라우저로 돌아갈 HttpServletResponse.sendRedirect()호출을 이슈화하는것을 간단히 끝낸다. 모든 모델 속성은 HTTP쿼리 파라미터처럼 간단히 나타난다. 이것은 모델이 오직 문자열 형태 HTTP쿼리 파라미터로 변환될수 있는 객체만(대개 String이나 String으로 변환가능한)을 포함해야하는것을 의미한다.
만약 RedirectView을 사용하고 view가 컨트롤러 자체에 의해서 생성된다면, 최소한 redirect URL이 컨트롤러로 삽입되는것을 선호할것이다. 그래서 이것은 컨트롤러로 태울(baked into)뿐 아니라 view명과 함께 컨텍스트내 설정된다.
RedirectView사용이 잘 작동하는 동안, 컨트롤러자체가 RedirectView를 생성한다면, 컨트롤러는 redirection이 발생하는것을 감지한다. 이것은 실제로 차선책이고 너무 타이트하게 커플링한다. 컨트롤러는 응답이 다루어지는 방법에 대해 다루지는 않을것이다. 이것은 대개 삽입되는 view명의 개념에서만 생각한다.
특수한 redirect: 접두사는 이것이 달성되도록 허용한다. 만약 view명이 접두사 redirect를 가지고 반환된다면, UrlBasedViewResolver(과 모든 하위클래스)는 redirect가 필요하다는것을 특별히 표시하는것처럼 이것을 인식할것이다. view명의 나머지는 redirect URL처럼 처리될것이다.
net 효과는 컨트롤러가 RedirectView를 반환하는 것과 같다. 하지만 컨트롤러 자체는 논리적인 view명의 개념에서 다루어진다. redirect:/my/response/controller.html과 같은 논리적인 view명은 현재 서블릿 컨텍스트에 상대적으로 redirect 될것이다. 반면에 redirect:http://myhost.com/some/arbitrary/path.html와 같은 이름은 절대경로의 URL로 redirect할것이다. 중요한 것은 redirect view명이 다른 논리적인 view명처럼 컨트롤러내로 삽입되는것이다. 컨트롤러는 redirection이 발생하는 것을 감지하지 않는다.
UrlBasedViewResolver와 하위클래스에 의해 해석될 view명을 위한 접두사인 특별한 forward:를 사용하는 것은 가능하다. 이 모든것은 URL을 고려하는 view명의 나머지에 InternalResourceView(궁극적으로는 RequestDispatcher.forward() 하는)를 생성한다. 그러므로, InternalResourceViewResolver/InternalResourceView를 사용할때 이 접두사를 결코 사용하지 않는다. 하지만 당신이 다른 view기술을 사용할때 잠재적으로 사용한다. 몇몇 경우 Servlet/JSP엔진에 의해 다루어지는 자원에 대해 발생하는 forward를 강요한다. 만약 당신이 많은 것을 할 필요가 있다면, 당신은 다중 view결정자를 묶을수 있다.
redirect: 접두사를 사용할때, 접두사를 가진 view명이 컨트롤러로 삽입된다면, 어떤 특별한 것을 감지하지 않는 컨트롤러는 응답을 다루는 개념에서 발생한다.
Spring구조의 대부분은 Spring 웹 MVC프레임워크가 하는것처럼 국제화를 지원한다. DispatcherServlet은 당신에게 클라이언트 로케일을 사용하여 메시지를 자동적으로 결정하도록 한다. 이것은LocaleResolver객체를 사용해서 수행한다.
요청이 들어올때 DispatcherServlet는 로케일 결정자를 찾고 로케일 결정자가 찾아진다면 로케일을 셋팅하기 위해 그 결정자를 사용한다. RequestContext.getLocale()메소드를 사용하여 당신은 로케일 결정자에 의해 결정된 로케일을 언제나 가져올수 있다.
자동적인 로케일 전환외에도 당신은 핸들러 맵핑에 인터셉터(핸들러 맵핑 인터셉터의 좀더 다양한 정보를 위해 Section 13.4.3, “요청 가로채기 - HandlerInterceptors 인터페이스”를 보라)를 첨부할수 있다. 특정 상황하에 로케일을 변경하는 것은 요청의 파라미터에 기반한다.
로케일 결정자와 인터셉터는 org.springframework.web.servlet.i18n패키지내 모두 명시되고 일반적인 방법으로 당신의 애플리케이션 컨텍스트내 설정된다. 여기에 Spring에 포함된 로케일 결정자의 선택된 일부가 있다.
이 로케일 결정자는 클라이언트의 브라우저에 의해 보내어진 요청내 accept-language헤더를 조사한다. 언제나 이 헤더 필드는 클라이언트의 OS시스템의 로케일을 포함한다.
이 로케일 결정자는 로케일이 정의되었다면 보기 위해 클라이언트내 존재하는 Cookie를 조사한다. 만약 쿠키가 존재한다면 그 특정 로케일을 사용한다. 로케일 결정자의 프라퍼티를 사용하여 당신은 최대생명(maximum age)만큼 쿠키의 이름을 명시할수 있다. 아래에서 CookieLocaleResolver를 정의하는 예제를 보라.
<bean id="localeResolver">
<property name="cookieName" value="clientlanguage"/>
<!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
<property name="cookieMaxAge" value="100000">
</bean>
Table 13.6. CookieLocaleResolver 프라퍼티
프라퍼티 | 디폴트 값 | 설명 |
---|---|---|
cookieName | classname + LOCALE | 쿠키의 이름 |
cookieMaxAge | Integer.MAX_INT | 쿠키가 클라이언트에 일관적으로 머무를 최대시간. 만약 -1이 정의된다면 쿠키는 저장되지 않는다. 이것은 단지 클라이언트가 브라우저는 닫을때 까지만 사용가능하다. |
cookiePath | / | 이 파라미터를 사용하여 당신은 당신 사이트의 특정부분을 위해 쿠키의 가시성(visibility)에 제한을 둘수 있다. cookiePath가 정의되었을때 쿠키는 오직 그 경로와 그 하위경로에서만 볼수 있을것이다. |
당신은 LocaleChangeInterceptor을 사용해서 로케일을 변경할수 있다. 이 인터셉터는 하나의 핸들러 맵핑(Section 13.4, “Handler mappings”을 보라.)에 추가될 필요가 있다. 이것은 요청내 파라미터를 찾아내고 로케일(이것은 컨텍스트내 존재하는 LocaleResolver의 setLocale()을 호출한다.)을 변경한다.
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> <property name="paramName" value="siteLanguage"/> </bean> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="localeChangeInterceptor"/> </list> </property> <property name="mappings"> <value>/**/*.view=someController</value> </property> </bean>
siteLanguage라는 이름의 파라미터를 포함하는 모든 *.view형태의 자원 호출은 지금 로케일을 변경할것이다. 그래서 다음 URL인 http://www.sf.net/home.view?siteLanguage=nl을 위한 요청은 사이트 언어를 네델란드어로 변경할것이다.
Spring 웹 MVC프레임워크가 제공되는 테마(theme) 지원은 테마가 적용된 애플리케이션의 룩앤필을 좀더 향상되게 해준다. 테마는 기본적으로 애플리케이션의 시작적인 스타일에 영향을 주는 정적 자원(대개 스타일시트와 이미지)의 집합이다.
당신이 웹 애플리케이션내 테마를 사용하고자 한다면, 당신은 org.springframework.ui.context.ThemeSource을 셋업헤야할것이다. WebApplicationContext인터페이스는 ThemeSource를 확장하지만 전용 구현물에 대한 응답을 위임한다. 디폴트로 위임은 classpath의 가장 상위의 프라퍼티파일로부터 로드하는 org.springframework.ui.context.support.ResourceBundleThemeSource가 될것이다. 사용자정의 ThemeSource구현물을 사용하길 원하거나 ResourceBundleThemeSource의 basename접두사를 설정할 필요가 있다면, 예약된 이름은 "themeSource"을 가지고 애플리케이션 컨텍스트내 bean을 등록할수 있다. 웹 애플리케이션 컨텍스트는 bean을 자동으로 감지하고 시작한다.
ResourceBundleThemeSource를 사용할때, 테마는 간단한 프라퍼티 파일내 정의된다. 프라퍼티 파일은 테마를 만드는 자원을 목록화한다. 다음은 프라퍼티 파일의 예제이다.
styleSheet=/themes/cool/style.css background=/themes/cool/img/coolBg.jpg
프라퍼티 파일의 key들은 view코드로부터 테마의 요소를 참조하기 위해 사용되는 이름들이다. JSP를 위해서는 대개 spring:message태그와 매우 유사한 spring:theme 사용자정의 태그를 사용하여 수행된다. 다음의 JSP 일부분은 룩앤필을 커스터마이징하기 위해 위에서 정의된 테마를 사용한다.
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <html> <head> <link rel="stylesheet" href="<spring:theme code="styleSheet"/>" type="text/css"/> </head> <body background="<spring:theme code="background"/>"> ... </body> </html>
디폴트에 의해, ResourceBundleThemeSource는 빈(empty) basename접두사를 사용한다. 결과처럼, 프라퍼티 파일은 classpath의 가장 상위에서 로드될것이다. 그래서 우리는 classpath의 가장 상위 디렉토리(이를테면, /WEB-INF/classes)에 cool.properties 테마 정의를 둘것이다. ResourceBundleThemeSource는 테마의 완벽한 국제화를 허용하는 표준 Java자원 번들 로딩 기법을 사용한다. 예를 들어, 우리는 특별한 배경이미지(이를테면, 네델란드어의 텍스트를 가진)를 참조하는 /WEB-INF/classes/cool_nl.properties를 가질수 있다.
지금 우리는 정의된 테마를 가진다. 해야할 것중에 남은것은 사용할 테마를 결정하는 것이다. DispatcherServlet은 사용할 ThemeResolver구현물을 찾기 위해 "themeResolver"라는 이름의 bean을 검색할것이다. 테마 결정자는 LocalResolver와 같은 방법으로 작동한다. 이것은 특정 요청을 위해 사용되는 테마를 감지하고 요청의 테마를 변경할수 있다. 다음의 테마 결정자는 Spring에 의해 제공된다.
Table 13.7. ThemeResolver 구현물
클래스 | 상세설명 |
---|---|
FixedThemeResolver | "defaultThemeName" 프라퍼티를 사용하여 셋팅된 고정된 테마를 선택한다. |
SessionThemeResolver | 테마는 사용자 HTTP세션에서 유지된다. 이것은 단지 각각의 세션을 위해 한번만 셋팅될 필요가 있을뿐 아니라 세션간에 지속되지 않는다. |
CookieThemeResolver | 선택된 테마는 user-agent 머신의 쿠키에 저장된다. |
Spring은 또한 간단한 요청 파라미터를 포함하는 모든 요청에 테마를 변경하도록 해주는 ThemeChangeInterceptor도 제공한다.
Spring은 웹 애플리케이션내 파일업로드를 다루기 위한 멀티파트(multipart)지원을 내장한다. 멀티파트 지원을 위한 디자인은 org.springframework.web.multipart패키지내 명시된 플러그인 가능한 MultipartResovler객체로 할수 있다. 특별히 Spring은 Commons FileUpload(http://jakarta.apache.org/commons/fileupload)와 COS FileUpload (http://www.servlets.com/cos)을 사용하기 위해 MultipartResolver를 제공한다. 파일을 업로드하는 지원되는 방법은 이 장의 끝에 서술될것이다.
디폴트에 의해 멀티파트 핸들링은 몇몇 개발자들이 그들 스스로 멀티파트를 다루길 원하는 것처럼 Spring에 의해 수행되지 않을것이다. 당신은 웹 애플리케이션 컨텍스트에 멀티파트 결정자를 추가함으로써 당신 스스로 이것을 가능하게 할수 있다. 당신이 그렇게 한 후에 각각의 요청은 그것이 멀티파트를 포함하는지 보기 위해 조사할것이다. 만약 멀티파트가 찾아지지 않는다면 요청은 기대되는 것처럼 계속될것이다. 어쨌든 멀티파트가 요청내에 발견된다면 당신의 컨텍스트내 명시된 MultipartResolver가 사용될것이다. 그리고 나서 요청내 멀티파트 속성은 다른 속성처럼 처리될것이다.
다음의 예제는 CommonsMultipartResolver를 사용하는 방법을 보여준다.
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="100000"/>
</bean>
이것은 CosMultipartResolver을 사용하는 예제이다.
<bean id="multipartResolver" class="org.springframework.web.multipart.cos.CosMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="100000"/>
</bean>
물론 당신은 작업을 수행하는 멀티파트 결정자를 위해 클래스패스내 적당한 jar파일을 복사해 넣을 필요가 있다. CommonsMultipartResolver의 경우 당신은 commons-fileupload.jar을 사용할 필요가 있다. CosMultipartResolver의 경우 cos.jar를 사용한다.
지금 당신은 멀티파트 요청을 다루기 위해 Spring을 셋업하는 방법을 보았다. 이것을 실제로 사용하기 위해 어떻게 해야 하는지에 대해 얘기해보자. Spring DispatcherServlet가 멀티파트 요청을 탐지했을때 이것은 당신의 컨텍스트내 선언된 결정자를 활성화시키고 요청을 처리한다. 기본적으로 하는 것은 멀티파트를 위한 지원을 가진 MultipartHttpServletRequest로 최근의 HttpServletRequest을 포장해 넣는것이다. MultipartHttpServletRequest을 사용하면 당신은 이 요청에 의해 포함된 멀티파트에 대한 정보를 얻을수 있고 당신 컨트롤러내 스스로 멀티파트를 얻을수 있다.
MultipartResolver가 그 작업을 마친후 요청은 다른것처럼 처리될것이다. 이것을 사용하기 위해 당신은 파일 업로드 필드를 가진 폼을 생성하고 Spring이 폼의 필드에 바인드 하도록 하자. 사용자가 실제로 파일을 업로드하기 위해, 우리는 (HTML)폼을 생성해야만 한다.
<html> <head> <title>Upload a file please</title> </head> <body> <h1>Please upload a file</h1> <form method="post" action="upload.form" enctype="multipart/form-data"> <input type="file" name="file"/> <input type="submit"/> </form> </body> </html>
당신이 볼수 있는것처럼, bean의 프라퍼티가 byte[]를 가지는 데 이어 명명된 필드를 생성한다. 게다가 우리는 multipart필드을 인코딩하는 방법을 브라우저에 알려주는 인코딩 속성(enctype="multipart/form-data")을 추가했다(이것을 잊지말라.).
다른 프라퍼티처럼 그것은 자동적으로 당신이 ServletRequestDatabinder로 사용자 정의 편집기(editor)를 틍록하기 위해 객체내 바이너리 데이터를 두기 위한 문자열이나 원시타입으로 형변화되지 않는다. 파일을 다루고 빈에 결과를 셋팅하기 위해 사용가능한 두개의 편집기가 있다. StringMultipartEditor는 파일을 문자열로 형변환(사용자 정의 문자셋을 사용하여)하는 능력을 가진다. 그리고 ByteArrayMultipartEditor는 파일을 바이트 배열로 형변환한다. 그것들은 CustomDateEditor가 하는것처럼 작동한다.
그리고 웹사이트내 폼을 사용하여 파일을 업로드하기 위해 결정자를 선언하라. 컨트롤러를 위한 url맵핑은 빈과 컨트롤러 자신을 처리할것이다.
<beans>
<!-- lets use the Commons-based implementation of the MultipartResolver interface -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/upload.form=fileUploadController
</value>
</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>
</beans>
그후 컨트롤러와 파일 프라퍼티를 가지는 실질적인 빈을 생성하라.
public class FileUploadController extends SimpleFormController { protected ModelAndView onSubmit( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws ServletException, IOException { // 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 } // well, let's do nothing with the bean for now and return return super.onSubmit(request, response, command, errors); } protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws ServletException { // 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 them } } public class FileUploadBean { private byte[] file; public void setFile(byte[] file) { this.file = file; } public byte[] getFile() { return file; } }
당신이 볼수 있는 것처럼 FileUploadBean은 파일 데이터를 가지는 byte[]타입의 프라퍼티를 가진다. 컨트롤러는 Spring이 빈에 의해 명시된 프라퍼티를 위한 찾을수 있는 멀티파트 객체 결정자가 변환하는 방식을 알도록 사용자 지정 편집기를 등록한다. 이 예제에서 빈의 byte[] 프라퍼티로 하는것은 아무것도 없다. 하지만 실제로 당신은 당신이 무엇을 원하든지(데이터베이스에 저장을 하거나 누군가에게 메일을 보내더라도) 할수 있다.
파일이 (폼 지원)객체의 문자열타입 프라퍼티에 일관적으로 바운드되는 예제는 다음과 같을것이다.
public class FileUploadController extends SimpleFormController { protected ModelAndView onSubmit( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws ServletException, IOException { // 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 } // well, let's do nothing with the bean for now and return return super.onSubmit(request, response, command, errors); } protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws ServletException { // 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 object and convert them } } public class FileUploadBean { private String file; public void setFile(String file) { this.file = file; } public String getFile() { return file; } }
물론, 이것은 일반적인 텍스트 파일을 업로드하는 개념을 설명하는 마지막 예제이다(이것은 이미지 파일을 업로드하는 경우에는 잘 작동하지 않을것이다.)
세번째(그리고 마지막) 옵션은 (폼 지원)객체의 클래스에 선언된 MultipartFile 프라퍼티에 직접적으로 바인딩하는 것이다. 이 경우, 수행될 형변환이 없기 때문에 사용자 정의 PropertyEditor를 등록할 필요가 없다.
public class FileUploadController extends SimpleFormController { protected ModelAndView onSubmit( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws ServletException, IOException { // 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 } // well, let's do nothing with the bean for now and return return super.onSubmit(request, response, command, errors); } } public class FileUploadBean { private MultipartFile file; public void setFile(MultipartFile file) { this.file = file; } public MultipartFile getFile() { return file; } }
2.0버전에서, Spring은 JSP와 Spring 웹 MVC를 사용할때 폼 요소를 다루기 위해 포괄적인 데이터 바인딩 태그를 제공한다. 각각의 태그는 사용하기에 친숙하고 직관적이도록 만들기 위해 관련 HTML태그의 속성을 지원한다. 태그로 생성된 HTML은 HTML 4.01/XHTML 1.0 과 호환된다.
다른 폼/input 태그 라이브러리와는 달리, Spring의 폼 태그 라이브러리는 당신의 컨트롤러가 다루는 command객체와 참조 데이터에 접근하여 Spring MVC와 통합된다. 다음의 예제에서 보는것처럼, 폼 태그는 JSP개발, 읽기및 유지보수를 좀더 쉽게 만든다.
폼 태그를 통해 각각의 태그가 사용된 예제를 보자. 우리는 해설이 필요한 태그에 대한 생성된 HTML부분을 포함한다.
폼 태그 라이브러리는 spring.jar내 포함되어 있다. 라이브러리 서술자는 spring-form.tld 이다.
이 라이브러리로부터 태그를 사용하기 위해, JSP페이지의 가장 상위에 다음의 지시자를 추가한다.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
form 이 태그명의 접두사일때, 당신은 이 라이브러리로부터 이 태그를 사용한다.
이 태그는 HTML 'form' 태그를 표시하고 바인딩을 위해 바인딩 경로를 내부 태그로 나타낸다. PageContext내 command객체에 두어서, command객체는 내부 태그에 의해 접근할수 있다. 이 라이브러리내 다른 모든 태그는 form 태그의 내포된다.
User 라고 불리는 도메인 객체가 있다고 가정하자. 이것은 firstName 과 lastName과 같은 프라퍼티를 가지는 JavaBean이다. 우리는 form.jsp를 반환하는 폼 컨트롤러의 폼지원객체처럼 이것을 사용할것이다. 아래는 form.jsp의 예제이다.
<form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form>
firstName 과 lastName 값들은 페이지 컨트롤러에 의해 PageContext에 위치하는 command객체로부터 가져온다. 내부 태그가 form태그와 함께 사용되는 방법의 좀더 복잡한 예제를 보기 위해 계속 보라.
생성된 HTML은 표준적인 형태처럼 보인다.
<form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value="Harry"/></td> <td></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value="Potter"/></td> <td></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form>
선행 JSP는 폼 지원 객체의 변수명이 'command'라고 가정한다. 다른 이름으로 폼 지원객체를 모델에 넣는다면, 명명된 변수에 폼을 바인딩할수 있다.
<form:form commandName="user">
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName" /></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName" /></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
이 태그는 바운드(bound)값을 사용하여 'text' 타입으로 HTML 'input' 태그를 표시한다. 이 태그의 예제를 위해. Section 13.9.2, “form 태그”를 보라.
이 태그는 'checkbox' 타입의 HTML 'input' 태그를 표시한다.
User가 신문 예약구독자와 취미목록과 같은 선택물을 가진다고 가정해보자. 아래는 Preferences 클래스의 예제이다.
public class Preferences { private boolean receiveNewsletter; private String[] interests; private String favouriteWord; public boolean isReceiveNewsletter() { return receiveNewsletter; } public void setReceiveNewsletter(boolean receiveNewsletter) { this.receiveNewsletter = receiveNewsletter; } public String[] getInterests() { return interests; } public void setInterests(String[] interests) { this.interests = interests; } public String getFavouriteWord() { return favouriteWord; } public void setFavouriteWord(String favouriteWord) { this.favouriteWord = favouriteWord; } }
form.jsp 는 다음과 같을것이다.
<form:form> <table> <tr> <td>Subscribe to newsletter?:</td> <%-- Approach 1: Property is of type java.lang.Boolean --%> <td><form:checkbox path="preferences.receiveNewsletter"/></td> <td></td> </tr> <tr> <td>Interests:</td> <td> <%-- Approach 2: Property is of an array or of type java.util.Collection --%> Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/> Herbology: <form:checkbox path="preferences.interests" value="Herbology"/> Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/> </td> <td></td> </tr> <tr> <td>Favourite Word:</td> <td> <%-- Approach 3: Property is of type java.lang.Object --%> Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/> </td> <td></td> </tr> </table> </form:form>
checkbox 태그에 대해 3가지 접근법이 있다.
접근법 1 - java.lang.Boolean 타입의 값에 할당할때, input(checkbox)는 할당된 값이 true라면 'checked' 처럼 표시된다. value 속성은 setValue(Object) 값 프라퍼티의 값을 결정한다.
접근법 2 - array 이나 java.util.Collection 타입의 값을 할당할때, input(checkbox)은 설정된 setValue(Object)값이 할당된 Collection에 존재한다면 'checked' 처럼 표시된다.
접근법 3 - 다른 타입의 값을 할당하기 위해, input(checkbox)은 설정된 setValue(Object) 이 할당된 값과 일치한다면, 'checked' 로 표시된다.
이러한 접근법에도 불구하고, 같은 HTML구조가 생성된다. 아래는 몇가지 checkbox를 가지는 HTML이다.
<tr> <td>Interests:</td> <td> Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/> <input type="hidden" value="1" name="_preferences.interests"/> Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/> <input type="hidden" value="1" name="_preferences.interests"/> Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/> <input type="hidden" value="1" name="_preferences.interests"/> </td> <td></td> </tr>
당신이 보기를 기대하지 않는 것은 각각의 checkbox뒤 추가적인 hidden필드이다. HTML페이지내 checkbox가 체크되지 않았을때, 폼이 서브릿되었을때 HTTP요청 파라미터의 일부로 서버에 보내지지는 않을것이다. 그래서 우리는 Spring 폼 데이터 바인딩이 작업하도록 하기 위해 HTML내 이러한 흠을 위한 대안이 필요하다. checkbox 태그는 각각의 checkbox를 위한 밑줄("_") 에 의해 접두사 처리된 hidden파라미터를 포함하는 Spring규칙을 따른다. 이렇게 함으로써, 당신은 “checkbox가 폼에서 보여지고 폼 데이터가 checkbox의 상태를 반영하도록 바운드할 객체를 원한다고” Spring에 효과적으로 알린다.
이 태그는 'radio' 타입을 가진 HTML 'input' 태그를 표현한다.
전형적인 사용법 패턴은 값은 다르지만 같은 프라퍼티에 대해 바운드되는 다중 태그 인스턴스를 포함할것이다.
<tr> <td>Sex:</td> <td>Male: <form:radiobutton path="sex" value="M"/> <br/> Female: <form:radiobutton path="sex" value="F"/> </td> <td></td> </tr>
이 태그는 바운드 값을 사용하는 'password' 타입을 가진 HTML 'input' 태그를 표현한다.
<tr> <td>Password:</td> <td> <form:password path="password" /> </td> </tr>
이 태그는 HTML 'select' 요소를 표현한다. 이것은 내포된 option 과 options 태그의 사용처럼 선택된 옵션에 대한 데이터 바인딩을 지원한다.
User가 많은 skill 을 가진다고 가정해보자.
<tr> <td>Skills:</td> <td><form:select path="skills" items="${skills}"/></td> <td></td> </tr>
User의 skill이 Herbology라면, 'Skills' 의 HTML소스는 다음과 같을것이다.
<tr> <td>Skills:</td> <td><select name="skills" multiple="true"> <option value="Potions">Potions</option> <option value="Herbology" selected="true">Herbology</option> <option value="Quidditch">Quidditch</option></select></td> <td></td> </tr>
이 태그는 HTML 'option'을 표현한다. 이것은 바운드된 값에 기초하여 'selected'를 셋팅한다.
<tr> <td>House:</td> <td> <form:select path="house"> <form:option value="Gryffindor"/> <form:option value="Hufflepuff"/> <form:option value="Ravenclaw"/> <form:option value="Slytherin"/> </form:select> </td> </tr>
User의 house가 Gryffindor라면, 'House'의 HTML소스는 다음과 같을것이다.
<tr> <td>House:</td> <td> <select name="house"> <option value="Gryffindor" selected="true">Gryffindor</option> <option value="Hufflepuff">Hufflepuff</option> <option value="Ravenclaw">Ravenclaw</option> <option value="Slytherin">Slytherin</option> </select> </td> </tr>
이 태그는 HTML 'option' 태그의 목록을 표현한다. 이것은 바운드 값에 기초하여 'selected' 속성을 셋팅한다.
<tr> <td>Country:</td> <td> <form:select path="country"> <form:option value="-" label="--Please Select"/> <form:options items="${countryList}" itemValue="code" itemLabel="name"/> </form:select> </td> <td></td> </tr>
User 가 UK에서 살고있다면, 'Country'의 HTML소스는 다음과 같을것이다.
<tr> <td>Country:</td> <tr> <td>Country:</td> <td> <select name="country"> <option value="-">--Please Select</option> <option value="AT">Austria</option> <option value="UK" selected="true">United Kingdom</option> <option value="US">United States</option> </select> </td> <td></td> </tr> <td></td> </tr>
예제가 보여주는 것처럼, options태그와 option태그의 조합의 사용법은 같은 표준 HTML을 생성한다. 하지만 예제 "-- Please Select" 의 디폴트 문자열처럼 보여주기 위한 JSP내 값을 명시하는것을 허용한다.
이 태그는 HTML 'textarea'를 표현한다.
<tr> <td>Notes:</td> <td><form:textarea path="notes" rows="3" cols="20" /></td> <td><form:errors path="notes" /></td> </tr>
이 태그는 바운드 값을 사용하여 'hidden' 타입을 가진 HTML 'input' 를 표현한다. 바운드되지 않은 hidden값을 서브밋하기 위해, 'hidden' 타입을 가진 HTML input 태그를 사용하라.
<form:hidden path="house" />
우리가 hidden값으로 'house' 값을 서브밋하도록 선택한다면, HTML은 다음과 같을것이다.
<input name="house" type="hidden" value="Gryffindor"/>
이 태그는 HTML 'span' 태그내 필드 에러를 표현한다. 이것은 컨트롤러내 생성된 에러와 컨트롤러에 관련된 유효성체커(validators)에 의해 생성에러에 접근한다.
우리가 폼을 서브밋할때 firstName 과 lastName 필드를 위한 모든 에러 메시지를 보여주길 원한다고 가정하자. 우리는 UserValidator 라고 불리는 User 클래스의 인스턴스를 위한 유효성체커(validator)를 가진다.
public class UserValidator implements Validator { public boolean supports(Class candidate) { return User.class.isAssignableFrom(candidate); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required."); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required."); } }
form.jsp 은 다음과 같을것이다.
<form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <%-- Show errors for firstName field --%> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <%-- Show errors for lastName field --%> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form>
우리가 firstHame 과 lastName 필드내 빈 값을 가진 폼을 서브밋한다면, HTML은 다음과 같을것이다.
<form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <%-- Associated errors to firstName field displayed --%> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <%-- Associated errors to lastName field displayed --%> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form>
주어진 페이지를 위한 에러의 전체 목록은 보여주길 원하는가..? 아래의 예제는 errors 태그가 몇가지 기본적인 와이들카드 기능을 제공한다는 것을 보여준다.
path="*" - 모든 에러를 보여준다.
path="lastName*" - lastName 필드와 관련된 모든 에러를 보여준다.
아래의 예제는 필드뒤에 필드에 따른 에러를 보여주면서 페이지의 가장 상위에서 에러의 목록을 보여줄것이다.
<form:form> <form:errors path="*" cssClass="errorBox" /> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form>
HTML은 다음과 같이 보일것이다.
<form method="POST"> <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </form>
Spring은 당신의 요청이 적합한 컨트롤러에 의해 다루어지는 동안 발생하는 기대되지 않는 예외의 고통을 쉽게 하기 위해 HandlerExceptionResolvers제공한다. HandlerExceptionResolvers는 웹 애플리케이션 서술자인 web.xml내 당신이 명시할수 있는 예외 맵핑과 다소 비슷하다. 어쨌든 그들은 예외를 다루기 위한 좀더 유연한 방법을 제공한다. 그들은 예외가 던져질때 어떠한 핸들러가 수행되는지에 대한 정보를 제공한다. 게디가 예외를 다루는 프로그램적인 방법은 당신에게 요청이 다른 URL로 포워딩되기 전에 적절하게 응답하기 위한 방법을 위해 많은 옵션을 제공한다.(서블릿이 예외 맵핑을 명시하는것을 사용할때처럼 같은 결과를 낸다.)
더욱이 HandlerExceptionResolver을 구현하는 것은 오직 resolveException(Exception, Handler)메소드를 구현하고 ModelAndView를 반환하는 문제이다. 당신은 아마도 SimpleMappingExceptionResolver을 사용할지도 모른다. 이 결정자는 당신에게 던져지고 view이름에 그것을 맵핑하는 어떠한 예외의 클래스명을 가져오도록 할것이다. 이것은 서블릿 API로 부터 예외를 맵핑하는 기능과 기능적으로 유사하다. 하지만 이것은 또한 다른 핸들러로부터 잘 정제된 예외의 맵핑을 구현하는것이 가능하다.
많은 프로젝트를 위해, 규칙을 만들고 가능한 디폴트를 가지는 것은 필요하다. 설정에 대한 규칙의 테마는 현재 Spring 웹 MVC에서 명시적으로 지원된다. 이것이 의미하는 것은 당신이 명명규칙과 같은 것을 만들때, 핸들러 맵핑, view 결정자(resolver), ModelAndView 인스턴스를 셋업하기 위해 필요한 설정의 양을 충분히 줄일수 있다. 이것은 빠른 프로토타이핑을 위해 큰 이득이고 일관성의 정도를 더해준다.
![]() | Tip |
---|---|
Spring배포판은 이 부분에서 언급된 설정에 대한 규칙지원을 보여주는 웹 애플리케이션을 포함한다. 애플리케이션은 'samples/showcases/mvc-convention' 디렉토리에서 찾을수 있다. |
설정에 대한 규칙은 MVC의 3가지 핵심 영역(모델, 뷰, 컨트롤러)을 할당한다.
ControllerClassNameHandlerMapping 클래스는 요청 URL과 이러한 요청을 다루기 위한 Controller 인스턴스간에 맵핑을 판단하는 규칙을 사용하는 HandlerMapping 구현물이다.
예제 : 다음 Controller 구현물을 보라. 클래스의 name의 특별한 인지를 가진다.
public class ViewShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // the implementation is not hugely important for this example... } }
이것은 관련 Spring 웹 MVC설정 파일의 일부이다.
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewShoppingCart" class="x.y.z.ViewShoppingCartController"> <!-- inject dependencies as required... --> </bean>
ControllerClassNameHandlerMapping은 애플리케이션 컨텍스트내 정의된 다양한 핸들러(또는 Controller) bean 모두를 찾고
몇가지 더 많은 예제를 보라. 그러면 중심적인 생각이 즉시 친숙해질것이다.
WelcomeController는 '/welcome*' 요청 URL을 맵핑한다.
HomeController는 '/home*' 요청 URL을 맵핑한다.
IndexController는 '/index*' 요청 URL을 맵핑한다.
RegisterController는 '/register*' 요청 URL을 맴핑한다.
DisplayShoppingCartController 는 '/displayshoppingcart*' 요청 URL을 맵핑한다.
(대소문자에 대한 주의 - camel-case성질을 가지는 Controller 클래스명의 경우. 모두 소문자)
MultiActionController 핸들러 클래스의 경우, 생성된 맵핑은 좀더 복잡하다. 하지만 다소 덜 이해가 될수 있다. 다음의 모든 Controller 이름은 MultiActionController 구현물이 되는것으로 가정된다.)
AdminController는 '/admin/*' 요청 URL을 맵핑한다.
CatalogController는 '/catalog/*' 요청 URL을 맵핑한다.
xxxController 처럼 Controller 구현물 명명의 표준적인 규칙을 따른다면, ControllerClassNameHandlerMapping은 처음으로 정의하는 것의 지루함을 줄이고 잠재적으로 SimpleUrlHandlerMapping을 관리한다.
ControllerClassNameHandlerMapping 클래스는 AbstractHandlerMapping 기본 클래스를 확장해서 당신은 HandlerInterceptor 인스턴스를 정의할수있다.
ModelMap 클래스는 기본적으로 공통적인 명명 규칙에 충실한 View에 표시되는 객체를 추가할수 있는 실제보다 좋게 보이는 Map이다. 그 생각은 상세하게 설명하는 것보다는 간단하다. 나는 당신에게 몇가지 예제를 보여줄것이다.
다음의 Controller 구현물을 보자. 객체가 명시된 이름없이 ModelAndView에 추가되는 것에 주의하라.
public class DisplayShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { List cartItems = // get a List of CartItem objects User user = // get the User doing the shopping ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- the logical view name mav.addObject(cartItems); <-- look ma, no name, just the object mav.addObject(user); <-- and again ma! return mav; } }
ModelAndView 클래스는 객체가 추가될때 객체를 위한 key를 자동적으로 생성하는 사용자정의 Map 구현물인 ModelMap 클래스를 사용한다. 추가된 객체를 위한 이름을 판단하기 위한 전략은 User와 같은 단계적 객체의 경우, 객체 클래스의 짧은 클래스명을 사용하는 것이다. 단계적 객체를 위해 생성되는 이름의 몇가지 예제가 ModelMap 인스턴스를 두는 것을 아래에서보라.
추가된 x.y.User 인스턴스는 생성된 'user' 이름을 가질것이다.
x.y.Registration 인스턴스는 생성된 'registration' 이름을 가질것이다.
x.y.Foo 인스턴스는 생성된 'foo' 이름을 가질것이다.
추가된 java.util.HashMap 인스턴스는 생성된 'hashMap' 이름을 가질것이다. (당신은 아마도 'hashMap'이 다소 덜 직관적이기 때문에 이 경우 이름에 대해 명시적이길 원할것이다.)
null을 추가하는 것은 IllegalArgumentException이 던져지는 결과를 만든다. 잠재적으로 객체가 null로 추가된다면, 이름에 대해 명시적이길 원할것이다.).
Set, List 또는 배열 객체를 추가한 후 이름을 생성하기 위한 전략은 collection내 첫번째 객체의 짧은 클래스명을 가지고 collection을 검색한다. 그리고 name에 추가된 'List'를 가지고 사용한다. 몇가지 예제는 collection을 좀더 명백하게 하기 위해 이름 생성을 할것이다.
추가된 하나 이상의 x.y.User요소를 가진 x.y.User[] 배열은 생성된 'userList' 이름을 가질것이다.
추가된 하나 이상의 x.y.User요소를 가진 x.y.Foo[] 배열은 생성된 'fooList' 이름을 가질것이다.
추가된 하나 이상의 x.y.User요소를 가진 java.util.ArrayList는 생성된 'userList' 이름을 가질것이다.
추가된 하나 이상의 x.y.Foo 요소를 가진 java.util.HashSet은 생성된 'fooList' 이름을 가질것이다.
빈 java.util.ArrayList는 전혀 추가되지 않을것이다.(이를테면, adObject(..) 호출은 기본적으로 무연산 명령이 될것이다. ).
RequestToViewNameTranslator 인터페이스는 논리적인 view명이 명시적으로 제공되지 않았을때 논리적인 View 명을 판단하는 책임을 가진다. 이것은 하나의 구현물인 다소 교활하게 명명된 DefaultRequestToViewNameTranslator 클래스를 제외한다.
DefaultRequestToViewNameTranslator는 이러한 형태로 요청 URL을 논리적인 view명으로 맵핑한다. 다음의 Controller 구현물과 관련된 Spring 웹 MVC XML 설정을 보라.
public class RegistrationController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // process the request... ModelAndView mav = new ModelAndView(); // add data as necessary to the model... return mav; // notice that no View or logical view name has been set } }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <!-- this bean with the well known name generates view names for us --> <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/> <bean class="x.y.RegistrationControllerController"> <!-- inject dependencies as necessary --> </bean> <!-- maps request URLs to Controller names --> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
반환된 ModelAndView에 셋팅되는 View 나 논리적인 view이름은 없다. 이것은 요청 URL로부터 논리적인 view명를 생성할 DefaultRequestToViewNameTranslator이다. ControllerClassNameHandlerMapping와 함께 사용되는 위 RegistrationControllerController의 경우, 'http://localhost/registration.html' 요청 URL은 DefaultRequestToViewNameTranslator에 의해 생성된 'registration'의 논리적인 view명의 결과가 된다. 논리적인 view명은 InternalResourceViewResolver bean에 의해 '/WEB-INF/jsp/registration.jsp' view로 해석될것이다.
![]() | Tip |
---|---|
당신은 DefaultRequestToViewNameTranslator bean을 명시적으로 정의할 필요가 없다. 당신이 DefaultRequestToViewNameTranslator의 디폴트 셋팅을 승낙한다면, 명시적으로 설정되지 않았을때 Spring 웹 MVC DispatcherServlet이 클래스의 인스턴스를 인스턴스화할것이라는 사실에 의존할수 있다. |
물론, 당신이 디폴트 셋팅을 변경할 필요가 있다면, 당신은 자체적인 DefaultRequestToViewNameTranslator bean을 명시적으로 설정할 필요가 있다. 설정될수 있는 다양한 프라퍼티의 상세한 설명을 위해서 DefaultRequestToViewNameTranslator 클래스를 위한 편리한 Javadoc을 보라.
Spring 웹 MVC에 대한 더 많은 자원을 위한 링크와 위치를 아래에서 보라.
Spring 배포판은 단계별 접근법을 사용하여 완전한 Spring 웹 MVC-기반의 애플리케이션을 빌드하여 독자를 가이드하는 Spring 웹 MVC 튜토리얼을 포함한다. 이 튜토리얼은 Spring 배포판에서 'docs' 디렉토리에 있다. 온라인 버전은 Spring 프레임워크 웹사이트에서 찾을수 있다..
Seth Ladd와 다른 사람들에 의해 만들어진 “Expert Spring Web MVC and WebFlow” 책은 Spring 웹 MVC 의 완벽한 하드카피 소스이다.