2019-11-19 작성

Spring MVC 프로젝트의 기본 구조와 실행 순서

Spring MVC 프로젝트의 기본 구조

Spring의 전체적인 실행 순서

요청
-> DispatcherServlet -> HandlerMapping
-> 요청 처리하기 (Controller <-> Service <-> DAO <-> DB)
-> DispatcherServlet -> ViewResolver -> View
-> DispatcherServlet -> 응답

예시 1. 일반적인 기본 동작 순서

예시 2. 위 예시에서 Controller 뒷부분의 과정을 생략함

단계별 스프링 실행 순서

1. 클라이언트가 요청을 보낸다.

예를 들어, 사용자가 블로그 포스팅을 읽기 위해 http://localhost:8080/post/view 라는 블로그 글 조회 요청을 보냈다고 가정해보자.

2. 클라이언트의 요청을 DispatcherServlet이 가로챈다.

DispatcherServlet이란 무엇일까? 'dispatch' 의미는 '보내다'라는 뜻을 가지고 있다. 즉, 가장 앞단에서 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아서 적합한 컨트롤러에 위임해주는 프론트 컨트롤러(Front Controller)인 셈이다. 그렇다고 모든 요청을 가로채는 건 아니고 web.xml에 등록된 내용만 가로챈다. 아래 소스는 초기 web.xml 파일의 일부이다.

<servlet-mapping>
	<servlet-name>appServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

초기 web.xml에서는 <url-pattern>이 '/'으로 해당 애플리케이션의 모든 URL로 등록되어 있어서 모든 요청을 가로챈다. 만약 *.do와 같이 특정 URL 형식을 적용하고 싶다면 아래와 같이 내용을 바꿔주면 된다. 그렇게 되면 http://localhost:8080/post/view.do 의 형식으로 보내야 할 것이다.

<servlet-mapping>
	<servlet-name>appServlet</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>

3. 가로챈 요청을 HandlerMapping에게 보내 해당 요청을 처리할 Controller를 찾는다.

HandlerMapping은 실제 클라이언트의 조회 요청을 수행할 Handler를 찾아주는 역할을 한다. 아래 소스처럼 클래스에 @Controller 어노테이션을 붙이면 servlet-context.xml에서는 이를 인식하여 컨트롤러로 등록한다. 그리고 컨트롤러에서 @RequestMapping 어노테이션을 사용하여 /post/view 값을 설정한다면 해당 컨트롤러의 해당 메서드를 인식하고 찾아가게 된다.

(스프링 4.3부터는 @RequestMapping 문장을 좀 더 간추려서 @GetMapping("/post/view") 로 선언할 수도 있다)

@Controller
class HomeController {

    @RequestMapping(value = "/post/view")
    public String view() {
        return "";
    }
}

4. 실제 요청 처리

이제 컨트롤러에서 비즈니스 로직을 거쳐 DB에서 포스팅 글 조회 기능을 수행한다. 즉, Controller -> Service -> DAO -> DB -> DAO -> Service -> Controller 의 순서로 진행이 된다. 컨트롤러에서 출발하여 DB에서 값을 가져온 후 다시 컨트롤러로 돌아오는 구조인 셈이다.

아래 2개의 변수는 DB에서 값을 가져왔다고 가정하였다.

@Controller
public class HomeController {

	@RequestMapping(value = "/post/view")
	public String view() {
		
		// DB에서 포스팅 정보를 가져왔다는 가정한다.
		String title = "DB에서 조회한 포스팅 제목";
		String content = "DB에서 조회한 포스팅 내용";
		
		return "home";
	}
}

5. ViewResolver를 통해 view 화면을 찾는다.

이제 DB에서 조회한 title, content를 실제 화면으로 보여줘야 한다. 아래 소스는 Model 객체를 이용하여 addAttribute 함수를 이용하여 화면에 보여줄 값을 세팅하고 있다. return 값은 "home"이라고 명시되어 있는데 이는 home.jsp 파일로 보여주겠다는 의미이다. 이 문자열은 나중에 servlet-context.xml에 설정된 prefix와 suffix 정보를 참조하여 /WEB-INF/views/home.jsp 파일을 찾는 정보를 제공한다.

최종적으로 컨트롤러는 결과를 출력할 뷰와 뷰에 전달할 객체를 담고 있는 Model 객체를 리턴한다. ViewResolver는 컨트롤러에서 보내온 view이름(여기서는 home)을 토대로 view 화면을 찾게 된다. 

@Controller
public class HomeController {

	@RequestMapping(value = "/post/view")
	public String view() {
		
		// DB에서 포스팅 정보를 가져왔다는 가정한다.
		String title = "DB에서 조회한 포스팅 제목";
		String content = "DB에서 조회한 포스팅 내용";
        
		model.addAttribute("title", title);
		model.addAttribute("content", content);
		
		return "home";
	}
}

