출처 : http://blog.outsider.ne.kr/902
이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.
Part V. 웹
레퍼런스 문서의 이 부분에서는 스프링 프레임워크의 표현계층(특히 웹기반의 표현계층)에 대한 지원을 다룬다.
스프링 프레임워크의 웹프레임워크인 Spring Web MVC는 앞의 두 개의 장에서 다룬다. 나머지 장에서는 스프링 프레임워크와 다른 웹기술(Struts와 JSF 같은)의 통합에 대해서 다룬다.
이번 섹션에서는 마지막으로 스프링의 MVC 포틀릿 프레임워크를 다룬다.
- Chapter 16, 웹 MVC 프레임워크
- Chapter 17, 뷰 기술
- Chapter 18, 다른 웹 프레임워크와의 통합
- Chapter 19, Portlet MVC Framework
16. 웹 MVC 프레임워크
16.1 스프링 웹 MVC 프레임워크 소개
스프링 웹 모델-뷰-컨트롤러 (MVC) 프레임워크는 핸들러 매핑, 뷰 처리, 파일 업로드같은 로케일과 테마처리로 요청을 핸들러로 디스패치하는 DispatcherServlet을 중심으로 설계되었다. 기본 핸들러는 넓은 범위로 유연한 처리(handling) 메서드를 제공하는 @Controller와 @RequestMapping 어노테이션에 기반한다. 스프링 3.0부터는 @Controller 메카니즘도 @PathVariable 어노테이션과 다른 기능들을 통해서 RESTful 웹사이트와 어플리케이션을 생성할 수 있게 한다.
스프링 웹 MVC와 스프링의 일반적인 핵심 설계원리는 “확장에는 열려있고 수정에는 닫혀있다(Open for extension, closed for modification)” 원리이다.
스프링 웹 MVC 핵심 클래스의 일부 메서드들은 final이다. 개발자들은 자신만의 동작을 위해서 이러한 메서드들을 오버라이드할 수 없다. 이는 임의로 할 수 없지만 특히 원리를 명심해 두어야 한다.
이 원리에 대해서 알고 싶다면 Seth Ladd등이 쓴 Expert Spring Web MVC and Web Flow를 참고해라. 특히 첫번째 에디션의 117 페이지에 있는 "A Look At Design" 부분을 살펴봐라. 아니면 다음을 참고해라.
스프링 MVC를 사용할 때 final 메서드에는 어드바이스를 추가할 수 없다. 예를 들어 AbstractController.setSynchronizeOnSession() 메서드에는 어드바이스를 추가할 수 없다. AOP 프록시와 왜 final 메서드에는 어드바이스를 추가할 수 없는지는 Section 8.6.1, “AOP 프록시 이해하기”를 참고해라.
스프링 웹 MVC에서 커맨드 객체나 폼을 바인딩하기 위한 객체(form-backing object)에 어떤 객체라도 사용할 수 있다. 프레임워크에 특화된 인터페이스나 기반 클래스를 구현할 필요가 없다. 스프링의 데이터 바인딩은 아주 유연하다. 예를 들어 스프링의 데이터 바인딩은 타입 불일치를 시스템 오류가 아니라 어플리케이션이 평가할 수 있는 유효성검사 오류(validation errors)로 다룬다. 그러므로 폼객체가 유효하지 않은 제출을 처리하고 문자열을 적절하게 변환하려고 타임이 없는 문자열로 비즈니스 객체의 프로피티들을 복사할 필요가 없다. 대신에 때로는 비즈니스 객체로 직접 바인딩하는 것을 더 선호한다.
스프링의 뷰 처리(view resolution)는 엄청나게 유연하다. Controller가 보통 데이터를 가진 모델 Map을 준비하고 뷰 이름을 선택하는 담당을 하지만 Controller 응답 스트림에 직접 작성하고 요청을 완료할 수도 있다. 뷰 이름 처리는 빈 이름, 프로퍼티 파일이나 커스텀 ViewResolver 구현체로 파일의 확장자나 Accept 헤더의 내용협상(content type negotiation)을 통해서 설정할 수 있다. 모델(MVC에서 M)은 뷰 기술을 완전히 추상화할 수 있도록 Map 인터페이스이다. JSP, Velocity, Freemarker같은 템플릿 기반의 렌더링 기술과 직접 통합하거나 XML, JSON, Atom과 다른 타입의 컨텐츠들을 직접 생성할 수 있다. 모델 Map은 JSP 요청 속성, Velocity 템플릿 모델처럼 적절한 형식으로 변환된다.
16.1.1 스프링 웹 MVC의 기능
스프링 웹 플로우 (SWF, Spring Web Flow)는 웹 어플리케이션 페이지 흐름을 관리하는 최상의 솔루션이다.
SWF는 서블릿 환경과 포틀릿 환경에서 모두에서 스프링 MVC, 스트럿츠, JSF같은 프레임워크와 통합한다. 순사하게 요청 모델과는 반대로 대화식 모델의 이점을 갖는 비즈니스 처리과정을 가진다면 SWF가 해결책이 될 것이다.
SWF는 여러 상황에서 재사용할 수 있는 내장된 모쥴처럼 논리적인 페이지 흐름을 갖을 수 있도록 하고 비즈니스 처리과정을 유도하는 제어된 네비게이션드로 사용자를 도와주는 웹 어플리케이션 모듈을 구성하는데 최적이다.
SWF에 대한 자세한 내용은 Spring Web Flow 웹사이트 를 참고해라.
스프링의 웹 모듈은 웹을 지원하는 많은 독특한 기능들을 포함한다.
- 역할의 깔끔한 분리. 컨트롤러, 밸리데이터(validator), 커맨드 객체, 폼 객체, 모델 객체, DispatcherServlet, 핸들러 매핑, 뷰 리졸버등과 같은 각 역할들인 전용 객체들로 실행될 수 있다.
- JavaBean처럼 프레임워크 클래스와 어플리케이션 클래스의 강력하고 직관적인 설정. 이 설정 기능은 웹 컨트롤러에서 비즈닌스객체와 밸리데이터에 대한 참조같은 컨텍스트를 건너서 쉽게 참조할 수 있는 기능을 포함한다.
- 적응성(Adaptability), 비침투성, 유연성(flexibility) 해당 시나리오에 필요한 모든 컨트롤러 메서드 시그니처(파라미너 어노테이션(@RequestParam, @RequestHeader, @PathVariable 등등) 중 하나를 사용할 수도 있는)를 정의해라.
- 중복없이 재사용가능한 비즈니스 코드. 특정 프레임워크 기반 클래스를 확장하려고 해당 클래스를 복제하는 대신에 존재하는 비지니스 객체를 커맨드 객체나 폼 객체로 사용해라.
- 커스터마이징할 수 있는 바인딩과 유효성검사(validation). 수동으로 파싱해서 비즈니스 객체로 변환하는 문자열만 사용가능한 폼 객체 대신에 문제가 있는 값을 유지하는 어플리케이션 수준의 유효성 검사 오류인 타입 불일치, 로컬라이징된 날짜나 숫자를 바인딩 등등.
- 커스터마이징할 수 있는 핸들러 매핑과 뷰 처리(view resolution). 핸들러 매핑과 뷰 처리전략은 간단한 URL 기반의 설정부터 정교하고 목적에 맞는 처리 전략까지의 범위를 포함한다. 스프링은 특정 기술에 위임하는 웹 MVC 프레임워크보다 더 유연하다.
- 유연한 모델 전환자(transfer). 이름/값 Map으로 된 모델 전환자는 어떤 뷰 기술과도 쉽게 통합할 수 있도록 지원한다.
- 커스터마이징할 수 있는 로케일과 테마 처리. 스프링 태그라이브러리를 쓰던 안쓰던 JSP를 지원하고 JSTL을 지원하고 별도의 중계 등이 필요없이 Velocity를 지원한다.
- 소위 스프링 태그 라이브러리로 알려진 간단하고 강력한 JSP 태그 라이브러리는 데이터 바인딩과 테마같은 기능을 지원한다. 커스텀 태그는 마크업 코드에서 최대한의 유연성을 제공한다. 태그 라이브러리 디스크립터에 대한 정보는 Appendix F, spring.tld 부록을 참고해라.
- 스프링 2.0에 추가된 JSP 폼태그 라이브러리는 JSP 페이지에서 폼을 훨씬 쉽게 작성할 수 있게 한다. 태그 라이브러리 디스크립터에 대한 정보는 Appendix G, spring-form.tld 부록을 참고해라.
- 빈의 생명주기는 현재의 HTTP 요청이나 HTTP Session의 범위가 된다. 이는 스프링 MVC의 기능이 아닌 스프링 MVC가 사용하는 WebApplicationContext 컨테이너의 독특한 기능이다. 이러한 빈의 범위는 Section 4.5.4, “리퀘스트, 세션, 글로벌 세션 범위”에 나와있다.
16.1.2 다른 MVC 구현체의 플러그인 기능(Pluggability)
어떤 프로젝트에서는 스프링이 아닌 MVC 구현체를 더 선호한다. 많은 팀은 기술과 도구에 대해서 기존에 연구했던 것을 사용할 수 있기를 원한다. Struts 프레임워크에 대한 많은 양의 지식과 경험이 이미 존재한다. Struts의 아키텍처적인 결점을 견딜수 있다면 Struts를 웹 계층으로 선택할 수 있고 동일하게 WebWork나 다른 웹 MVC 프레임워크에도 적용된다.
스프링의 웹 MVC를 사용하지는 않지만 스프링이 제공하는 다른 해결책들은 사용하고자 한다면 선택한 웹 MVC 프레임워크와 스프링을 쉽게 통합할 수 있다. 스프링의 ContextLoaderListener로 스프링 루트 어플리케이션 컨텍스트를 구동하고 Struts나 WebWorc 액션내에서 ServletContext 속성(또는 스프링 각각의 헬퍼 메서드)을 통해 루트 어플리케이션 컨텍스트 에 접근해라. "플러그인"이 없으므로 전용 통합도 필요없다. 웹계층의 뷰에서 진입점인 루트 어플리케이션 컨텍스트 인스턴스로 스프링을 그냥 라이브러리처럼 사용할 수 있다.
등록한 빈과 스프링의 서비스는 스프링 웹 MVC가 없어도 사용할 수 있다. 이러한 시나리오에서 스프링은 Struts나 WebWork와 경쟁하지 않는다. 스프링은 빈 설정부터 데이터 접근과 트랜잭션 처리까지 순수한 웹 MVC 프레임워크가 처리하지 않은 많은 영역을 처리할 뿐이다. 그러므로 단지 JDBC나 하이버네이트로 트랜잭션 추상화를 사용하고자 할때도 스프링 중간계층이나 데이터접근 계층으로 어플리케이션을 풍부하게 할 수 있다.
16.2 DispatcherServlet
많은 웹 MVC 프레임워크처럼 스프링의 웹 MVC 프레임워크는 요청 중심(request-driven)이고 요청을 컨트롤러로 디스패치하는 중앙 서블릿을 중심으로 설계되었고 웹 어플리케이션을 개발에 대한 기능들을 제공한다. 하지만 스프링의 DispatcherServlet은 앞에서 얘기한 것 이상은 하지 않는다. DispatcherServlet는 스프링 IoC 컨테이너와 완전히 통합되어 있어서 스프링이 가진 모든 다른 기능을 사용할 수 있게 한다.
스프링 웹 MVC DispatcherServlet의 요청 처리 흐름은 다음 다이어그램에 설명되어 있다. 패턴에 대해서 작 알고 있는 사람은 DispatcherServlet가 “Front Controller” 디자인 패턴(이는 스프링 웹 MVC와 인기있는 다수의 웹 프레임워크가 사용하는 패턴이다)의 표현이라는 것을 알 수 있을 것이다.
[스프링 웹 MVC에서 요청의 처리 흐름(고수준)]
<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>/example/*</url-pattern> </servlet-mapping> </web-app>
Section 4.14, “ApplicationContext의 추가적인 기능들”에서 설명했듯이 스프링의 ApplicationContext 인스턴스는 범위를 가질 수 있다. 웹 MVC 프레임워크에서 각 DispatcherServlet는 루트 WebApplicationContext에서 이미 정의된 모든 빈을 상속받은 자신만의 WebApplicationContext를 가진다. 이 상속받은 빈들은 서블릿에 특화된 범위를 덮어쓸 수 있고 주어진 서블릿 인스턴의 빈에 새로운 범위의 빈을 정의할 수 있다.
[스프링 웹 MVC의 컨텍스트 계층]
DispatcherServlet을 초기화하면서 스프링 MVC는 웹 어플리케이션의 WEB-INF 디렉토리에서 [servlet-name]-servlet.xml라는 이름의 파일을 검색하고 거기에 정의된 빈을 생성하고 전역 범위에 같은 이름으로 정의된 모든 빈 정의를 덮어쓴다.
다음의 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
>/golfing/*</
url-pattern
>
</
servlet-mapping
>
</
web-app
>
위의 서블릿 설정에서 어플리케이션에 /WEB-INF/golfing-servlet.xml라는 파일이 있어야 할 것이다. 이 파일은 스프링 웹 MVC에 특화된 컴포넌트(빈)을 모두 담고 있을 것이다. 서블릿 초기화 파라미터로 이 설정 파일의 정확한 위치를 변경할 수 있다.(자세한 내용은 아래를 보아라.)
WebApplicationContext는 웹 어플리케이션이 필요로 하는 추가 기능들을 가진 보통의 ApplicationContext의 확장이다. WebApplicationContext는 테마를 처리하는 기능(Section 16.9, “테마(theme) 사용” 참고)과 서블릿이 연결되어 있다는 것을 인지하고 있다는 점(ServletContext에 대한 링크를 가지고 있다)에서 일반적인 ApplicationContext와는 다르다. WebApplicationContext는 ServletContext에 바인딩되어 있다. WebApplicationContext에 접근해야 한다면 RequestContextUtils 클래스의 정적 메서드를 사용해서 항상 WebApplicationContext를 검색할 수 있다.
16.2.1 WebApplicationContext의 전용 빈 타입
스프링 DispatcherServlet는 요청을 처리하고 적절한 뷰를 렌더링하도록 전문화된 빈을 사용한다. 이러한 빈은 스프링 MVC의 일부이다. WebApplicationContext에서 이러한 빈을 하나 이상 설정해서 사용할 전용 빈을 선택할 수 있다. 하지만 아무것도 설정하지 않는다면 스프링 MVC가 기본 빈의 목록을 유지하므로 처음에는 설정하지 말아야 한다. 자세한 내용은 다음 섹션에서 설명한다. 우선은 아래의 표에 나온 DispatcherServlet가 의존하는 전용 빈 타입의 목록을 보자.
Table 16.1. WebApplicationContext의 전용 빈 타입빈(Bean) 타입 설명 HandlerMapping 들어오는 요청을 HandlerMapping 구현체가 다양하게 만든 크리테리아에 기반한 전처리자(pre-processors)와 후처리자(post-processors)(핸들러 인터셉터)의 목록에 매핑한다. 대부분의 인기있는 구현체는 어노테이션이 붙은 컨트롤러를 지원하지만 그렇지 않은 구현체들도 존재한다. HandlerAdapter 핸들러가 실제로 호출되었는지 여부에 상관없이 DispatcherServlet이 요청에 매핑된 핸들러를 호출하도록 돕는다. 예를 들어 어노테이션이 붙은 컨트롤러를 호출하려면 다양한 어노테이션을 처리해야 한다. 그러므로 HandlerAdapter의 주요 목적은 이러한 세부내용에서 DispatcherServlet을 보호하는 것이다. HandlerExceptionResolver 예외를 뷰에 매핑하고 더 복잡한 예외 처리 코드도 사용할 수 있다. ViewResolver 논리적인 스프링 기반의 뷰 이름을 실제 View 타입으로 처리한다. LocaleResolver 국제화된 뷰화면을 제공할 수 있도록 클라이언트가 사용하는 로케일을 처리한다. ThemeResolver 개인화된 레이아웃을 제공하는 것처럼 웹 어플리케이션이 사용할 수 있는 테마를 처리한다. MultipartResolver HTML 폼에서 업로드하는 파일을 처리하는 등의 멀티파트(multi-part) 요청을 파싱한다. FlashMapManager 한 요청에서 다른 요청으로 속성을 전달하는데(보통은 리다이렉트사이에서) 사용할 수 있도록 "입력" FlashMap과 "출력" FlashMap을 저장하고 획득한다.
16.2.2 기본 DispatcherServlet 설정
이전 섹션에서 얘기했듣이 각 전문화된 빈에 대해서 DispatcherServlet는 기본적으로 사용할 구현체의 목록을 유지하고 있다. 이 정보는 org.springframework.web.servlet 패키지의 DispatcherServlet.properties 파일에 존재한다.
모든 전용 빈은 자신만의 합리적인 기본값을 가진다. 하지만 조만간 이러한 빈이 제공하는 프로퍼티들을 커스터마이징해야 할 것이다. 예를 들어 InternalResourceViewResolver 설정의 prefix 프로퍼티에 뷰 파일의 부모위치를 설정하는 것은 아주 일반적이다.
세부내용과는 관계없이 WebApplicationContext에 InternalResourceViewResolver같은 전용 빈을 일단 설정하면 사용할 기본 구현체의 목록을 효과적으로 오버라이드하고 설정하지 않았다면 전용 빈 타입을 사용한다는 것이 여기서 이해야할 중요한 개념이다. 예를 들어 InternalResourceViewResolver를 설정하면 ViewResolver 구현체의 기본 목록은 무시된다.
Section 16.14, “스프링 MVC 설정하기”에서는 MVC 자바설정과 MVC XML 네임스페이스를 포함해서 스프링 MVC를 설정하는 다른 선택사항들을 배울 것이다. 이는 간단한 시적점이 되고 스프링 MVC가 어떻게 동작하는지에 대해 어느 정도 지식이 있다고 가정한다. 어플리케이션을 설정하는데 어떤 방법을 선택했는지에 상관없이 이번 섹션에서 설명한 기본적인 개념은 도움이 될 것이다.
16.2.3 DispatcherServlet 처리 순서
DispatcherServlet을 설정한 뒤 요청이 해당 DispatcherServlet에 들어오면 DispatcherServlet는 요청을 다음과 같이 처리하기 시작한다.
- WebApplicationContext는 요청을 검색해서 처리과정중에 컨트로러와 다른 요소들이 사용할 수 있는 속성처럼 요청을 바인딩한다. 이는 기본적으로 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 키에 바인딩된다.
- 로케일 리졸버는 요청을 처리할 때(뷰를 렌더링하고 데이터를 준비하는 등) 진행중인 요소들이 사용할 로케일을 처리할 수 있도록 요청에 바인딩된다. 로케일 처리가 필요없다면 로케일 리졸버는 필요없다.
- 테마 리졸버는 뷰같은 요소들이 어느 테마를 사용할지 결정하도록 요청에 바인딩된다. 테마를 사용하지 않는다면 테마 리졸버를 무시할 수 있다.
- 멀티파트 파일 리졸버를 지정했다면 요청이 멀티파트인지 검사한다. 멀티파트라면 진행중인 다른 요소들의 추가적인 처리를 위해 요청을 MultipartHttpServletRequest로 감싼다. 멀티파트 처리에 대한 자세한 내용은 Section 16.10, “스프링의 멀티파트(multipart) (파일 업로드) 지원”를 참고해라.
- 적절한 핸들러를 검색한다. 핸들러를 찾으면 모델을 준비하거나 랜더링하기 위해서 핸들러와 연결된 실행체인을 순서대로 실행한다.
- 모델이 반환되면 뷰를 렌더링한다. 모델이 반환되지 않으면(전처리자나 후처리자가 보안적인 이슈등으로 요청을 가로챘기 때문일 것이다.) 요청이 이미 실행되었기 때문에 뷰도 렌더링하지 않는다.
WebApplicationContext에 선언한 핸들러 예외 리졸버가 요청을 처리하는 중에 던져진 예외를 잡는다. 이러한 예외 리졸버를 사용하면 예외를 처리하는 커스텀 동작을 정의할 수 있도록 한다.
스프링 DispatcherServlet도 서블릿 API에 정의된대로 last-modification-date의 반환을 지원한다. 특정 요청에 마지막 수정일시를 결정하는 과정은 직관적이다. DispatcherServlet가 적절한 핸들러 매핑을 검색하고 찾아낸 핸들러가 LastModified 인터페이스를 구현했는지를 검사한다. 만약 구현했다면 LastModified 인터페이스의 long getLastModified(request) 메서드의 값을 클라이언트에 반환한다.
서블릿 초기화 파라미터(init-param 요소)를 web.xml 파일의 서블릿 선언에 추가해서 개별 DispatcherServlet 인스턴스를 커스터마이징할 수 있다. 지원하는 파라미터 목록은 다음 표를 참고해라.
Table 16.2. DispatcherServlet 초기화 파라미터파라미터 설명 contextClass WebApplicationContext를 구현한 클래스로 해당 서블릿이 사용하는 컨텍스트를 인스턴스화한다. 기본적으로 XmlWebApplicationContext를 사용한다. contextConfigLocation 어디서 컨텍스트를 찾을 수 있는지를 나타내려고 컨텍스트 인스턴스에 (contextClass가 지정한) 전달하는 문자열이다. 문자열은 여러 컨텍스트를 지원하려고 여러 문자열(콤마로 구분한다)로 이루어질 수 있다. 여러 컨텍스트를 사용하는 경우에 빈의 위치는 두번 정의되는데 마지막 위치를 사용한다. namespace WebApplicationContext의 네임스페이스. 기본값은 [servlet-name]-servlet이다.
16.3 컨트롤러 구현하기
컨트롤러는 일반적으로 서비스 인터페이스를 통해서 정의하는 어플리케이션 동작에 대한 접근을 제공한다. 컨트롤러는 사용자 입력을 해석해서 뷰로 사용자에게 나타내는 모델로 변환한다. 스프링은 컨트롤러를 아주 다양하게 생성할 수 있도록 아주 추상적인 방법으로 컨트롤러를 구현한다.
스프링 2.5에서 @RequestMapping, @RequestParam, @ModelAttribute 등과 같은 어노테이션을 사용해서 MVC 컨트롤러에 어노테이션 기반의 프로그래밍 모델을 도입했다. 이 어노테이션 지원은 서블릿 MVC와 포틀릿 MVC 모두에서 사용할 수 있다. 이 방식으로 구현한 컨트롤러는 특정 기반 클래스를 확장하거나 특정 인터페이스를 구현하지 말아야 한다. 게다가 이러한 컨트롤러에 서블릿이나 포틀릿 기능에 대한 접근을 쉽게 설정할 수 있더라도 보통 서블릿이나 포틀릿 API에 직접적인 의존성을 가지지 않아야 한다.
Tip
예제 저장소에서 사용할 수 있는 MvcShowcase, MvcAjax, MvcBasic, PetClinic, PetCare 등 다수의 웹 어플리케이션은 이번 섹션에서 설명한 어노테이션 지원을 사용한다.
@Controller
public
class
HelloWorldController
{
@RequestMapping
(
"/helloWorld"
)
public
String helloWorld(Model model)
{
model.addAttribute(
"message"
,
"Hello World!"
);
return
"helloWorld"
;
}
}
여기서 보듯이 @Controller와 @RequestMapping 어노테이션으로 메서드 이름과 시그니처를 유연하게 할 수 있다. 이 특정 예제에서 메서드는 Model을 받고 뷰 이름으로 String을 반환하지만 이번 섹션의 뒷부분에서 설명하듯이 여러가지 다른 메서드 파라미터와 반환값을 사용할 수 있다. @Controller와 @RequestMapping을 비롯한 다수의 다른 어노테이션들은 스프링 MVC 구현체의 기초를 형성한다. 이번 섹션에서는 이러한 어노테이션을 설명하고 서블릿 환경에서 이러한 어노테이션들을 사용하는 가장 일반적인 방법을 설명한다.
16.3.1 @Controller로 컨트롤러 정의하기
@Controller 어노테이션은 해당 클래스가 컨트롤러의 역할을 한다는 것을 나타낸다. 스프링에서는 어떤 컨트롤러 기반 클래스도 확장할 필요가 없고 서블릿 API를 참조할 필요도 없다. 하지만 필요하다면 서블릿에 특화된 기능을 참조할 수 있다.
@Controller 어노테이션은 어노테이션이 붙은 클래스의 스테레오 타입처럼 동작하고 클래스의 역할을 나타낸다. 디스패처는 매핑된 메서드에 이러한 어노테이션이 붙은 클래스를 찾고 @RequestMapping 어노테이션(다음 섹션 참조)을 탐지한다.
디스패처의 컨텍스트에 표준 스프링 빈 정의를 사용해서 어노테이션이 붙은 컨트롤러 빈을 명시적으로 정의할 수 있다. 하지만 클래스패스에서 컴포넌트 클래스를 탐지와 컴포넌트 클래스들을 위해 빈 정의의 자동등록에 대한 스프링의 일반적인 지원에 맞추어 @Controller 스테레오타입도 자동탐지가 가능하다.
이렇게 어노테이션이 붙은 컨트롤러의 자동탐지를 활성화하려면 설정에 컴포넌트 스캔을 추가해야 한다. 다음 XML 코드처럼 spring-context 스키마를 사용해라.
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
xsi:schemaLocation="
<
context:component-scan
base-package
=
"org.springframework.samples.petclinic.web"
/>
<!-- ... -->
</
beans
>
16.3.2 @RequestMapping으로 요청 매핑하기
/appointments같은 URL을 매핑하려면 전체 클래스나 특정 핸들러 메서드에 @RequestMapping 어노테이션을 사용해라. 보통 클래스수준의 어노테이션은 폼(form) 컨트롤러에 특정 요청 경로(또는 경로 패턴)을 매핑하고 추가적인 메서드 수준의 어노테이션은 특정 HTTP 요청 메서드("GET", "POST" 등)나 HTTP 요청 파라미터 상태로 매핑 범위를 좁힌다.
Petcare 예제에서 가져온 다음 코드는 이 어노테이션을 사용하는 스프링 MVC 어플리케이션의 컨트롤러를 보여준다.
@Controller
@RequestMapping
(
"/appointments"
)
public
class
AppointmentsController
{
private
final
AppointmentBook appointmentBook;
@Autowired
public
AppointmentsController(AppointmentBook appointmentBook)
{
this
.appointmentBook = appointmentBook;
}
@RequestMapping
(method = RequestMethod.GET)
public
Map<String, Appointment> get()
{
return
appointmentBook.getAppointmentsForToday();
}
@RequestMapping
(value=
"/{day}"
, method = RequestMethod.GET)
public
Map<String, Appointment> getForDay(
@PathVariable
@DateTimeFormat
(iso=ISO.DATE) Date day, Model model)
return
appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping
(value=
"/new"
, method = RequestMethod.GET)
public
AppointmentForm getNewForm()
{
return
new
AppointmentForm();
}
@RequestMapping
(method = RequestMethod.POST)
public
String add(
@Valid
AppointmentForm appointment, BindingResult result)
{
if
(result.hasErrors())
{
return
"appointments/new"
;
}
appointmentBook.addAppointment(appointment);
return
"redirect:/appointments"
;
}
}
이 예제에서 @RequestMapping를 여러 곳에서 사용했다. 첫번째는 타입(클래스) 수준에서 사용했고 이 컨트롤러의 모든 핸들링 메서드는 /appointments 경로에 상대적이라는 것을 나타낸다. get() 메서드의 @RequestMapping는 좀 더 세련되었다. 이 @RequestMapping은 GET 요청만 받아들인다. 즉, /appointments에 대한 HTTP GET 요청만 이 메서드를 실행한다. post()도 유사하다. getNewForm()은 HTTP 메서드와 경로를 함께 사용했으므로 appointments/new에 대한 GET 요청을 이 메서드가 처리한다.
getForDay() 메서드는 @RequestMapping의 또다른 사용방법인 URI 템플릿 보여준다. (다음 섹션 참고.)
클래스 수준의 @RequestMapping는 필수가 아니다. 클래스에 @RequestMapping를 사용하지 않으면 모든 경로는 상대경로가 아니라 절대경로가 된다. PetClinic 예제 어플리케이션에서 가져온 다음 예제는 @RequestMapping를 사용한 다중 액션 컨트롤러를 보여준다.