본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] MVC 프로젝트 구조 파악하기

by 김코더 김주역 2020. 12. 31.
반응형

* 이 블로그에서 설명하는 Spring 프로젝트의 서버 환경은 Tomcat이다.

 

Spring을 공부하기 위해 여러 사전 지식들을 익혔는데 이제 드디어 본격적으로 Spring 프로젝트를 다뤄볼 시간이다.

이번 포스팅에서는 MVC 프로젝트를 생성해보고 구조가 어떻게 생겼는지, 어떻게 동작하는지 분석해보기로 한다.

 

1. MVC 프로젝트 생성

프로젝트 생성 창에서 Spring Legacy Project를 선택한다.

만약 이 프로젝트가 뜨지 않을 경우 아래 포스팅을 참고하면 된다.

kimcoder.tistory.com/233

 

STS에 Spring Legacy Project가 없을 경우??

Spring 프로젝트를 이클립스 또는 플러그인 환경에서 생성하려고 할 때, Spring Legacy Project가 표시되지 않는 경우가 있다. 본인도 처음에 Starter Project들만 있어서 뭔가 이상하다 싶었는데 정말 이상

kimcoder.tistory.com

 

 

그리고 Spring MVC Project를 선택한다.

 

그리고 top-level package명을 지정해야 한다.

쉽게 말하면 최상위 패키지 이름을 정하라는 뜻이다.

여러분들이 친숙한 이름으로 정하되, 아래 설명되어 있는 양식을 지키는 것을 권장한다.

 

Finish를 누르고 기다리면 1분 내로 프로젝트가 빌드된다.

 

 

 

2. MVC 프로젝트 구조

구조를 확인하기 앞서 막 생성된 프로젝트를 한번 실행해보자.

 

Hello world! 문구와 함께 실행 시각이 표시되는데,

이런 결과가 출력되도록 샘플 코드가 이미 작성되어 있었던 것 같다.

 

이제 구조를 살펴봐서 어떻게 이런 결과가 뜨게 됐는지 파악해보자.

Project Explorer 창을 보면 프로젝트의 구조를 확인할 수 있다.

여기서 빨간 상자로 표시한 파일들의 기능은 이렇다.

  • HomeController.java : Dispatcher에서 전달된 요청을 처리하는 곳
  • resource 폴더 : 이미지같은 리소스들을 저장하는 폴더 
  • servlet-context.xml : 스프링 컨테이너 설정 파일
  • home.jsp : 화면에 표시될 View 부분이다. 처음에는 html 코드를 기반으로 작성되어있다.
  • web.xml : DispatcherServlet 맵핑을 해주며, 스프링 설정 파일의 위치를 정의해주는 곳이다.

※ DispatcherServlet : 클라이언트로부터 최초로 요청을 받는 서블릿, 이 곳을 기점으로 작업을 분배한다.

※ WEB-INF에 있는 내용들은 클라이언트가 직접 접근할 수 없고, 내부적으로만 접근할 수 있다.

 

 

 

3. 프로젝트 동작

어떤 파일부터 설명할지 고민해보았는데 역시 과정 순으로 설명하는 것이 베스트인 것 같다.

 

1) client의 요청

아까, 생성한 프로젝트를 바로 실행해봤을 때로 되돌아가보면 주소창의 port 넘버 옆에 /demo가 붙어 있는 것을 볼 수 있었다.

이 /demo는 Context path라고 하고, tomcat 폴더의 server.xml 파일에서 확인할 수 있다.

기본적으로 Context path는 아까 지정했던 최상위 패키지명의 끝 이름으로 지정되며, 반드시 Context path를 붙여야 클라이언트를 접속시킬 수 있다.

 

<server.xml>

 

 

2) DispatcherServlet 맵핑, 스프링 설정 파일 위치 정의

<web.xml>

그 다음은 <servlet-mapping>로 이동한다.

Context path 뒤의 '/' 로 시작하는 요청(사실상 모든 요청)을 받아서 "appServlet" 이라는 이름의 <servlet>으로 매핑하여 DispatcherServlet에서 처리하게 한다. 이 때, <servlet>과 <servlet-mapping>의 <servlet-name>을 같게 해놓아야 매핑된다. 이 <servlet> 에서 스프링 설정 파일의 위치는 /WEB-INF/spring/appServlet/servlet-context.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">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<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>/</url-pattern>
	</servlet-mapping>