6. 찾은 view 화면을 View에 보내면 이 결과를 다시 DispatcherServlet에 보내고, DispatcherServlet는 클라이언트에게 결과(포스팅 조회)를 전송한다.

아래는 view 화면 (home.jsp) 의 모습이다. $를 이용하여 컨트롤러에서 Model 객체를 이용하여 설정한 파라미터 값들을 설정하면 된다. 

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
	<body>
		<h1>${title}</h1>

		<P>${content}</P>
	</body>
</html>

여기까지 진행이 되었다면, 사용자가 http://localhost:8080/post/view.do 로 조회 요청을 하였을 때, DB에서 가져온 결과값이 출력이 될 것이다.

JSP 파일은 Model 객체를 넘겨받고 그 Model 객체 안의 속성 값들의 정보를 ${} 기호에 표현된 부분에 치환한다. 예로 들어 ${title}은 Model에서 addAttribute 메서드를 통해 추가했던 title 변수의 정보를 담고 있다.

이제 대표적인 설정파일에 대해서 알아보려고 한다. 

web.xml 설정 파일

web.xml은 WAS가 최초 구동될 때 WEB-INF 디렉토리에 존재하는 web.xml을 읽고, 그에 해당하는 웹 애플리케이션 설정을 구성한다. 한마디로 각종 설정을 위한 설정파일이다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://Java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- 첫번째 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- 두번째 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- 세번째 -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- 네번째 -->
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

</web-app>

각 태그를 세부적으로 살펴보면 크게 4개 태그로 분류할 수 있다.

  • <context-param> : 모든 서블릿 및 필터가 공유하는 루프 스프링 컨테이너 정의
  • <listener> : 모든 서블릿 및 필터가 공유하는 스프링 컨테이너를 작성한다.
  • <servlet> : DispatcherServlet을 구현하기 위해 어떤 클래스를 이용해야 할지와 초기 파라미터 정보를 포함한다
    • <servlet-name> : 서블렛명을 지정하면 해당 이름을 가지고 다른 설정 파일에서 해당 서블릿 정보를 참조한다
    • <servlet-class> : 어떤 클래스를 가지고 DispatcherServlet을 구현할 것인지를 명시하고 있다.
    • <init-param> : 초기화 파라미터에 대한 정보. servlet에 대한 설정 정보가 여기에 들어간다. 만약 초기화 파라미터에 대한 정보를 기술하지 않을 경우 스프링이 자동적으로 스프링 컨테이너를 생성한다. 
    • <load-on-startup> : 서블릿이 로딩될 때 로딩 순서를 결정하는 값. 톰캣이 구동되고 서블릿이 로딩되기 전 해당 서블릿에 요청이 들어오면 서블릿이 구동되기 전까지 기다려야 한다. 이 중 우선순위가 높은 서블릿부터 구동할 때 쓰이는 값이다.
  • <servlet-mapping> : servlet에서 지정한 패턴으로 클라이언트 요청이 들어오면 해당 을 가진 servlet에게 이 요청을 토스하는 정보를 기술한다. 

root-context.xml 설정 파일

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- Root Context: defines shared resources visible to all other web components -->
		
</beans>

이 파일은 최초에는 텅 비어있다. 다른 모든 웹 구성요소에 표시되는 공유 리소스를 정의하는 곳인데, 이곳에서는 view와 관련되지 않은 객체를 정의한다. 예를 들어 Service, Repository(DAO), DB등 비즈니스 로직과 관련된 설정을 해주는 곳이다.

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<context:component-scan base-package="com.company.devpad" />
	
</beans:beans>

다음은 servlet-conext.xml 파일이다. 여기서 주목해야 하는 부분은 prefix와 suffix 부분이다. 서블릿 설정으로 prefix(접두사)와 suffix(접미사)를 붙여주는 역할을 담당한다. 즉, 우리가 일일이 전체경로와 .jsp를 붙이지 않아도 되도록 도와준다.

그다음으로 <context:component-scan base-package="com.company.devpad" /> 부분을 보자. 이 부분은 스프링에서 사용하는 bean을 일일이 xml에 선언하지 않고도 필요한 것을 어노테이션(Annotation)을 자동으로 인식하게 하는 역할을 한다.

각 태그를 세부적으로 살펴보자.

  • <annotation-driven> : @Controller 어노테이션을 감지하여 해당 클래스를 Controller로 등록할 수 있도록 해주는 태그 
  • <resources> : 정적인 html문서 같은 웹 리소스들의 정보를 기술하는 태그 
  • <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> : Controller가 Model를 리턴하고 DispatcherServlet이 jsp 파일을 찾을 때 쓰이는 정보를 기술하는 태그. "home"이라는 문자열을 반환하면 /WEB-INF/views/ 경로에서 접미사가 .jsp인 해당 파일을 찾는다. /WEB-INF/views/home.jsp 
  • <context:component-scan> : Java 파일의 @Component로 등록된 Bean 객체를 찾도록 해주는 태그 

References