[Spring] Tomcat + Spring Framework 구동 흐름 정리

|

1. Tomcat에 의해 web.xml 로드하고, web.xml의 ContextLoaderListener 생성

 

* ContextLoaderListener
- ServletContextListener 인터페이스를 구현하고 있으며, ApplicationContext를 생성하는 역할을 수행한다.
- Servlet의 생명주기를 관리해준다.
- Servlet을 사용하는 시점에 서블릿 컨텍스트에 ApplicationContext 등록, Servlet이 종료되는 시점에 ApplicationContext 삭제

- `contextConfigLocation` 파라미터를 사용하여 load할 수 있는 설정파일을 지정

 

* ApplicationContext

- IoC엔진, bean의 생명주기 담당

- 최상위 컨텍스트
- root-context에 등록되는 빈들은 모든 컨텍스트에서 사용 할 수 있다. (공유가능)
- Service, DAO를 포함한 웹환경에서 독립적인 Bean들을 담아둔다. (@Service, @Repository)
- 서로 다른 servlet-context에서 공유해야 하는 빈들을 등록해놓고 사용할 수 있다.
- servlet-context내 Bean들은 이용이 불가능하다.

// web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app  xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
          version="3.1">
  <display-name>ㅇㅇㅇㅇ</display-name>
  <description></description>
  <context-param>
    <param-name>package.version</param-name>
    <param-value>5.3.5.0-SNAPSHOT</param-value>
  </context-param>
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>*.do</url-pattern>
    <url-pattern>*.json</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>HTMLTagFilter</filter-name>
    <filter-class>softin.sams.framework.filter.HTMLTagFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>HTMLTagFilter</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>FServiceLogFilter</filter-name>
    <filter-class>softin.sams.framework.filter.FServiceLogFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>FServiceLogFilter</filter-name>
    <url-pattern>*.do</url-pattern>
    <url-pattern>*.json</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>WebResourceAuthorizedFilter</filter-name>
    <filter-class>softin.sams.framework.filter.WebResourceAuthorizedFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>WebResourceAuthorizedFilter</filter-name>
    <url-pattern>/docs/*</url-pattern>
  </filter-mapping>
  <!-- <filter>
    <filter-name>CustomWebSecurityFilter</filter-name>
    <filter-class>softin.sams.framework.filter.CustomWebSecurityFilter</filter-class>
    <init-param>
      <param-name>content.security.policy</param-name>
      <param-value>default-src 'self'; connect-src 'self' *.kakao.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.kakao.com *.daumcdn.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: *.kakao.com *.daumcdn.net;</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CustomWebSecurityFilter</filter-name>
    <url-pattern>*.do</url-pattern>
    <url-pattern>*.json</url-pattern>
  </filter-mapping> -->
  
  <!-- ② contextConfigLocation 파라미터를 사용하여 load할 수 있는 설정파일을 지정 ★★★ -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath*:inframework/spring/context-*.xml,
      classpath*:inframework/spring/custom/context-*.xml
    </param-value>
  </context-param>
  
  <!-- ① ContextLoaderListener ★★★ --> 
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  
  <!-- 서블릿 컨텍스트 파일 경로 지정 ★★★ --> 
  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/config/inframework/springmvc/dispatcher-*.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
    <url-pattern>*.json</url-pattern>
    <url-pattern>*.in</url-pattern>
  </servlet-mapping>
  
  <!-- <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.jpg</url-pattern>
    <url-pattern>*.png</url-pattern>
    <url-pattern>*.gif</url-pattern>
  </servlet-mapping> -->
  
  <login-config>
    <auth-method>BASIC</auth-method>
  </login-config>
  <session-config>
    <cookie-config>
      <name>Softin-ORGN_SID</name>
    </cookie-config>
    <session-timeout>60</session-timeout>
    <tracking-mode>COOKIE</tracking-mode>
  </session-config>
  <welcome-file-list>
    <welcome-file>index.do</welcome-file>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>403</error-code>
    <location>/html/errorView.html</location>
  </error-page>
  <error-page>
    <location>/error/errorView.do</location>
  </error-page>
  <!-- JNDI 설정 -->
  <resource-ref>
    <description>JNDI DATASOURCE</description>
    <res-ref-name>JNDI-softin-orgn</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>
  <!-- Security -->
  <security-constraint>
    <web-resource-collection>
        <web-resource-name>Allowed methods</web-resource-name>
        <url-pattern>/*</url-pattern>
        <http-method-omission>GET</http-method-omission>
        <http-method-omission>POST</http-method-omission>
    </web-resource-collection>
    <auth-constraint />
    <!-- HTTPS Redirect -->
    <!-- <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint> -->
  </security-constraint>
  <jsp-config>
    <jsp-property-group>
      <url-pattern>*.jsp</url-pattern>
      <trim-directive-whitespaces>true</trim-directive-whitespaces>
    </jsp-property-group>
  </jsp-config>
</web-app>

 

2. contextConfigLocation에 mapping된 xml 파일을 기준으로 Spring Container(ROOT: 첫번째 spring container)가 구동

- 여기서 대상이 되는 xml파일은 case가 여러가지가 될수 있다. ( ex. root-context.xml, applicationContext.xml)

- 아래 예시의 경우 (context-common.xml) 전자정부 프레임워크의 context-*.xml 파일들에서 설정한 내용들을 모두 합쳐놓은 부분

- 여기서 대상이 되는 xml파일은 주로 view 지원을 제외한 공통 bean을 설정한다.(web과 관련된 bean들은 등록하지 않음)

// context-common.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:context="http://www.springframework.org/schema/context"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
							http://www.springframework.org/schema/context	http://www.springframework.org/schema/context/spring-context-4.0.xsd">

	<!-- Component scan -->
    <context:component-scan base-package="inframework,softin.sams">
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

	<!-- Spring XML 설정파일에서 Properties 정보 읽어 올수 있도록 추가 -->
	<bean id="propertyConfigurer" class="softin.sams.framework.properties.configurer.EncryptablePropertyConfigurer">
		<!-- SamsProperties 에서 properties 설정 파일 로드 처리함 -->
		<property name="samsProperties" ref="samsProperties" />
    </bean>

	<!-- MessageSource -->
	<bean id="localMessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
		<property name="basenames">
			<list>
				<value>classpath:/inframework/message/message-common</value>
				<value>classpath:/inframework/message/message-code</value>
			</list>
		</property>
		<property name="defaultEncoding" value="UTF-8"/>
		<property name="cacheSeconds">
			<value>60</value>
		</property>
	</bean>
	<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>

	<!-- 로컬 리소스 -->
	<bean id="samsMessage" class="softin.sams.framework.properties.SamsMessage">
        <property name="messageSource" ref="localMessageSource" />
	</bean>
	<bean id="samsProperties" class="softin.sams.framework.properties.SamsProperties" init-method="loadProperties">
		<property name="samsMessage" ref="samsMessage" />
	</bean>
    
	<!-- DB 리소스 -->
    <bean id="messageSource" class="softin.sams.framework.properties.DBMessageSource">
	</bean>
	<bean id="samsDBMessage" class="softin.sams.framework.properties.SamsDBMessage">
        <property name="messageSource" ref="messageSource" />
    </bean>

	<!-- MultiPart File Upload -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<property name="maxUploadSize" value="${Globals.upload.maxUploadSize:10485760}"/>
		<property name="maxInMemorySize" value="${Globals.upload.maxInMemorySize:10240}"/>
	</bean>
</beans>

 

3. Client로부터 웹 어플리케이션 요청이 옴.

- 최초의 클라이언트 요청에 의해 DispatcherServlet 생성

- DispatcherServlet 객체는 WEB-INF/config 폴더에 존재하는 대상이 되는 xml파일 (ex. servlet-context.xml) 파일을 로딩하여 두번째 스프링 컨테이너를 구동

- 이 두 번째 스프링 컨테이너가 Controller 객체를 메모리에 생성한다.
- DispatcherServlet은 FrontController의 역할을 수행한다.(클라이언트로부터 요청 온 메시지를 분석하여 알맞은 PageController에게 전달하고 응답을 받아 요청에 따른 응답을 어떻게 할지 결정한다.)

- 이때 첫번째 Spring Container(ROOT) 가 구동되면서 생성된 DAO, VO, ServiceImpl 클래스들과 협업하여 알맞은 작업을 처리하게 된다.

 

* DispatcherServlet 클래스에 의해 만들어지는 것이 WebApplicationContext

* WebApplicationContext

- servlet-context에 등록되는 빈들은 해당 컨텍스트에서만 사용할 수 있다.
- DispatcherServlet이 직접 사용하는 Controller를 포함한 웹 관련 빈을 등록하는 데 사용 (@Controller)
- 독자적인 컨텍스트들을 가지며, root-context 내 빈 사용이 가능

 

// dispatcher-servlet.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"
       	xmlns:aop="http://www.springframework.org/schema/aop"
       	xmlns:p="http://www.springframework.org/schema/p"
       	xmlns:context="http://www.springframework.org/schema/context"
       	xmlns:mvc="http://www.springframework.org/schema/mvc"
       	xsi:schemaLocation="http://www.springframework.org/schema/beans 	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                http://www.springframework.org/schema/context 	http://www.springframework.org/schema/context/spring-context-4.0.xsd
                http://www.springframework.org/schema/aop    http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/mvc 	http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd" >

    <!-- <context:component-scan base-package="inframework,softin">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan> -->

    <!-- 패키지 내 Controller, Service, Repository 클래스의 auto detect를 위한 mvc 설정 -->
    <context:component-scan base-package="inframework,softin.sams,softin.test" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

	<!--
    <mvc:annotation-driven />
    <context:annotation-config />
    <aop:aspectj-autoproxy />
    -->

    <!-- <mvc:annotation-driven /> -->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <!-- JSON HTML Tag escape 처리 -->
            <!-- ★★★ HttpMessageConverter: 자바객체와 Http 요청/응답 몸체 사이의 변환 처리--> 
            <bean class="softin.sams.framework.common.gson.CustomGsonHttpMessageConverter" />
        </mvc:message-converters>
    </mvc:annotation-driven>

    <aop:aspectj-autoproxy />

      <!-- 인증 인터 셉터 -->
    <mvc:interceptors>
        <!-- 사용자 인증 사용하는 경우 아래 path 추가 필수 -->
        <mvc:interceptor>
			<mvc:mapping path="/cmm/**" />
			<mvc:mapping path="/web/**" />
			<mvc:mapping path="/api/**" />
			<mvc:mapping path="/inorg/**" />
            <!-- exclude -->
            <mvc:exclude-mapping path="/api/msagw/**"/>
            <mvc:exclude-mapping path="/msagw/api/**"/>
			<bean class="softin.sams.framework.interceptor.DefaultAuthInterceptor" />
        </mvc:interceptor>

        <!-- 로그인 접근 인증 -->
        <mvc:interceptor>
			<mvc:mapping path="/login/**" />
            <!-- exclude -->
            <mvc:exclude-mapping path="/login/logout.do"/>
			<bean class="softin.sams.framework.interceptor.LoginAccessInterceptor" />
        </mvc:interceptor>

        <!-- 서버 -->
		<mvc:interceptor>
			<mvc:mapping path="/server/**" />
            <!-- health check -->
            <mvc:exclude-mapping path="/server/health/**"/>
			<bean class="softin.sams.framework.server.interceptor.ServerAuthenticInterceptor" />
		</mvc:interceptor>

        <!-- MSA GW -->
		<mvc:interceptor>
            <mvc:mapping path="/api/msagw/**" />
            <mvc:mapping path="/msagw/api/**" />
			<bean class="softin.sams.framework.msagw.interceptor.MsaGwAuthenticInterceptor" />
		</mvc:interceptor>

		<!-- Open API -->
		<mvc:interceptor>
			<mvc:mapping path="/openapi/**" />
			<bean class="softin.sams.framework.openapi.interceptor.OpenApiServerInterceptor" />
		</mvc:interceptor>

    </mvc:interceptors>

	<!-- Tiles3 적용 -->
  	<bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver" p:order="1">
		<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView" />
	</bean>
    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/config/inframework/tiles/default-layout.xml</value>
            </list>
        </property>
    </bean>
</beans>

 

4. 클라이언트 요청으로부터 DispatcherServlet의 전체 흐름

- 클라이언트가 해당 어플리케이션에 접근하면 접근한 URL 요청을 DispatcherServlet이 가로챔 

- RequestMappingHandlerMapping이 해당 요청을 처리할 컨트롤러를 찾음 

- DefaultListableBeanFactory 가 mainContoller 를 쓰면 된다고 알려줌

- RequestResponseBodyMethodProcessor가 servlet-context.xml 에 선언해놓은 MessageConverter을 이용하여 요청 바디(파라미터)를 읽음

- DataSourceTransactionManager 로 DB 접속

- 컨트롤러에서 view를 리턴하면 ViewResolver가 먼저 받아 해당 view가 존재하는지 검색

- DispatcherServlet은 ViewResolver를 통해 접두사와 접미사가 붙은 JSP 파일의 이름과 경로를 리턴받아 최종적으로 JSP를 실행. view에 결과를 보낸 후 DispatcherServlet은 최종 결과를 클라이언트에 전송

And