본문 바로가기
👨‍💻 개발/spring boot

[개념 및 기초] #3. 스프링 부트 동작원리

by 말랑한곰탱이 2021. 10. 13.

1. 내장 톰캣을 가진다.

톰캣을 따로 설치할 필요 없이 바로 실행 가능하다.

Socket : 운영체제가 가지고 있는 것!

아파치 : 요청한 파일을 응답해주는 것

톰캣 : 요청한 파일 중 자바 코드가 요청되면 그것을 컴파일하여 html로 돌려준다.

2. 서블릿 컨테이너(톰캣)

사진 출처 : https://parkyounghwan.github.io/2019/08/23/spring/java-servlet-container/

 

요청 -> 무조건 동작하는 게 아님. 

정적인 파일(html, css, png 등) 요청하면 아파치가 응답. 톰캣 x

동적인 파일(자바) 요청하면 톰캣이 응답.

 

URL - Uniform Resource Locator : 위치 http://naver.com/a.png

URI - Uniform Resource Identifier :  식별자 http://naver.com/picture/a

 

스프링은 url 접근방식을 막아 놈. uri로 요청해야 함. 특정한 파일 요청을 할 수 없다. 

-> 요청 시에는 무조건 자바를 거쳐야 한다. -> 톰캣이 수행한다. 

 

자바 관련된 것을 요청할 때마다 서블릿 컨테이너에서 일어나는 일 :

  1. 스레드 생성
  2. 서블릿 객체 생성

스레드가 요청마다 만들어진다. 서블릿 객체는 하나로 사용한다. 최대 스레드 개수를 넘어가면 다음 요청들은 대기한다. 응답을 해주면 그 스레드와 서블릿 객체는 대기 중인 요청에 사용된다.

-> 속도가 빠름! 멀티 스레드를 사용하고 지속적으로 재사용하기 때문.

 

3. web.xml

web.xml : 웹 애플리케이션의 배포 설명자로, 각 어플리케이션의 환경을 설정하는 역할을 한다. 서버가 처음 로딩될 때 읽어 들이고, 해당 환경설정에 대해 tomcat에 적용하여 서버를 시작한다.

 

ServletContext의 초기 파라미터 설정

- 하나의 웹 어플에 적용할 수 있는 공통 변수라고 생각하면 된다. (웹 애플리케이션당 하나)

- 웹 어플당 하나라는 말은 웹 어플이 하나의 JVM에서 돌아갈 때 하는 말이다.

  분산 웹 어플 환경이라면 JVM 마다 context가 다를 수 있으므로 주의 요망

- web.xml 파일의 <context-param> 부분의 name과 value를 이용하여 세팅한다.

- ex) getServletContext(). getInitParameter(String);

출처: https://ceomk.tistory.com/69 [The way of the future]

 

Session의 유효시간 설정

사용자의 session 유효기간을 설정한다.

 

Servlet/JSP에 대한 정의 및 매핑

정의 : Servlet/JSP의 위치나 정보가 정의되어있음.

매핑 : 요청한 자원, 데이터의 위치를 알려주고 이동할 수 있게 도와줌.

 

Mime Type 매핑

Mime Type : 들고 온 데이터의 type (text, image, video, audio, application 등)

Mime은 "Multipurpose Internet Mail Extensions"의 약자로 파일 변환을 위한 포맷이다. 이름에서도 알 수 있듯이 MIME은 이메일에 첨부된 파일을 텍스트 문자 형태로 변환해서 이메일과 함께 전송하기 위해 개발된 포맷이다. MIME 이름의 Internet Mail Extension에서 본래의 의도를 알 수 있다. 

 

Mime Type에 맞게 매핑을 해준다. Type이 다른 곳으로 가면 에러!

 

Welcome File list

아무 이유 없이 들어온 애들을 보내는 곳. 관리자가 설정하기 나름이다.

 

Error Pages 처리 및 설정

이상한 주소가 들어올 때 Error Pages를 보냄 

 

리스너/필터 설정

리스너 : 바쁜 문지기(web.xml)를 대신하여 관리자가 원하는 애를 빠르게 식별하여 처리하도록 도와줌.

필터 : 들어올 자격이 있는지 검증

 

보안 

말 그래도 보안.

 

여기에서 Servlet/JSP 매핑 시(web.xml에 직접 매핑 or @WebServlet 어노테이션 사용)에 모든 클래스에 매핑을 적용시키기에는 코드가 너무 복잡해지기 때문에 FrontController 패턴을 이용.

 

4. FrontController 패턴

