Spring과 Spring Boot 동작이 어떻게 다른거야~
원티드 백엔드 강의를 들으면서, 내가 스프링과 스프링 부트의 동작 흐름에 대한 차이를 제대로 이해하지 못하고 있다는 걸 깨달았다.
이전 회사에서는 Spring Framework를 사용했는데, 설정 단계와 프로젝트 초기 세팅이 꽤나 복잡해서 고생했던 기억이 있다. 이번 프로젝트에서는 Spring Boot를 사용하게 되면서 초기 설정이 훨씬 간편해졌다는 걸 느꼈지만, 두 기술의 내부 흐름을 제대로 알 못한 채 활용하고 있다는 생각이 들었다.
그래서 이번 기회에 Spring과 Spring Boot의 동작 흐름과 차이점을 정리해 보려고 한다.
Web Server, WAS 너희 뭐야..? 왜 나왔어?
인터넷이 처음 나왔을때, 웹 페이지는 주로 텍스트 기반의 단순 정적 콘텐츠(HTML 파일)로 구성되었다. 사용자가 요청할 때마다 콘텐츠를 빠르고 안정적으로 전달할 시스템이 필요했고, 정적 파일을 관리하고 제공하기 위해 웹 서버가 등장했다. 웹 서버는 클라이언트로부터 HTTP 요청을 받아 HTML 파일, 이미지, CSS 같은 정적 리소스를 제공하였다. 초기에는 웹 사이트가 단순 HTML 파일로 구성되어 있었기 때문에 웹 서버가 정적 파일을 전송하는 역할로 충분하였다. 대표적인 웹 서버에는 Apache Http Server, Nginx가 있다.
하지만 점점 웹 사이트가 발전하면서 그 이상이 기능이 필요했다. 예를 들면 사용자 개개인마다 다른 정보를 제공하거나, DB 연동으로 동적인 기능이 필요해졌고, 웹 서버만으로는 한계가 있었다.
그래서 WAS가 나오게 되었다.
WAS는 동적 처리를 하기 위한 서버로, 디비와 연동하거나 프로그램 동적으로 페이지를 생성해준다. 대표적으로 Apache Tomcat가 있다.
흐름을 간단히 알아보면
가장 먼저 클라이언트가 웹 브라우저에 요청을 보내면, 이 요청은 인터넷을 통해 웹 서버로 전달이 된다.
웹 서버는 이 요청이 정적인지 동적인지 확인을 한 후에 정적인 콘테츠인 경우 직접 처리하고, 동적 콘텐츠인 경우, WAS로 보낸다.
요청을 받은 WAS는 비즈니스 로직에 따라 동적인 콘텐츠를 생성하고, 응답 결과를 웹서버로 반환한다. 그러면 최종적으로 웹 서버가 클라이언트에 반환한다.
현재 이미지에서는 웹 서버와 WAS가 분리되어 있지만 WAS안에 웹 서버가 포함되기도 한다.
다양한 구조
- Client - [웹 서버] - DB
-
Client - [WAS] - DB 이 구조는 WAS 단독으로 구성된 구조로, WAS가 웹 서버와 웹 컨테이너 역할을 동시에 수행한다.
- Client - [웹 서버] - [WAS] - DB
- Web Server는 웹 브라우저 클라이언트로부터 HTTP 요청을 받는다.
- Web Server는 클라이언트의 요청(Request)을 WAS에 보낸다.
- WAS는 관련된 Servlet을 메모리에 올린다.
- WAS는 web.xml을 참조하여 해당 Servlet에 대한 Thread를 생성한다. (Thread Pool 이용)
- HttpServletRequest와 HttpServletResponse 객체를 생성하여 Servlet에 전달한다.
5-1. Thread는 Servlet의 service() 메서드를 호출한다.
5-2. service() 메서드는 요청에 맞게 doGet() 또는 doPost() 메서드를 호출한다.
protected doGet(HttpServletRequest request, HttpServletResponse response) - doGet() 또는 doPost() 메서드는 인자에 맞게 생성된 적절한 동적 페이지를 Response 객체에 담아 WAS에 전달한다.
- WAS는 Response 객체를 HttpResponse 형태로 바꾸어 Web Server에 전달한다.
- 생성된 Thread를 종료하고, HttpServletRequest와 HttpServletResponse 객체를 제거한다.
하나가 모든 기능을 하면 (내가)편할텐데 왜 역할을 나눈거지..? ㅎ
Web Server와 WAS를 분리하는 이유
대량의 데이터를 처리하는 경우 WAS에 부하가 걸릴 수 있어, 각각의 역할을 나누어 부하를 방지한다.
웹 서버는 정적인 컨텐츠(HTML, 이미지 등)를 빠르게 처리하고 제공하는 데 특화되어있고, WAS는 동적인 컨텐츠(비즈니스 로직, 데이터베이스 연동 등)를 처리하는 데 최적화되어 있다. 각각의 역할로 분리하면 각 서버가 맡은 역할에 맞게 최적화할 수 있다. 또, 서버를 확장하거나 로드 밸런싱을 할 때 더 유연하게 대응할 수 있다.
그래서 WAS가 동적인 처리를 구체적으로 어떻게 한다는 거야??
웹 서버 와 WAS의 처리 방식
웹 서버의 정적 콘텐츠 처리
정적 처리의 경우 사용자가 요청하는 즉시 그대로 전달되는 콘텐츠로, 사용자가 URL을 요청하면 웹 서버는 해당 파일을 찾아 클라이언트에게 HTTP 응답 형식으로 그대로 반환한다.
WAS의 동적 콘텐츠 처리
동적 콘텐츠는 서버 사이드 로직이 실행되어 결과를 생성하고, 이 결과를 클라이언트에 반환하는 형태로 서블릿, JSP, Spring, PHP 등의 기술이 있다.
요청이 웹 서버를 지나 WAS로 들어오면, WAS는 요청을 받아 서블릿이나 JSP를 실행하여 동적인 요청을 처리한다. 가장 먼저 웹 컨테이너가 URL을 분석하여 해당 요청을 처리할 서블릿을 찾는다. 서블릿은 자바로 작성된 웹 애플리케이션의 구성 요소로, 클라이언트의 동적 요청을 처리하는 역할을 하는데, 웹 컨테이너는 서블릿 매핑을 통해 요청을 어떤 서블릿이 처리할지 결정하고, 해당 서블릿을 초기화한다.
요청을 받은 서블릿은 DB 조회, 외부 API 호출 등의 다양한 동작을 수행하고 응답 객체를 사용해 동적인 객체를 생성하여 웹 서버를 통해 클라이언트로 반환하는 과정을 거친다.
JSP? 서블릿? 컨테이너? 좀 더 자세하게 말해줘~~~
JSP(JavaServer Pages) 는 HTML 코드 내에 Java 코드를 넣어 동적인 웹 페이지를 생성하는 기술이다. HTML과 Java코드의 결합으로
자바 코드는 <% %> 태그 안에 작성한다. 주로 화면 출력에 사용된다.
처음 요청이 들어오면 서블릿으로 변환되어 컴파일 된 후 서블릿처럼 실행된다.
Servlet는 서버 측에서 HTTP 요청을 처리하고 응답을 생성하는 Java 클래스이다. 서블릿은 HttpServlet 클래스를 상속받아서 구현되며, doGet(), doPost() 같은 메서드를 오버라이드하여 요청을 처리한다. 주로 비즈니스 로직을 처리하거나, 클라이언트의 요청을 처리하고 응답을 생성한다.
이 둘은 MVC 패턴에서 함께 사용되며, JSP는 뷰를, 서블릿은 컨트롤러의 역할을 한다. 서블릿이 클라이언트의 요청을 받아 처리하고 그 결과를 JSP로 전달하면, JSP는 데이터를 받아 뷰를 생성하여 사용자에게 동적인 페이지를 제공한다.
서블릿에서 데이터를 처리하고 결과를 request에 저장한 후, 해당 데이터를 JSP로 전달하여 HTML 페이지를 동적으로 생성할 수 있다.
java8에서 제공하는 Servlet Interface
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
Servlet Container
톰캣의 핵심으로, 컨테이너는 서블릿을 관리하고, 클라이언트의 요청을 처리할 적절한 서블릿을 선택해서 그 요청을 해당 서블릿에 전달하는 역할을 한다.
위의 서블릿 인터페이스에서 보이듯이
서블릿의 생명 주기는 서블릿 초기화 → 서비스 → 소멸
과정으로 이루어지는데 컨테이너는 이 과정을 관리한다.
서블릿이 처음 요청을 받았을때는 init() 메서드가 호출되고, 이후 요청에 대한 처리는 service() 메서드가 처리한다.
그리고 톰캣 서버가 종료될 때 서블릿이 destroy() 메서드를 호출하여 종료된다.
여러 웹 애플리케이션을 독립적으로 관리하고, 요청에 대해 서블리고 JSP 페이지를 반환하고 처리한다.
자 그럼 프로그램 시작 시 서블릿 컨테이너에서 일어나는 과정을 보자.
애플리케이션이 시작되면 서블릿 컨테이너는 설정된 모든 서블릿을 메모리에 미리 로드해!!! 이 과정에서 서블릿 클래스 파일들이 메몸리에 객체로 생성되서 올라가지!!! 그러면 서블릿 컨테이너는 각 서블릿을 초기화 하면서 init() 메서드를 호출하게 돼!! 이렇게 설정, 준비가 끝난 서블릿은 요청 받을 준비가 된거지
이렇게 준비된 서블릿은 서블릿 컨테이너에 올라가서, 사용자가 요청을 보내면 서블릿 컨테이너가 적절한 서블릿을 찾아서 요청을 전달하게 되는겨~!
설정은 어디서 하냐고~? 서블릿을 등록하고 매핑하는 설정 파일인 web.xml이 있어. 톰캣과 같은 서블릿 컨테이너가 이걸 읽고 서블릿을 초기화하지.
하지만 스프링 부트에서는 이런 번거로운 과정을 거치지 않아도 돼! 왜냐~ 스프링 부트에서는 DispatcherServlet 같은 기본 서블릿이 자동으로 등록되거든~ 스프링 부트가 내장 톰캣을 시작할 때, 스프링 부트 자체가 DispatcherServlet을 기본으로 등록해서 모든 요청을 처리하도록 하기 때문이야
1
2
3
4
5
6
7
8
9
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication 어노테이션이 붙은 메인 클래스를 실행하면 스프링 부트가 DispatcherServlet을 포함한 여러 기본 설정을 자동으로 등록해줘~!!
따라서, / 경로로 오는 모든 요청이 DispatcherServlet을 거쳐 가게 돼~!
이처럼 서블릿과 JSP는 동적 웹 애플리케이션에서 클라이언트의 요청을 처리하고, 응답을 생성하는 중요한 역할을 한다.
간략한 개념을 알아봤는데,
실제로는 서블릿과 JSP가 어떻게 실행될까? 바로 톰캣이 그 역할을 한다.
톰캣은 서블릿 컨테이너와 JSP 엔진을 제공하는 WAS로, 서블릿과 JSP가 동작하도록 관리하고 실행하는 환경을 제공한다.
Tomcat
톰캣은 WAS의 대표적은 미들웨어 서비스로 일반적으로 Apache Tomcat라고 불린다. 웹 서버의 미들웨어 아파치의 기능 일부를 가져와 함께 사용되면서 웹 서버의 기능과 WAS를 모두 포함한다.
Architecture
-
Context: 톰캣 내에서 단일 웹 애플리케이션
-
Connector: 클라이언트로 부터 HTTP 요청을 받고 적절한 서블릿으로 전달하는 역할을 하며, 멀티스레딩을 지원하기 때문에 여러 클라이언트의 요청을 처리할 수 있다.
-
Host: Tomcat 서버에 대한 네트워크 이름(www.myname.com)의 연결을 의미한다. 호스트는 임의의 수의 컨텍스트(응용프로그램)를 포함할 수 있고, Tomcat의 기본 구성에는 localhost라는 이름의 호스트가 포함된다.
-
Engine: 톰캣의 핵심 역할로, 서비스에는 여러 개의 커넥터가 있을 수 있는데 엔진은 이러한 커넥터로부터 모든 요청을 수신하고 처리하여 클라이언트에 전송할 적절한 커넥터로 응답을 다시 전달한다. 엔진에는 하나 이상의 호스트가 포함되어야 하며, 그중 하나가 기본 호스트로 지정되어야 한다. 기본 Tomcat 구성에는 localhost 호스트를 포함하는 Catalina 엔진이 포함된다
-
Listener: org.apache.catalina.LifecycleListener 인터페이스를 구현하여 특정 이벤트에 응답할 수 있는 Java 객체이다.
-
Realm: 모든 컨테이너 구성 요소(엔진, 호스트, 컨텍스트) 안에 나타날 수 있으며 사용자, 비밀번호, 사용자 역할의 데이터베이스를 나타낸다. 그 목적은 컨테이너 기반 인증을 지원하는 것이다.
-
Valve: 컨테이너(컨텍스트, 호스트 또는 엔진)에 삽입하면 애플리케이션에 도달하기 전에 들어오는 모든 HTTP 요청을 가로채는 인터셉터와 같은 요소ㄹ, 특정 애플리케이션, 가상 호스트에서 실행되는 애플리케이션 또는 엔진 내에서 실행되는 모든 애플리케이션으로 향하는 요청을 사전 처리한다.
커넥터는 클라이언트로부터 받은 HTTP 요청을, Container로 전달한다. Container은 해당 요청을 처리할 적절한 서블릿을 찾아 실행하고, 그 결과를 Connector을 통해 클라이언트에 돌려준다.
위에 보면 Connector는 멀티 스레딩을 지원한다고 되어 있다. 톰캣의 Connector는 멀티스레딩을 지원하여, 각 HTTP 요청을 각각이 스레드로 처리할 수 있도록 하기 때문에, 사용자가 동시에 여러 요청을 보내더라도, 톰캣은 각각의 스레드에서 처리한다. 이를 통해 여러 사용자가 동시에 시스템을 사용할 수 있는 것이다.
결과적으로, 스프링이나 스프링 부트에서는 톰캣이 멀티스레딩을 통해 다수의 요청을 관리하기 때문에, 우리는 서버 설정이나 스레드 관리에 신경 쓰지 않아도 되는 것이다.
톰캣은 서블릿을 실행하기 위해 서블릿 컨테이너를 제공한다. 이를 통해 웹 애플리케이션의 요청을 적절한 서블릿으로 전달하고, 해당 서블릿을 실행 후 받은 응다 결과를 반환한다. 또한, 톰캣은 JSP 파일을 컴파일하고 이를 서블릿으로 변환하여 클라이언트에게 동적 페이지를 제공하는 JSP 엔진도 제공한다.
따라서 서블릿이나 JSP를 실행하려면 톰캣과 같은 WAS가 필요하다. 톰캣은 이러한 요청을 처리하기 위한 모든 환경을 자동으로 설정하고 관리해 주기 때문에, 개발자는 비즈니스 로직에 집중할 수 있다.
그렇다면 스프링은 톰캣과 함께 어떻게 동작할까? 스프링 프레임워크는 톰캣을 내장형 WAS로 사용하거나, 외부 WAS와 연결하여 웹 애플리케이션을 실행할 수 있다. 특히, 스프링 부트에서는 내장 톰캣을 사용하여 별도의 서버 설치 없이 쉽게 웹 애플리케이션을 실행할 수 있다.
톰캣은 서블릿과 JSP를 실행할 수 있는 서버 환경을 제공하고, 스프링 프레임워크는 이를 MVC 패턴을 기반으로 더 효율적으로 관리한다.
스프링에서는 Model-View-Controller(MVC) 패턴을 사용해 애플리케이션의 구조를 세분화하고, 각 구성 요소가 독립적으로 동작하면서도 협력할 수 있도록 한다.
최근에는 MV2 모델로 진화하여, 뷰의 표현을 더욱 유연하게 처리하고, 서버와 클라이언트 간의 상호작용을 개선하는 방식이 되었다.
스프링과 톰캣의 동작 방식 및 구조
톰캣은 독립적인 웹 애플리케이션 서버로 별도로 설치하고 설정해야 한다. 톰캣 서버를 직접 실행하기 위해서는 web.xml과 같은 설정 파일을 작성하고, 배포할 WAR 파일을 톰캣 서버의 webapps폴더에 배포하고, 톰캣을 시작해야 한다. 그러면 톰캣은 서블릿 컨테이너로 요청을 받아 서블릿을 실행한다.
스프링에서 톰캣은 실행되기 전에 web.xml 등 여러 설정 파일을 작성하여야 하며, 서블릿을 매핑하고 초기화하는 과정이 필요하기 때문에, 처음 접하면 복잡할 수 있다.
스프링 부트에서의 톰캣 및 구조
스프링 부트는 기본적으로 스프링 프레임워크를 사용하기 때문에 스프링의 모든 기능을 포함한다. 또한 스프링을 사용할 때 필요한 기본적인 라이브러리들이 포함되어 있다.
그리고 톰캣이 내장되어 있는 구조로, 애플리케이션이 실행될 때 톰캣 서버가 자동으로 실행되기 때문에, 외부에 별도로 톰캣 서버를 설치할 필요가 없다.
스프링 부트는 “Convention Over Configuration” (COC) 접근 방식을 채택하여 개발자의 설정에 대한 부담을 줄이고 개발 속도를 높인다. 대부분의 설정을 자동으로 처리해 주기 때문에 개발자가 직접 설정할 필요가 줄기 때문이다.
스프링 부트는 자동 설정을 제공하는데, 예로 JPA나 MySQL을 사용할 때, application.properties에서 spring.datasource.url, spring.datasource.username, spring.datasource.password를 설정하면, 그 외의 설정은 자동으로 처리해준다.
그리고 스프링 프레임워크를 사용하려면 필요한 라이브러리들을 별도로 추가해야 하지만, 스프링 부트를 사용하면 이러한 라이브러리들을 자동으로 설정해 주고, 필요한 의존성을 쉽게 추가할 수 있도록 돕는 스타터(startup) 패키지가 제공돼서 훨씬 간편하다.
또한 @SpringBootApplication 어노테이션을 사용하면 자동으로 서블릿 컨테이너 설정, 서버 포트 설정, 애플리케이션 초기화 등이 자동으로 이루어진다.
이번 기회를 통해 톰캣의 동작 방식과 내부 구조에 대해 알게 된 것 같다. 톰캣이 어떻게 서블릿을 관리하고 요청을 처리하는지, 그리고 멀티스레딩을 통해 다수의 요청을 효율적으로 처리하는 구조를 이해할 수 있었다.
또한, 스프링 프로젝트가 톰캣의 멀티스레딩 기능을 활용해 여러 사용자 요청을 동시에 처리한다는 것을 알게 되었다. 앞으로는 스프링 프로젝트에서의 요청 흐름과 다중 사용자 지원 방식을 더 잘 활용할 수 있을 것 같다.