</web-app>

※ ContextLoaderListener은 root context를 생성해주는 역할을 한다.

※ <load-on-startup>은 서블릿을 만들고 초기화 할 순서를 의미하며, 작은 수를 가진 서블릿이 우선적으로 만들어진다.

 

 

3) Controller 찾기

<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.example.demo" />
</beans:beans>

- <context:component-scan>의 base-package는 bean을 스캔할 패키지다. 이 태그를 추가하면 XML 설정 방식과 Annotation 설정 방식을 혼용할 수 있다. @Controller 어노테이션은 @Component 어노테이션을 포함하므로 @Controller 어노테이션이 붙은 클래스도 bean 등록 대상이다. 즉, Controller로 지정하고 싶은 클래스에 @Controller 어노테이션을 작성해주면 된다.

Controller 클래스에 해당하는 HomeController.java는 바로 뒤에 설명할 것이다.

 

- <beans:bean> 에서 org.springframework.web.servlet.view.InternalResourceViewResolver 클래스를 bean으로 설정하고 client에게 보여줄 View에 해당하는 파일 경로를 지정해주면 되는데, <beans:property>의 prefix, suffix는 경로의 각각 앞, 뒤에 붙을 문자열이다.

중간 경로는 Controller 클래스의 client 요청에 해당하는 메소드의 반환값이다.

 

- 마지막으로 <resources> 를 설명하고 넘어가야 할 것 같다. 프로젝트의 resources 폴더는 이미지, css, js 같은 정적 리소스들을 저장하는 곳이다. "/resource/**" 패턴의 요청(/resource 및 /resource의 하위 경로)이 들어왔다면 "/resources/" 경로에서 리소스를 찾을 수 있게 매핑해준다.

그런데, 아래와 같이 web.xml에서 DispatcherServlet의 매핑은 "/"로 되어 있기 때문에 정적 리소스를 인식하지 못한다.

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

즉, 프로젝트에서 정적 리소스를 포함한다면 <url-pattern>을 /가 아닌 다른 패턴으로 지정하거나, /로 지정했더라도 정적 리소스를 인식하도록 하는 특별한 설정이 필요하다. bean xml 설정 파일에 <mvc:default-servlet-handler/>를 추가하면 <url-pattern>을 /로 지정했더라도 정적 리소스가 성공적으로 인식된다.

 

 

4) Controller 작성

<HomeController.java>

Controller 클래스임을 명시하기 위해 @Controller 어노테이션이 작성되었고, 요청을 처리하는 메소드임을 명시하기 위해 @RequestMapping 어노테이션이 파라미터와 함께 작성된 모습이다. @RequestMapping의 value 속성에는 요청을 처리할 URL 형식을 DispatcherServlet이 매핑된 디렉토리를 기준으로 지정하면 된다.

요청 방식(GET/POST)를 따로 지정해주고 싶을 때는 method 속성을 넣어주면 되는데, form의 method 속성과 일치하지 않다면 405에러가 뜨게 된다.

그리고 이 메소드의 return 값인 "home" 이 위에서 설명했던 view의 중간 경로이다.

package com.example.demo;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	/**
	 * Simply selects the home view to render by returning its name.
	 */
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		
		return "home";
	}
	
}

 

이렇게 메소드를 리턴해주면 servlet-context.xml의 bean에서

prefix인 "/WEB-INF/views/"와 중간 경로인 "home"과, suffix인 ".jsp" 가 합쳐져서

/WEB-INF/views/home.jsp 라는 view 파일 경로로 이동하여 client에게 응답해주는 것이다.

 

 

5) View

<home.jsp>

${serverTime} 은 HomeController.java 에서 model 객체에 추가한 속성의 속성값을 받아오는 부분이다.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
<h1>
	Hello world!  
</h1>

<P>  The time on the server is ${serverTime}. </P>
</body>
</html>

 

 

최종적으로 이 jsp파일이 웹상에서 client에게 보여지게 된다.

반응형

댓글