최초 앞단에서 request 요청을 받아서 필요한 클래스에 넘겨준다. 왜? web.xml에 다 정의, 매핑하기가 너무 힘들기 때문.

 

요청이 URI 혹은 자바 파일인 경우 바로 자원으로 이동 못하고, 톰캣으로 이동

-> 톰캣에서 request(요청한 사람의 정보. 어떤 데이터를 들고 왔는지, 요청하는지)와 response(응답해야 할 데이터) 객체 자동 생성

-> 객체로 만들면 자바에서 request. 함수 or변수를 쉽게 사용 가능

-> web.xml에서 일을 하지만 바쁘기 때문에, but 미리 선택한 특정 주소가 들어오면 FrontController가 그 주소를 낚아챔.

-> 낚아채면 실제 자원에 접근하기 위해 내부에서 자원으로 request요청을 함. (외부에선 자원 접근 불가, 내부에서는 가능)

-> 이때 새로운 요청이 생기기 때문에 request와 response가 새롭게 new 될 수 있다. 그래서 아래의 RequestDispatcher가 필요하다.

 

5. RequestDispatcher

필요한 클래스 요청이 도달했을 때 FrontController에 도착한 request와 response를 그대로 유지시켜준다. 웹페이지에서 페이지를 이동했을 때, 데이터를 가지고 이동하게 해 준다.

 

6. DispatchServlet

FrontController 패턴을 직접 짜거나 RequestDispatcher를 직접 구현할 필요가 없다. 왜냐면 스프링에는 DispatchServlet이 있기 때문! JSP에선 직접 짜야됨!

스프링의 DispatchServlet = FrontController + RequestDispatcher

 

DispatchServlet이 자동 생성될 때 수많은 객체가 생성(IoC)된다. 보통 필터들이다. 해당 필터들은 내가 직접 등록할 수 도 있고 기본적으로 필요한 필터들은 자동 등록된다.

 

궁극적으로 하는 일 : 주소 분배 

주소 분배를 하려면 각각의 클래스들이 메모리에 저장되어 있어야 한다. 그래서 주소 분배를 하기 컴포넌트 스캔을 통해 Src 소스 폴더 내의 자바 파일에서 객체를 자동으로 메모리에 등록한다. IoC. 이때 자바파일에 어노테이션을 보고 메모리에 띄울지를 결정함. 보통 @Controller, RestController, Configuration, Repository, Service, Component가 붙으면 띄움.

 

7. 스프링 컨테이너

DispatchServlet에 의해 생성되는 수많은 객체들은 어디에서 관리될까?

 

첫째, ApplicationContext.

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

 

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

 

servlet-applicationContext는 ViewResolver, Interceptor, MultipartResolver 객체를 생성하고 웹과 관련된 어노테이션 Controller, RestController를 스캔한다. 

해당 파일은 DispatchServlet에 의해 실행된다.

 

root-applicationContext는 해당 어노테이션을 제외한 어노테이션 Service, Repository 등을 스캔하고 DB 관련 객체를 생성한다. 스캔이란 메모리에 로딩한다는 뜻.

해당 파일은 ContextLoaderListener에 의해 실행된다. ContextLoaderListener를 실행해주는 녀석은 web.xml이기 때문에 root-applicationContext는 servlet-applicationContext보다 먼저 로드된다. 당연히 servlet-applicationContext에서는 root-applicationContext가 로드한 객체를 참조할 수 있지만 그 반대는 불가능하다. 생성 시점이 root -> servlet 이기 때문이다.

이미지 출처 : spring.io



둘째, Bean Factory

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



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

GET 요청 => http://localhost:8080/post/1(uri) (post/1 : 식별자)

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

 

9. 응답

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

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

 

요청 처리 로직

이미지 출처 : https://asfirstalways.tistory.com/334

  1. 톰캣 실행 시. web.xml이 자신이 해야 할 일을 쫙 읽는다. 근데 할 일이 너무 많다… 누가 분배해줄 순 없나..?라는 생각을 가지고 있음.
  2. ContextLoaderListener create. 
  3. applicationContext가 읽어짐. -> root-context.xml도 같이 읽어짐. 보통 DB와 관련된 어떤 객체들을 컴포넌트 스캔을 해서 메모리에 올린다.
  4. Spring Container(root) 내부에 DB 관련된 애들을 메모리에 로드한다.
  5. 사용자에게 request 받음.
  6. DispatcherServlet이 FrontController 패턴으로 web.xml을 일을 분배해서 해줌.
  7. servlet-context.xml을 읽고 웹과 관련된 애들을 메모리에 띄운다.
  8. 이제 주소 분배를 하고 응답을 html 파일로 할지 Data로 할지 결정한다.