개요
이전에는 그냥 개발만 하고, 컨트롤러, 서비스, JPA와 같은 것만 다루다 보니까 어떤 흐름인지 느낌적으로만 알았는데 이전에 순수 자바로 스프링을 구현하는 과정에서 순수 톰캣을 다뤄봤었다. 해당 웹 서비스를 제작할 때, JSP와 서블릿, DB Handler를 통해서 구현했었는데, 왜 스프링 부트를 사용하지 않고 톰캣을 쓰지?시간을 낭비하는건 아닌가?라는 생각이 들었었다. 주변의 지인들도 톰캣과 서블릿을 사용해서 프로젝트를 하고있다고 하니까 다들 기겁하던 모습이 아직도 선명하다...
하지만, 최근에 스프링과 스프링부트에 대한 깊은 공부를 하게 되면서, 왜 서블릿을 통해서 과제를 진행했는지 알게되는 것같다. 이번 포스팅에서는 외부에서 Http 요청을 통해 API를 호출했을 때, 스프링 부트 내부에서 이뤄지는 흐름과 과정에 대해서 정리해보면서 그 동안의 깨달음을 정리하고자 한다.
1. Tomcat
1-1. Tomcat의 개요
Tomcat은 Apache Software Foundation에서 개발한 오픈 소스 Servlet Container이자 웹 서버입니다.
Tomcat은 Servlet과 JSP(Javer Server Pages)를 실행할 수 있는 환경을 제공합니다. 또한, Java EE 사양을 일부 구현하여 서블릿과 JSP 기반의 웹 애플리케이션을 호스팅하고 실행할 수 있습니다.
1-2. Tomcat의 역할
Tomcat은 여러가지의 역할을 수행할 수 있지만, 크게 서블릿 컨테이너와 웹 서버를 얘기할 수 있습니다.
톰캣은 서블릿의 생명주기( 서블릿 생성, 초기화, 서비스, 소멸)를 관리하고 요청과 응답의 관리, 세션 관리등의 기능을 수행합니다.
또한 일종의 웹 서버로 HTTP 요청을 처리하고 정적지원(HTML, CSS, 이미지)를 서빙할 수 있습니다.
1-3. Tomcat의 특징
톰캣은 다른 Java EE에 비해서 굉장히 가볍고 빠르다는게 가장 큰 특징이라고 할 수 있습니다. 또한 서버를 구동하기 위한 설정이 용이하고 다양한 구성 옵션을 제공하여 필요에 따라 커스터마이징이 가능합니다. 다른 자바EE 기반 WAS보다 훨씬 큰 커뮤니티가 존재합니다. 별 것아닐 수 있다고도 생가할 수 있지만, 개발자라면 누구나 에러와 트러블 슈팅을 만나게 될 텐데, GPT를 쓸 수 도 있지만, 커뮤니티를 잘 찾아본다면 내가 지금 겪는 에러를 누군가가 해결한 기록이 남아있어 수월하게 오류를 처리할 수 있습니다.
1-4. Spring에서의 Tomcat
Spring에서 사용하는 WAS로는 기본적으로 Tomcat과, Jetty, Undertow를 사용할 수 있습니다. 이 외에도 다른 WAS가 있는 지는 모르겠지만 이 중에서 Tomcat을 가장 많이 사용합니다. Spring에서는 Tomcat을 다운받아서 연결해줘야 하지만, Spring Boot에서는 starter-web에 내장되어 있습니다.
2. Servlet
2-1. Servlet의 개념
Servlet은 동적 웹 페이지를 만들 때 사용하는 자바 기반의 웹 애플리케이션 프로그래밍 기술입니다.
Servlet은 서버에서 실행되다가 웹 브라우저에서 요청하면 해당 기능르 수행한 후 웹 브라우저에 결과를 전달합니다.
쉽게 생각해서 동적으로 요청을 처리해주는 친구라고 생각하면 될 것같습니다.
2-2. Servlet 경험
최근에는 Spring과 Spring Boot만 배우고 API를 개발하지만, 어떻게 요청이 해당 컨트롤러의 메서드에 전달되는지는 잘 모르는 것같습니다. 물론 저도 이전까지는 모르다가 약간의 강제로 인해서 서블렛을 접했습니다.
@WebServlet(name = "BookmarkManager", value = "/bookmark-manager")
public class BookmarkManagerServlet extends HttpServlet {
DBHandler db;
public void init() {
db = new DBHandler();
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
LinkedList<CustomBookmark> bookmarks = db.getBookmarkData();
request.setAttribute("bookmarks", bookmarks);
request.getRequestDispatcher("/bookmark-manager.jsp").forward(request, response);
}
}
다소 예전에 개발했던 코드지만, 이 코드를 보면 @WebServlet 어노테이션을 통해 서블릿의 이름과 경로를 정의하는 모습을 볼 수 있습니다. 이 코드에서는 BookmarkManager라는 서블릿으로 등록하고 /bookmark-manager 경로에 들어오는 요청을 처리합니다.
서블릿의 생명주기를 보면, 프로그램이 실행되면 서버에서 서블릿이 생성되고 초기화(init) 과정을 거칩니다.
이후 해당 경로로 들어오는 요청이 있을 때마다 doGet과 같은 메서드가 호출됩니다. 프로그램이 종료되면 서블릿은 소멸(destroy)됩니다.
이 코드에서 가장 중요한 부분은 doGet 메서드의 매개변수로 들어오는 HttpServletRequest request와 HttpServletResponse response입니다. 이 두 매개변수는 클라이언트가 /bookmark-manager 경로로 요청할 때 브라우저에서 전달되는 데이터를 포함합니다. 이를 통해 서버는 URL 상의 매개변수와 값을 파악하고, 해당하는 JSP를 반환할 수 있습니다.
- HttpServletRequest: 이 객체는 URL의 Path Variable과 Parameter를 가져올 수 있습니다. 예를 들어, request.getParameter("paramName")을 통해 URL에 포함된 파라미터 값을 가져올 수 있습니다.
- HttpServletResponse: 이 객체는 서버에서 클라이언트로의 응답을 담당합니다. doGet 메서드에서 처리된 데이터를 request.setAttribute로 설정하고, 이를 JSP에 전달하여 최종 결과를 사용자에게 보여줍니다.
이렇게 하면 동적으로 결과를 제공할 수 있으며, 이전에 언급한 동적인 웹 애플리케이션의 개념을 더 잘 이해할 수 있습니다.
이전에 Servlet과 JSP를 쓸 때는 이걸 왜 하지라는 생각이 강했지만, 지금보면 스프링과 스프링 부트를 이해하기 위해서 반드시 필요한 개념을 배운 것같습니다.
3. Spring & Spring Boot
3-1. Spring
자바로 개발하는 웹 서비스와 웹 애플리케이션은 Spring의 등장 전과 후로 나뉠 정도로 크게 다릅니다. 기존의 EJB(Enterprise JavaBean)를 대체하면서 많은 코드들이 필요 없어지고, 복잡도가 크게 줄어들었습니다. Spring은 개발자가 개발에만 집중할 수 있도록 DI(Dependency Injection)와 IoC(Inversion Of Control) 등의 개념을 도입했습니다. 이는 SOLID 원칙을 따르는 설계를 쉽게 구현할 수 있도록 도와줍니다.
DI(Dependency Injection):
- 객체 간의 의존성을 프레임워크가 주입해주는 방식으로, 개발자가 의존성을 직접 생성하고 관리할 필요가 없습니다. 이를 통해 코드의 결합도가 낮아지고, 테스트가 용이해집니다.
IoC(Inversion Of Control):
- 제어의 역전으로, 객체의 생성 및 생명주기 관리를 개발자가 아닌 프레임워크가 담당합니다. 이는 객체 간의 의존성을 더욱 효율적으로 관리하고, 확장성을 높여줍니다.
Spring을 사용하면 각 코드가 자신의 역할만 수행하게 되면서 확장성 있는 코드를 작성할 수 있습니다. 또한, 프레임워크가 객체를 관리함으로써 개발자는 비즈니스 로직에 더 집중할 수 있게 됩니다. 이러한 이유로 Java를 통한 웹 개발에 봄(Spring)이 찾아왔다는 의미에서 'Spring'이라는 이름이 지어졌습니다.
3-2. Spring Boot
Spring은 많은 장점이 있지만, 그럼에도 불구하고 단점도 존재했습니다. 기존의 EJB에 비해서 설정할 내용이 많이 줄어들기는 했지만, 여전히 엄청나게 많은 설정을 해야 했습니다. 또한, WAS(Web Application Server)를 사용하기 위해 별도로 WAS를 설치하고, 그에 대한 설정을 추가해야 했습니다. 하나의 Dependency가 추가될 때마다 그에 관한 설정을 진행해야 했기 때문에 개발자는 설정 작업에 많은 시간을 소비해야 했습니다.
이러한 복잡성과 불편함을 해소하기 위해 Spring Boot가 탄생했습니다. Spring Boot는 Spring 애플리케이션 개발을 더욱 간편하게 하고, 설정 작업을 최소화하기 위해 만들어졌습니다. Spring Boot는 다음과 같은 주요 특징을 가지고 있습니다.
자동 설정(Auto-Configuration):
- Spring Boot는 애플리케이션 실행 시 필요한 설정을 자동으로 구성합니다. 예를 들어, 데이터베이스 연결 설정, 웹 서버 설정 등을 자동으로 처리하여 개발자가 별도로 설정할 필요가 없습니다. 이를 통해 개발자는 비즈니스 로직에만 집중할 수 있습니다.
내장 WAS(Embedded Web Server):
- Spring Boot는 Tomcat, Jetty, Undertow와 같은 내장 웹 서버를 제공합니다. 개발자는 별도로 WAS를 설치하지 않고도 애플리케이션을 실행할 수 있습니다. 이는 독립 실행형 애플리케이션을 쉽게 배포하고 실행할 수 있게 합니다. 기본 값으로는 Tomcat을 사용하고 있습니다.
독립 실행형 애플리케이션:
- Spring Boot는 JAR 파일로 패키징된 독립 실행형 애플리케이션을 만들 수 있습니다. 이는 복잡한 배포 과정 없이 쉽게 애플리케이션을 실행하고 배포할 수 있게 합니다. JAR 파일을 실행하면 내장된 WAS가 함께 실행되어 애플리케이션이 동작합니다.
Spring Boot Starter:
- Spring Boot는 다양한 Starter를 제공하여 필요한 의존성을 쉽게 추가할 수 있습니다. 예를 들어, spring-boot-starter-web을 사용하면 웹 애플리케이션 개발에 필요한 모든 의존성과 설정이 자동으로 추가됩니다. 이를 통해 개발자는 필요한 기능을 손쉽게 통합할 수 있습니다.
운영 환경 지원:
- Spring Boot는 Actuator를 통해 애플리케이션의 모니터링 및 관리 기능을 제공합니다. Actuator는 애플리케이션의 상태, 성능, 메트릭 등을 모니터링할 수 있는 엔드포인트를 제공합니다. 이를 통해 운영 환경에서 애플리케이션을 효과적으로 관리할 수 있습니다.
- Prometheus같은 Metrics 수집 툴을 사용하고 Grafana와 같은 시각화 툴을 사용해서 원격으로 서버의 상태를 모니터링 할 수 있습니다.
4. Dispatcher Servlet
Spring MVC의 핵심 구성 요소 중 하나인 DispatcherServlet은 모든 HTTP 요청을 중앙에서 처리하는 프론트 컨트롤러입니다. DispatcherServlet은 클라이언트의 요청을 받아서 적절한 핸들러(컨트롤러)로 전달하고, 처리된 결과를 다시 클라이언트에게 응답하는 역할을 합니다. 이를 통해 Spring MVC 애플리케이션의 전반적인 요청 처리 흐름을 관리합니다.
4-1. DispatcherServlet의 역할과 기능
요청 수신 및 분배:
- DispatcherServlet은 클라이언트로부터 들어오는 모든 HTTP 요청을 받아서 처리합니다. 이를 통해 애플리케이션의 중앙 진입점 역할을 합니다.
핸들러 매핑(Handler Mapping):
- DispatcherServlet은 요청 URL, HTTP 메서드(GET, POST 등), 요청 헤더 등의 정보를 바탕으로 어떤 컨트롤러의 어떤 메서드가 요청을 처리할지 결정합니다. 이를 위해 다양한 HandlerMapping 전략을 사용합니다. 대표적인 예로는 @RequestMapping 애노테이션이 있습니다.
핸들러 어댑터(Handler Adapter):
- 매핑된 핸들러를 실행하기 위해 HandlerAdapter를 사용합니다. HandlerAdapter는 실제로 핸들러 메서드를 호출하여 요청을 처리합니다.
뷰 리졸버(View Resolver):
- 핸들러가 요청을 처리한 후, 결과를 어떤 뷰(View)로 렌더링할지 결정합니다. ViewResolver는 논리적 뷰 이름을 실제 뷰로 변환하여 렌더링합니다. 대표적인 뷰 기술로는 JSP, Thymeleaf, FreeMarker 등이 있습니다.
응답 반환:
- 최종적으로 렌더링된 뷰나 데이터를 HTTP 응답으로 클라이언트에게 반환합니다. 이를 통해 클라이언트는 요청에 대한 결과를 확인할 수 있습니다.
4-2. DispatcherServlet의 동작 과정
DispatcherServlet의 동작 과정을 단계별로 설명하면 다음과 같습니다:
- 요청 수신:
- 클라이언트가 HTTP 요청을 보내면, 이 요청은 먼저 DispatcherServlet에 도달합니다.
- 핸들러 매핑(Handler Mapping):
- DispatcherServlet은 요청 URL과 HTTP 메서드를 바탕으로 어떤 컨트롤러와 메서드가 이 요청을 처리할지 결정합니다.
- 이를 위해 @RequestMapping 애노테이션과 같은 매핑 정보를 사용합니다.
- 핸들러 어댑터(Handler Adapter):
- 매핑된 핸들러를 실행하기 위해 적절한 HandlerAdapter를 찾습니다.
- HandlerAdapter는 핸들러 메서드를 호출하여 비즈니스 로직을 실행합니다.
- 핸들러 실행:
- 매핑된 컨트롤러의 메서드가 실행되어 요청을 처리하고, 결과를 반환합니다.
- 이 결과는 주로 ModelAndView 객체로 표현됩니다.
- 뷰 리졸버(View Resolver):
- ModelAndView 객체에 포함된 논리적 뷰 이름을 실제 뷰(View)로 변환합니다.
- ViewResolver는 이 논리적 이름을 사용하여 JSP, Thymeleaf, FreeMarker 등의 뷰 템플릿을 결정합니다.
- 뷰 렌더링(View Rendering):
- 결정된 뷰 템플릿을 사용하여 모델 데이터를 HTML 등의 형식으로 렌더링합니다.
- 렌더링된 결과는 DispatcherServlet에 의해 HTTP 응답으로 반환됩니다.
- 응답 반환:
- 최종적으로 렌더링된 뷰나 데이터를 HTTP 응답으로 클라이언트에게 반환합니다.
- 클라이언트는 요청에 대한 결과를 확인할 수 있습니다.
DispatcherServlet은 이러한 일련의 과정을 통해 Spring MVC 애플리케이션의 요청 처리 흐름을 관리하고, 효율적으로 클라이언트의 요청을 처리합니다. 이를 통해 애플리케이션의 구조가 명확해지고, 유지보수가 용이해집니다.
정리
최근에 과제와 코딩 테스트, 책을 읽느라 바빠서 과제를 통해서 깨달은 부분들을 정리하는 것을 많이 못했었는데, 이렇게 글로써 한 번 정리하니까 진짜 내 지식이 된 것 같다. 공부하는 양에 비해서 정리하는 시간이 아직은 많이 부족한 것같다.
원래 해당 포스팅에서 이 내용 + API를 처리하는 모든 과정에 대해서 다룰 예정이였는데, 분량상 한 번에 보기 힘들 것 같아서 다음 포스팅에서 작성할 예정이다.
'Backend > Framework' 카테고리의 다른 글
[Spring Boot - Trouble Shooting] Bean 타입이 달라도 Bean 주입이 안 될 수 있다 (1) | 2024.10.24 |
---|---|
[Spring Boot] Spring Boot의 API 요청 처리 흐름 완벽 해부 (0) | 2024.07.10 |
[Springboot] AWS S3 with Spring Boot3 (1) | 2024.04.29 |
[Springboot] 로그인 구현 & JWT (2) - 로그인 구현 (1) | 2023.11.15 |
[Springboot] 로그인 구현 & JWT (1) - JWT 개념 (0) | 2023.11.09 |