스프링부트 동작원리


(1) 내장 톰캣을 가진다.

스프링부트는 톰캣을 따로 설치할 필요 없이 바로 실행이 가능하다.

  • socket 통신: 연결이 지속되어 있는 통신 → 원하는 시점에 언제든지 데이터를 전달할 수 있음
  • http 통신: 요청 시에만 정적인(static) 자원을 응답해줌 → 소켓 통신에 비해 부하가 적지만 매번 새로운 연결을 해야 함
톰캣이란?

웹서버(=> 아파치)는 클라이언트가 요청(request)한 자원을 응답(response)해준다. 그러나 아파치는 .jsp 파일은 응답하지 못한다. 이때 자바코드를 인식시키기 위해 톰캣이라는 것을 달아 준다. 톰캣은 .jsp를 컴파일해서 .html 파일로 번역해서 돌려준다.

즉, 톰캣은 자바코드를 컴파일해서 .html 문서로 만들어 주는 역할을 한다.

 

 

(2) 서블릿 컨테이너

서블릿(Servlet)이란?
  • 기존에 서버는 정적인 자료(HTML, 사진, 텍스트)만을 주고 받았다. 하지만 웹에 다양한 기능이 요구되면서 정적인 자료뿐만 아니라 사용자의 요구에 맞춘 동적인 페이지들을 만들 필요가 생겼다. 이를 위해 만들어진 것이 바로 서블릿(Servlet)이다.
  • 서블릿은 클라이언트의 요청에 맞춰 동적인 결과를 만들어주는 웹 프로그래밍 기술이다.
  • 서블릿은 WAS(Web Application Server)의 서블릿 컨테이너 안에서 동작한다.
  • MVC 패턴의 Controller 역할을 맡는다.
서블릿 컨테이너(Servlet Container)란?
  • 서블릿을 담고 관리해주는 컨테이너
  • 구현되어 있는 서블릿 클래스의 규칙에 맞게 서블릿을 관리하며 클라이언트의 요청을 받으면 HttpServletRequestHttpServletResponse 객체를 생성하여 post, get 여부에 따라 동적인 페이지를 생성하여 응답한다.
  • 아파치 톰캣(Apache Tomcat), 제티(Jetty) 등이 서블릿 컨테이너

 

(3) web.xml

web.xml = 큰 성의 문지기 (관리자가 문지기에게 업무 매뉴얼을 주면, 그것을 보고 문지기가 일을 한다.)

  • ServletContext의 초기 파라미터
    성 출입 암구호: 왈라 → 왈라를 외친 A는 출입 가능
  • Session의 유효시간 설정
    A의 Session 유효시간은 3일로 설정됨. 3일 뒤에 문지기에게 3일 더 있을 것이라고 얘기하면 세션이 초기화되면서 3일 더 머무를 수 있다.
  • Servlet/JSP에 대한 정의 및 매핑
    문지기는 A가 요청한 자원, 로케이션, 식별자가 어디인지 정의되어 있는 문서를 확인한 뒤 그 위치를 정확히 알려주고 이동할 수 있게 안내함
  • Mime Type 매핑
    문지기는 A가 들고 올 데이터 타입이 무엇인지 물어봄
    - A가 아무것도 안 들고 온 경우 GET방식 요청 (데이터 X): select
    - A가 쌀을 들고 온 경우 쌀 창고로 데이터를 보낸 뒤, 쌀을 먹을 수 있도록 가공함
  • Welcome File list
    아무 이유 없이 들어온 사람들은 광장으로 보내라 => 광장: welcome file list
  • Error Pages 처리
    존재하지 않는 로케이션을 들고 온 사람은 이상한 광장으로 보내라 => 이상한 광장: error pages
  • 필터/리스너 설정
    필터: A의 신분을 확인 (A나라가 아닌 사람은 내쫓음)
    리스너: 문지기 대리인 (술 잘 먹는 사람만 확인하는 대리인. 문지기와 별도로 술 잘 먹는 사람만 지켜보는 사람)
  • 보안
    불법체류자, 범죄자, 현상수배범 등 이상한 사람들이 오면 쫓아냄

 

(4) FrontController 패턴

FrontController는 최초 앞단에서 request 요청을 받아서 필요한 클래스에 넘겨준다.

왜? web.xml에 다 정의하기가 너무 힘듦.

 

A라는 사용자가 외부에서 request를 한다. 이때 .do와 같이 특정 주소를 통해 요청을 하면 그 요청은 톰캣 내부의 FrontController로 보내진다. FrontController는 내부 자원 접근을 위한 request를 또 진행한다.

