이번 포스팅에서는 앞선 포스팅에서 생성한 프로젝트(New
> Spring Legacy Project
> Spring MVC Project
)를 바탕으로 프로젝트의 구조를 설명하고, 프로젝트가 로드되고 요청이 처리되는 과정에 대해 설명합니다.
참고로 저는 Blog
라는 프로젝트를 생성했고, 게시판 목록을 보여주는 것까지 시리즈에 포스팅 할 예정입니다.
프로젝트 생성: [New
> Spring Legacy Project
> Spring MVC Project
]
아래는 생성된 프로젝트의 초기 구조입니다.
Spring Boot 부터는 공식적으로 Gradle
과 Maven
을 지원하지만, 이전에는 기존 구조로 Maven
을 채택했기 때문에 Maven 프로젝트
로 생성되는 것을 볼수 있습니다.
(아주 예전에는 Ant
라는 빌드 툴을 많이 사용했다고 합니다. 시대 흐름: Ant
⇒ Maven
⇒ Gradle
)
스프링 프로젝트의 디렉터리 구조와 설명은 아래와 같습니다.
📁 Project명
src/main/java
: Java 소스 디렉터리
로 생성할 수 있는 여러 자바파일이 위치한 디렉터리입니다.
└ @Controller
, @Service
, @Component
, ... etc
src/main/resources
: 각종 환경설정 파일이 위치한 디렉터리입니다. 해당 디렉터리는 classpath로 지정되어있어, classpath:/
로 접근할 수 있습니다. mapper 파일
, logging 설정파일
, 시스템 설정파일(.properties)
등이 있습니다.
/src/test/java
, /src/test/resources
: JUnit
과 같은 테스트 라이브러리로 테스트할 때 사용하는 디렉터리입니다.
/src/main/webapp
└ resources
: 정적 리소스 디렉터리. js
, css
, image
등의 정적 파일을 넣어두는 디렉터리입니다.
└ classes
: 웹 어플리케이션에서 사용하는 클래스 파일이 위치한 디렉터리입니다. war 파일 형태로 배포하게 되면 src/main/java
소스가 컴파일된 .class 파일
, src/main/resources
내의 파일이 저장됩니다.
└ WEB-INF
: Web Information
의 약자로 웹에 관한 중요 정보를 넣는 디렉터리.
└ web.xml
: 웹 애플리케이션 설정 파일
target
디렉터리: 빌드 출력 결과 (.war)
pom.xml
: Maven 빌드 관리자
web.xml
web.xml
은 웹 애플리케이션(Web Application) - webapp을 개발하기 위해 반드시 필요한 파일입니다.배포 설명자(DD, Deployment Descriptor)로 불리며, 웹애플리케이션 설정 파일입니다.
Servlet Container
구동시 로드하는 파일 입니다. 즉, Servlet 요청을 처리와 관련된 내용이 들어갑니다.예를 들어
Servlet
,Listner
,Filter
,Error Page
,Session
설정 등이 포함될 수 있습니다.
프로젝트 세팅에 대해
대학에서 부터 학원까지 들었던 공통적인 말이 있습니다. "Spring은 초기세팅이 어렵다."
햇수로 4년차인 지금도 아직 모르는게 많지만, 프로그램에는 언제나 정답이 없으니 처음부터 엄청난 의미있고 구조적인 세팅을 할 수도 없고, 할 필요도 없다는 생각이 드네요
프로젝트 세팅을 어려워할 필요는 없다고 생각합니다. (일단 돌아가게라도 만들어라!!)
프로젝트 세팅은 단순히 그냥 프로젝트의 환경을 구성하는 것, 즉 프로젝트 커스터마이징입니다. 이는 초기에 국한되지 않고, 개발 중에도 변경될 수 있습니다.
세팅에 앞서 유의 깊게 봐야하는 두가지 파일이 있습니다.
pom.xml
: 프로젝트 빌드 관리자. 패키지 관리, 의존성(라이브러리) 관리web.xml
: 배포 설명자. Servlet 요청처리 관리, Spring 설정파일 관리2절에서는 pom.xml에 관련된 Maven과 pom.xml에 대해 설명하고
3절에서는 web.xml에 관련된 웹 애플리케이션 동작과정과 Spring 설정파일에 대해 설명할 예정입니다.
빌드
에 대해 들어본적이 있나요?
신입 시절 가장 어려웠던 개념 중에 하나가 바로 빌드ㆍ배포
인데요. 그 이유는 바로, 프로젝트 경험을 위해 개발만 해놓고 애플리케이션을 실제로 설치(이하 배포)해줄 일이 없었기 때문입니다.
애플리케이션을 사용자들이 사용하기 위해 애플리케이션을 배포해주기 위해서는 배포 파일
이 필요하고, 빌드
는 우리가 작성한 소스 코드 파일을 컴퓨터에서 독립적으로 실행할 수 있는 독립 소프트웨어 가공물(배포 파일)로 변환하는 과정을 의미합니다.
다시 말해, 소스코드(java), 파일 및 자원등(xml, jsp, js, jar, properties, etc)을 JVM이나 톰캣같은 WAS가 인식할 수 있는 구조로 패키징하는 과정 및 결과물(.jar, .war, 등) 이라고 할 수 있습니다.
Maven
은 이러한 빌드 과정을 지원하기 위한 빌드 도구(build tool)입니다. Maven은 빌드ㆍ배포 뿐만 아니라 패키지 의존성(라이브러리)를 관리하는 기능도 제공합니다.
Maven 기능을 이용하기 위해서는 POM이 사용됩니다. POM
이란 프로젝트 객체 모델(Project Object Model)로서 프로젝트의 다양한 정보를 처리위한 객체 모델입니다.
Maven에서는 pom.xml을 통해 이러한 POM 설정을 XML 태그로 설정할 수 있습니다.
아래는 pom.xml의 예시입니다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.khjdev</groupId>
<artifactId>blog</artifactId>
<name>Blog</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<spring-version>3.1.1.RELEASE</spring-version>
...
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
...
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
...
</plugins>
</build>
</project>
pom.xml에는 여러가지 태그가 있습니다. 아래는 그 설명입니다.
🔖<project>
: 루트 태그로 루트 태그 내에 모든 정보를 기술합니다.
└ modelVersion
: POM model 버전
└ groupId
: 프로젝트를 생성하는 조직의 고유 아이디. 일반적으로 도메인 이름을 거꾸로 적는다.
└ artifactId
: 프로젝트 빌드시 파일 대표이름이다. groupId 내에서 유일해야한다.
└ version
: 프로젝트의 현재 버전, 프로젝트 개발 중일 때는 SNAPSHOT을 접미사로 사용
└ packaging
: 패키징 유형(jar, war, 등)
└ name
: 프로젝트 이름
└ description
: 프로젝트에 대한 간략한 설명
└ url
: 프로젝트에 대한 참고 Reference 사이트
└ properties
: 라이브러리의 버전관리시 용이하다. 변수라고 보면된다.
ex) java.version 선언 후 dependencies에서 다음과 같이 활용한다.
<version>${java.version}</version>
└ dependencies
: 프로젝트와 의존관계에 있는 라이브러리들(dependency)을 관리한다.
└ build
: 빌드에 사용할 플러그인 목록
🔖<dependencies>
: 태그 아래에는 사용할(의존 관계에 있는) 라이브러리 목록을 기술합니다.
dependency
태그를 통해 기술된 의존관계는 Maven에서 Maven Repository에 등록된 라이브러리라면 자동으로 다운로드 해줍니다.
<dependecies>
<dependency>
<groupId>그룹 ID</groupId>
<artifactId>아티팩트 ID</artifactId>
<version>버전</version>
<scope>범위</scope>
</dependency>
</dependencies>
정리하자면, 프로젝트의 정보(버전, 이름, 라이브러리, 등)를 변경하기 위해서는 pom.xml 설정 파일을 변경해야 합니다.
그 다음 중요한 스텝은 web.xml
을 통해 스프링 설정파일
을 파악하고, 해당 파일을 통해 객체(bean)
를 선언하는 것입니다.
사용하기 전에 온전한 설정을 위해서 웹 애플리케이션 동작과정에 대해 이해하고 넘어가야 합니다.
Web Application의 동작 과정을 이해하기 위해서는 두가지 컨테이너(Servlet Container
와 Spring Container
)에 대해서 이해해야 합니다.
본디 컨테이너
는 여러 의미로 쓰일 수 있지만, 여기서 말하는 컨테이너의 의미는 인스턴스(instance)
의 수명주기(Lifecycle)
(생성 ~ 소멸)를 관리하는 역할을 수행하는 쉽게 말해 인스턴스 관리자
입니다.
Servlet 컨테이너
는 Servlet
의 생성, 수행, 소멸을 관리합니다.
Servlet이란
Servlet
은 사용자의요청(request)
을 처리해서 동적인 컨텐츠를 응답(response)하기 위한 프로그램입니다.
Spring 컨테이너
는 Spring 객체(bean)
의 생성, 수행, 소멸을 관리합니다.
"컨테이너가 관리를 한다는 것"은 정확히 어떤 의미인가요?
정확히 말해, 개발자가 컨테이너의 인스턴스의 수명주기에 관여하지 않는다는 것입니다.
아래에 두가지 예시가 있습니다.
1. Servlet Container - 우리는 어떠한 원리로Tomcat Server
가 실행되는지 알수 없습니다.
2. Spring Container -@Controller 어노테이션
을 사용했지만 우리는new
명령어를 통해 객체를 생성하지 않았습니다
Servlet Container
와 Spring container
사이의 관계를 도식화하면 아래와 같습니다.
Tomcat Server
를 구동한다
└ web.xml
을 로딩하여 Servlet Container
가 구동된다.
Servlet Container
가 web.xml의 기술된 listener
들과 servlet
목록 등 요청처리에 필요한 각종 설정들을 불러와 로드한다.
└ ContextLoaderListener
로 RootContext
를 생성
└ Servlet
에는 DispatcherServlet
하나만 생성하여 Servlet의 모든 요청을 받게하고, ServletContext
를 생성
RootContext 및 ServletContext 생성시 param으로 전달된 xml 파일을 통해 Spring Container
가 객체(bean)
을 생성ㆍ관리한다.
ServletContainer로 생성된 RootContext와 ServletContext는 서로 비슷한 부분이 많아, 비교하여 설명해볼까 합니다.
서블릿 요청(request)와 관련된 객체를 정의합니다. 주로 View와 관련된 객체(controllers, view resolvers, 등)를 설정합니다.
Root Context는 직역하자면 전역적으로 사용할 빈들을 선언한다는 의미로 해석될 수 있습니다. Servlet Context와는 반대로 View와 관련되지 않은 객체를 정의합니다.
Service, Repository(DAO), DB 등 비즈니스 로직
과 관련된 설정합니다. (일반적으로 여러 Servlet 인스턴스에서 공유해야 하는 데이터 저장소 및 비즈니스 서비스와 같은 인프라 Bean)
context:component-scan
Spring에서 빈을 생성하기 위해 XML에서 beans:bean 태그로 정의하거나, context:component-scan 태그을 통해 스프링 객체를 로드
<!-- servlet context -->
<context:component-scan base-package="com.khjdev.blog" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<!-- root context -->
<context:component-scan base-package="com.khjdev.blog">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
이러한 설정으로 인해, 우리는 자바코드에서 Controller
및 Service
, Repository
등의 Spring Component
를 사용할 수 있는 것입니다.
정리하자면 아래와 같습니다.
web.xml
에 Root Context
, Servlet Context
의 설정을 바꿀수 있다.Root Context
에는 Business Layer인 Service, Repository 등의 객체를 선언한다.Servlet Context
에서는 Presentation Layer인 View, ViewResolver 등 View와 관련된 객체를 선언한다.웹 애플리케이션의 동작 과정을 설명하면서, Servlet 요청을 처리하는 과정도 함께 기술할까 합니다.
앞서 설명했듯이 Servlet
이란 놈이 웹의 요청을 처리합니다. 하지만, 자바에서 공식 지원하는 Servlet API
를 이용하여 매번 Servlet 객체를 구현하여 요청을 처리할 수는 있지만, 정말 매~~우 귀찮은 일이기 때문에 우리는 Spring
이라는 웹 프레임워크를 사용하는 것입니다.
Spring에서는 이러한 문제를 해결하기 위해 모든 '/'로 들어오는 Servlet 요청을 DispatcherServlet이 받아서 처리하도록 구현했습니다.
web.xml
을 보면 아래와 같이 servlet이 한개 등록되어 있습니다.
<web-app>
...
<!-- 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>
Spring MVC가 어떤 구조로 되어있는지 설명하기 위해 웹 요청 처리 과정을 도식화하였습니다.
클라이언트가 요청합니다.
ex) 게시판 요청 - /boards
DispatcherServlet
은 HandlerMapping
에게 해당 요청(url)을 처리할 수 있는 Controller를 찾아 달라고 요청합니다.
└ 없으면 404 Error 반환
└ 있으면 해당 Controller에게 전달합니다.
Controller
는 받은 요청을 처리합니다.
└ 이때 Service단은 표시하시는 않지만, Controller에서도 Service에게 처리를 위임합니다.
해당 결과값으로, View
또는 ModelAndView
객체를 다시 DispatcherServlet 에게 반환합니다.
DispatcherServlet
은 Controller에게 받은 값을 ViewResolver
에게 전달하여, ViewResolver는 prefix와 suffix를 적용한 값을 반환합니다.
DispatcherServlet은 해당 뷰를 호출하면서 Model도 함께 전달합니다.
View
은 Model 객체를 가져와서 응답결과를 만들고 클라이언트에게 응답 결과를 전달합니다.
결론적으로 우리는 web.xml
에는 여러가지 웹요청에 관한 설정이 등록될 수 있습니다.
그중에서도 특히 Servlet
에는 DispatcherServlet
하나만 등록되고, 이 서블릿이 웹에 들어오는 모든 요청을 받아 분산시켜주는 역할을 합니다.
또한 web.xml
의 contextConfigLocation
에 기술된 스프링 빈 환경설정 파일을 바탕으로 Spring Container가 구동되어 해당 파일에서 찾을 수 잇는 빈(bean)들이 메모리에 올라갑니다. 그렇기 때문에 각 설정파일의 용도에 맞게 bean을 등록하거나, Spring이 객체를 인식할 수 있도록 component를 scan하는 작업을 해야합니다.
이번 포스팅에서는 Spring MVC 프로젝트의 구조를 살펴보고 웹 애플리케이션의 동작원리, 웹 요청 처리 과정에 대해 포스팅했습니다.
다음 포스팅에서는 이들과 실제 프로젝트에서 어떤 파일을 봐야되고, 어떻게 세팅해야되는지에 대해 포스팅하겠습니다. 읽어주셔서 감사합니다. 🥰🥰