Sample Project인 helloworld 프로젝트를 이루는 설정 파일과 소스 파일에 대한 설명을 하려고 한다. 애플리케이션에는 기본적으로 아래와 같은 설정파일이나 소스파일이 존재한다.
스프링 웹 MVC 애플리케이션의 요청 처리 로직은 컨트롤러 클래스에 들어 있다.
요청처리를 담당하는 스프링 웹 MVC 컨트롤러이다.
위의 예제코드에서 implements Controller 를 보면 HelloWorldController 클래스가 스프링 Controller 인터페이스를 구현하는 것을 볼 수 있다.
Controller 인터페이스에는 handle Request 메서드 정의가 들어 있다.
이 메서드 안에 요청 처리 로직을 구현해야한다. handleRequest 메서드는 ModelAndView 객체를 반환하며 객체에는 아래의 정보가 들어간다.
보통 java.util.Map 타입 객체로 모델 데이터를 표현한다. java.util.Map 객체의 각 원소는 모델 속성을 표현한다. 사용자에게 보여줄 JSP 페이지인 뷰의 이름은 String으로 저장하게 된다.
HelloWorldController의 handleRequest 메서드가 helloworld(String 값) 뷰와 modelData(java.util.Map 타입 객체)가 들어 있는 ModelAndView 객체를 반환하는 모습을 보여준다.
modelData에는 'Hello World !!' 메시지가 값인 msg 모델 속성이 들어있다.
위의 코드는 helloworld.jsp 코드이다. 코드를 살펴보면 helloworld 뷰(jsp 페이지)가 'Hello World !!' 메시지를 사용자에게 보여주기 위해 msg 모델 속성을 사용하는 것을 볼 수 있다.
HelloWorldController의 handleRequest 메서드가 JSP 페이지를 랜더링(표시)하는 과정은 아래의 이미지와 같다.
스프링 웹 MVC 프레임워크는 들어오는 HTTP 요청을 가로채 HelloWorldController의 handleRequest 메서드를 호출한다. handleRequest 메서드는 모델데이터와 뷰 정보가 들어 있는 ModelAndView 객체를 handleRequest 메서드에서 받으면 helloworld.jsp 페이지로 HTTP 요청을 보내면서(dispatch) helloworld.jsp 페이지가 요청 속성으로 모델 속성을 사용할 수 있게 한다.
정리: 스프링 웹 MVC 프레임워크는 HelloWorldController의 handleRequest 메서드를 호출하고 메서드가 반환하는 ModelAndView 객체를 사용해 helloworld.jsp 페이지를 표시한다.
helloworld 샘플 프로젝트에 설정된 빈 정의를 보여주는 config 파일이다.
myapp-config.xml 파일에서 HelloWorldController와 별도로 스프링 SimpleUrlHandlerMapping과 InternalResourceViewResolver 빈을 설정하는 것을 볼 수 있다.
SimpleUrlHandlerMapping
빈은 들어오는 HTTP 요청을 처리할 책임이 있는 컨트롤러에 전달하고 URL 경로를 사용해 요청을 컨트롤러에 매핑한다.
여기서 url property는 URL과 컨트롤러 빈 사이의 매핑을 설정한다.
key 속성으로 설정한 URL 경로인 /sayhello
를 HellooWorldController 빈으로 매핑하는 것이다.
InternalResourceViewResolver
빈은 ModelAndView에 들어 있는 뷰 이름으로 실제 뷰(JSP나 서블릿 등)의 위치를 찾는다.
실제 뷰 위치는 prefix 프로퍼티 값을 앞에 붙이고, suffix 프로퍼티 값을 뒤에 붙이는 방식으로 정해진다.
정리: SimpleUrlHandlerMapping은 호출한 컨트롤러를 찾고, InternalResourceViewResolver는 뷰 이름으로 실제 뷰를 찾는다.
Spring Web MVC는 SimpleUrlHandlerMapping과 InternalResourceViewResolver 빈을 자동으로 감지해서 요청을 처리하는 컨트롤러와 뷰를 찾는데 사용한다.
스프링 웹 MVC기반 애플리케이션에서는 요청을 DispatcherServlet(스프링 웹 MVC가 제공하는 서블릿)이 가로챈다.
DispatcherServlet은 요청을 적절한 컨트롤러에 전달하는 역할을 한다.
DispatcherServlet은 contextConfigLocation 서블릿 초기화 파라미터가 지정하는 웹 애플리케이션 컨텍스트 XML 파일과 관련이 있다. 코드를 보면 contextConfigLocation 초기화 파라미터는 myapp-config.xml 파일을 가르킨다.
DispatcherServlet은 웹 애플리케이션 컨텍스트 XML에 정의된 HandlerMapping과 ViewResolver 빈을 요청 처리에 사용한다.
DisplatcherServlet이 HandlerMapping 구현을 사용해서 요청에 맞는 적절한 컨트롤러를 찾고, ViewResolver 구현을 사용해 컨트롤러가 반환하는 뷰 이름을 가지고 실제 뷰를 찾는 것이다.
- 요청을 DispatcherServlet이 가로챈다.
- DispatcherServlet은 HandlerMapping 빈을 사용해서 요청을 처리하기 적합한 컨트롤러를 찾는다. (예시로 사용된 샘플의 helloworld 프로젝트는 SimpleUrlHandlerMapping을 사용해서 찾는다.)
- DispatcherServlet은 컨트롤러가 반환하는 뷰 이름을 ViewResolver 빈에게 전달해서 표시할 실제 뷰(JSP나 서블릿)를 찾는다.
- DispatcherServlet은 실제 뷰에 요청을 전달하고 컨트롤러가 반환한 모델 데이터를 뷰에서 요청 속성으로 사용할 수 있게 한다.
앞에서 DispatcherServlet이 웹 애플리케이션 컨텍스트 XML 파일에 정의된 HandlerMapping과 ViewResolver 빈과 상호작용해서 요청을 처리하는 것을 알아보았다.
DispatcherServlet은 내부적으로 어떻게 작동할까?
초기화시 DispatcherServlet은 자신에게 대응하는 웹 애플리케이션 컨텍스트 XML 파일을 로드하고(별도 지정이 되지 않는 상황에서는 WEB-INF 디렉토리에 <name-of-DispatcherServlet>-servlet.xml
파일을 지정한다.) SpringWebApplicationContext 객체 인스턴스를 만들게 된다.
SpringWebApplicaiontContext
SpringWebApplicaiontContext는 ApplicationContext 인터페이스의 하위 인터페이스로, 웹 애플리케이션에 특화된 기능을 제공한다.
아래는 WebApplicationContext XML 파일에서 빈에 지정할 수 있게 추가된 스코프이다.
request
session
application
websocket
이들은 모두 스프링 컨테이너가 HTTP 요청/HTTP Session 생성/ServletContext 생성/WebSocket 생성에 새로운 빈 인스턴스를 생성하고 파괴될 때 스프링 컨테이너는 이 빈 인스턴스를 파괴한다.
아래의 그림은 DispatcherServlet에 해당하는 웹 애플리케이션 컨텍스트 XML 파일에 정의된 빈과 루트 웹 애플리케이션 컨텍스트 XML 파일에 정의된 빈 사이의 관계이다.
루트 WebApplicationContext에 정의된 빈을 DispatcherServlet에 해당하는 WebApplicationContext 인스턴스에서 상속한다.
이 그림에서 servlet1, servlet2, servlet3은 web.xml 파일에 설정된 DispatcherServlet 인스턴의 이름인데, DispatcherServlet 인스턴스를 초기활 때 servlet1-servlet.xml ... 2,3 파일에 대응하는 WebApplicationContext가 생성되고 DispatcherServlet 인스턴스와 연관지어진다.
필자는 이번 스터디를 하면서 해당 분야를 이해하는데 어려웠다.
지금까지 알아본 내용은 Controller 인터페이스를 구현하면 컨트롤러를 만들수 있다! 인데, 이렇게 한 단계씩 보고나니 설정 부분들이 복잡하고 번거롭다고 느껴지게 되었다. 하지만, Controller 클래스를 개발할 때 이러한 방법을 사용하지 않고 필자는 @Controller를 사용해서 개발해왔다. @Controller와 @RequestMapping을 사용하게 되면 Controller를 더욱 쉽게 개발할 수 있다는 것을 느끼게 되었다.