이처럼 request가 재요청되면 요청할 때마다 새로 만들어지기 때문에 request와 response가 새롭게 new 될 수 있다.

이를 방지하기 위해 필요한 것이 RequestDispatcher이다. RequestDispatcher는 기존에 있는 request와 response를 유지해서 재사용이 가능하게 만든다.

 

(5) RequestDispatcher

필요한 클래스 요청이 도달했을 때 FrontController에 도착한 request와 response를 그대로 유지시켜 준다.

RequestDispatcher를 이용해야 페이지 간 데이터 이동이 가능하다.

 

 

(6) DispatcherServlet

  • FrontController 패턴 + RequestDispatcher
  • 우리는 FrontController 패턴을 직접 짜거나 RequestDispatcher를 직접 구현할 필요가 없다. 왜냐하면 스프링에는 DispatcherServlet이 있기 때문이다.
  • DispatcherServlet이 자동 생성될 때 수많은 객체가 생성(IoC)된다. 보통 필터들이다. 해당 필터들은 내가 직접 등록할 수도 있고, 기본적으로 필요한 필터들은 자동 등록된다.

 

(7) 스프링 컨테이너

 

① web.xml: 문지기 역할

② ContextLoaderListener: 모든 요청에서 공통적으로 쓰이는 DB 관련된 것 또는 모든 스레드가 공통적으로 사용해도 되는 것들을 ContextLoaderListner에서 미리 띄운다. ContextLoaderListner는 root-context.xml 파일을 읽어서 공통적으로 쓰이는 것들을 메모리에 띄우고, IoC 컨테이너에서 관리한다.

 

DispatcherServlet에 의해 생성되는 수많은 객체들은 어디에서 관리될까? (ApplicationContext, Bean Factory)

 

첫째, ApplicationContext

 

DispatcherServlet이 component scan을 하면 수많은 객체들이 ApplicationContext에 등록된다. 이것을 IoC라고 한다.

IoC란 제어의 역전을 의미한다. 개발자가 직접 new를 통해 객체를 생성하면 해당 객체를 가리키는 레퍼런스 변수를 관리하기 어렵다. 그래서 스프링이 직접 해당 객체를 관리한다. 이때 우리는 주소를 몰라도 된다. 왜냐하면 필요할 때 DI하면 되기 때문이다. DI(Dependency Injection)를 의존성 주입이라고 한다. 필요한 곳에서 ApplicationContext에 접근하여 필요한 객체를 가져올 수 있다. ApplicationContext는 싱글톤으로 관리되기 때문에 어디에서 접근하든 동일한 객체라는 것을 보장해준다.

 

ApplicationContext의 종류에는 두 가지가 있다. (servlet-applicationContext와 root-applicationContext)

1. servlet-applicationContext
servlet-context.xml

  • ViewResolver, Interceptor, MultipartResolver 객체를 생성한다.
  • 웹과 관련된 어노테이션 Controller, RestController를 스캔한다.
  • 해당 파일은 DispatcherServlet에 의해 실행된다.

[servlet-context.xml 예시]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?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"
    xmlns:tx="http://www.springframework.org/schema/tx"
    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
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.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>
    <!-- 파일 업로드 위한 MultipartResolver빈 등록 =============================== -->
    <!-- 주의: id를 반드시 multipartResolver로 등록해야 한다. -->
    <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <beans:property name="defaultEncoding" value="UTF-8"/>
        <beans:property name="maxUploadSize" value="-1"/> <!-- -1: 무제한 업로드 가능 -->
    </beans:bean>
    <!-- Password Security ====================================================== -->
    <beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
    
    <!-- Interceptor ============================================================ -->
    <interceptors>
        <!-- 로그인 체크 인터셉터 -->
        <interceptor>
            <mapping path="/user/**"/>
            <mapping path="/admin/**"/>
            <beans:bean class="com.common.interceptor.LoginCheckInterceptor"/>
        </interceptor>
        <!-- 관리자 체크 인터셉터 -->
        <interceptor>
            <mapping path="/admin/**"/>
            <beans:bean class="com.common.interceptor.AdminCheckInterceptor"/>
        </interceptor>
    </interceptors>
    <!-- Transaction 관련 ================================================= -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <context:component-scan base-package="com.multi.campus, com.memo.controller, com.memo.model" />
    <context:component-scan base-package="com.memo.service, com.common"/>
    <context:component-scan base-package="com.shop, com"/>
    <!-- 해당 패키지에 선언된 어노테이션을 가진 클래스들을 scan => 필요하다면 이를 객체로 생성하여 관리 -->
 
</beans:beans>
cs

 

2. root-applicationContext

root-context.xml

  • 어노테이션 Service, Repository 등을 스캔(=메모리에 로딩)하고, DB 관련 객체를 생성한다.
  • 해당 파일은 ContextLoaderListener에 의해 실행된다. ContextLoaderListner를 실행해주는 것은 web.xml이기 때문에 root-applicationContext는 servlet-applicationContext보다 먼저 로드된다.
    당연히 servlet-applicationContext에서는 root-applicationContext가 로드한 객체를 참조할 수 있지만, 그 반대는 불가능하다. 생성 시점이 다르기 때문이다.

[root-context.xml 예시]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?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:context="http://www.springframework.org/schema/context"
    xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
    xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <!-- Root Context: defines shared resources visible to all other web components -->
    <!--[1] context, mybatis-spring 네임스페이스 추가 -->
    
    <!--[2] database.properties 파일이 있는 위치 정보 추가  -->
    <context:property-placeholder location="classpath:/config/props/database.properties"/>
    
    <!--[3] DBCP 커넥션풀 빈 등록 (1. DBCP, 2. HikariCP)  -->
    <!-- 1. Apache DBCP -->
    <bean id="dataSource-dbcp" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${db.driverClassName}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
    </bean>
    <!-- 2. HikariCP  -->
    <bean id="dataSource-hikari" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <constructor-arg ref="hikariConfig"/>
    </bean>
    <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
        <property name="driverClassName" value="${db.driverClassName}"/>
        <property name="jdbcUrl" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
    </bean>
<!-- [4],[5],[6]: mybatis 관련 설정들 -->
    <!--[4] SqlSessionFactoryBean 등록 (mybatis 핵심 객체)  -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource-hikari"/>
        <property name="configLocation" value="classpath:/config/mybatis/mybatis-config.xml"/>
    </bean>
    <!--[5] SqlSessionTemplate 등록  -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    <!--[6] mybatis-spring:scan 설정  -->
    <mybatis-spring:scan base-package="com.multi.mapper"/>
    <!-- com.multi.mapper패키지의 모든 MyBatis 관련 어노테이션을 찾아서 처리함 -->
</beans>
 
cs

 

 

 

둘째, Bean Factory

필요한 객체를 Bean Factory에 등록할 수도 있다. 객체를 Bean Factory에 등록하면 초기에 메모리에 로드되지 않고 필요할 때 getBean()이라는 메서드를 통하여 호출해서 메모리에 로드할 수 있다. 이것 또한 IoC이다. 그리고 필요할 때 DI하여 사용할 수 있다. ApplicationContext와 다른 점은 Bean Factory에 로드되는 객체들은 미리 로드되지 않고 필요할 때 호출하여 로드하기 때문에 lazy-loading이 된다는 점이다.

 

스프링에서 Bean을 수동으로 등록하는 방법으로, 설정 class 위에 @Configuration을 추가하고, @Bean을 사용해 수동으로 빈을 등록할 수 있다. 이때 메서드 이름으로 빈 이름이 결정된다.

1
2
3
4
5
6
7
8
9
@Configuration
public class ResourceConfig {
 
    @Bean
    public MangKyuResource mangKyuResource() {
        return new MangKyuResource();
    }
 
}
cs

 

 

(8) 요청 주소에 따른 적절한 컨트롤로 요청 (Handler Mapping)

GET 요청 => http://localhost:9090/post/1

해당 주소 요청이 오면 적절한 컨트롤러의 함수를 찾아서 실행한다.

 

 

(9) 응답

HTML파일을 응답할지 Data를 응답할지 결정해야 하는데, HTML 파일을 응답하게 되면 ViewResolver가 관여하게 된다.

[servlet-context.xml의 일부]

1
2
3
4
<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>
cs

 

하지만 Data를 응답하게 되면 MessageConverter가 작동하게 되는데 메시지를 컨버팅할 때 기본전략은 json이다.

→ @ResponseBody 사용

 

 


출처

1. Youtube 메타코딩

https://www.youtube.com/channel/UCVrhnbfe78ODeQglXtT1Elw

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 

2. https://getinthere.tistory.com/11

 

스프링부트 with JPA 3강 - Springboot 동작원리!

1. 스프링부트 동작원리 (1) 내장 톰켓을 가진다. 톰켓을 따로 설치할 필요 없이 바로 실행가능하다. (2) 서블릿 컨테이너 (3) web.xml -ServletContext의 초기 파라미터 -Session의 유효시간 설정 -Servlet/JSP

getinthere.tistory.com

 

+ Recent posts