톰캣(Tomcat)이란?
아파치 톰캣(Apache Tomcat)은 자바 서블릿과 JSP(JavaServer Pages)를 실행할 수 있는 서블릿 컨테이너이자 웹 서버입니다. 톰캣은 HTTP 프로토콜을 통해 클라이언트의 요청을 받고, 서블릿 규격에 따라 자바 웹 애플리케이션을 실행하여 응답을 반환합니다.
스프링과 톰캣의 관계
스프링 프레임워크는 자체적으로 웹 서버 기능을 제공하지 않습니다. 대신, 서블릿 컨테이너인 톰캣 위에서 동작하여 웹 애플리케이션을 구동합니다. 스프링 애플리케이션은 톰캣과 같은 서블릿 컨테이너에 배포되어, 클라이언트의 HTTP 요청을 처리합니다.
동작 원리 상세 설명
1. 서블릿 컨테이너와 서블릿
• 서블릿 컨테이너는 서블릿의 생명주기(Lifecycle)를 관리하고, 클라이언트의 요청과 서블릿 간의 통신을 중재합니다.
• 서블릿은 자바를 기반으로 한 서버 측 컴포넌트로, HTTP 요청을 처리하고 응답을 생성합니다.
2. 스프링 MVC와 DispatcherServlet
• DispatcherServlet은 스프링 MVC의 핵심 서블릿으로, 서블릿 컨테이너에 등록되어 모든 HTTP 요청을 처리합니다.
• 스프링 애플리케이션을 톰캣에 배포하면, DispatcherServlet이 초기화되고, 서블릿 컨테이너는 이 서블릿을 통해 요청을 처리합니다.
3. 요청 처리 흐름
1. 클라이언트 요청: 브라우저나 다른 HTTP 클라이언트가 특정 URL로 요청을 보냅니다.
2. 톰캣의 요청 수신:
• 톰캣은 HTTP 요청을 수신하고, 해당 요청을 처리할 서블릿을 결정합니다.
• 이 매핑은 web.xml 파일이나 어노테이션 기반 설정으로 정의됩니다.
3. DispatcherServlet으로 요청 전달:
• 요청 URL 패턴이 DispatcherServlet에 매핑되어 있으므로, 톰캣은 이 서블릿의 service() 메서드를 호출합니다.
4. HandlerMapping을 통한 핸들러 탐색:
• DispatcherServlet은 요청 정보를 기반으로 적절한 컨트롤러의 메서드(핸들러)를 찾기 위해 HandlerMapping을 사용합니다.
5. 핸들러 어댑터를 통한 메서드 호출:
• 핸들러 어댑터는 찾은 컨트롤러의 메서드를 실행하고, 결과를 반환합니다.
6. 뷰 리졸버를 통한 뷰 결정:
• 컨트롤러 메서드의 반환 값을 기반으로 어떤 뷰를 사용할지 결정합니다.
• RESTful 서비스의 경우, @ResponseBody나 ResponseEntity를 사용하여 직접 응답을 반환합니다.
7. 응답 반환:
• DispatcherServlet은 생성된 응답을 서블릿 컨테이너인 톰캣에게 전달하고, 톰캣은 이를 클라이언트에게 반환합니다.
4. 서블릿 생명주기와 스프링
• 초기화 단계 (init):
• 톰캣이 시작될 때, 설정된 서블릿들을 초기화합니다.
• DispatcherServlet의 init() 메서드가 호출되어 스프링 애플리케이션 컨텍스트가 생성되고, 빈들이 초기화됩니다.
• 서비스 단계 (service):
• 클라이언트의 요청이 들어올 때마다 서블릿의 service() 메서드가 호출됩니다.
• 이 메서드는 HTTP 메서드(GET, POST 등)에 따라 doGet(), doPost() 등을 호출합니다.
• 소멸 단계 (destroy):
• 톰캣이 종료되거나 서블릿이 언로드될 때, destroy() 메서드가 호출되어 리소스를 해제합니다.
5. 스프링 부트와 내장 톰캣
• 스프링 부트는 애플리케이션을 쉽게 실행할 수 있도록 내장 톰캣 서버를 제공합니다.
• spring-boot-starter-web 의존성을 사용하면 별도의 외부 톰캣 설치 없이도 애플리케이션을 실행할 수 있습니다.
• 내장 톰캣은 SpringApplication.run() 메서드를 호출할 때 함께 시작됩니다.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args); // 내장 톰캣 시작
}
}
6. 필터와 인터셉터
• 필터(Filter):
• 서블릿 컨테이너 수준에서 요청과 응답을 변형하거나 로그를 남길 수 있습니다.
• 톰캣은 요청을 DispatcherServlet에 전달하기 전에 필터 체인을 통해 요청을 처리합니다.
• 인터셉터(Interceptor):
• 스프링 MVC에서 제공하는 기능으로, 컨트롤러의 메서드 호출 전후에 추가 로직을 수행할 수 있습니다.
• HandlerInterceptor를 구현하여 사용합니다.
7. 연결 과정 심화
• Connector:
• 톰캣은 HTTP 요청을 수신하기 위해 Connector를 사용합니다.
• 기본적으로 8080 포트에서 HTTP Connector를 통해 요청을 받습니다.
• Engine과 Host:
• 톰캣의 내부 구조로, 여러 개의 호스트와 애플리케이션을 관리합니다.
• 각 애플리케이션은 컨텍스트(Context)로 구분됩니다.
• Context:
• 하나의 웹 애플리케이션을 나타내며, 스프링 애플리케이션은 이 컨텍스트 내에서 동작합니다.
• context.xml 파일이나 톰캣 설정을 통해 컨텍스트를 구성합니다.
코드로 보는 상세한 동작
web.xml을 통한 서블릿 매핑 (전통적인 방식)
<web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
• 설명:
• dispatcher라는 이름의 서블릿을 등록하고, 모든 URL 패턴에 대해 이 서블릿이 처리하도록 매핑합니다.
• 톰캣은 이 설정을 읽고, DispatcherServlet을 초기화합니다.
어노테이션 기반 설정 (스프링 부트에서 주로 사용)
• 서블릿 컨테이너 설정이 자동으로 이루어지며, 개발자는 어노테이션을 통해 컨트롤러와 요청 매핑을 정의합니다.
@RestController
@RequestMapping("/users")
public class UserController {
// ...
}
• 설명:
• @RestController와 @RequestMapping을 사용하여 /users 경로에 대한 요청을 이 컨트롤러가 처리하도록 합니다.
• 내부적으로는 HandlerMapping이 이 정보를 사용하여 요청을 적절한 메서드로 매핑합니다.
톰캣의 스레드 처리
• 요청당 스레드 할당:
• 톰캣은 각 요청에 대해 스레드를 할당하여 동시 처리를 가능하게 합니다.
• 스레드 풀을 사용하여 성능을 최적화합니다.
• 스레드 안전성:
• 서블릿과 컨트롤러는 멀티스레드 환경에서 동작하므로, 인스턴스 변수 사용에 주의해야 합니다.
• 스프링 빈은 기본적으로 싱글톤이므로 상태를 가지는 변수를 사용하면 안 됩니다.
예외 처리 심화
• 서블릿 컨테이너 레벨의 예외 처리:
• 톰캣은 서블릿에서 처리되지 않은 예외를 받아서 기본 에러 페이지를 제공합니다.
• 스프링의 예외 처리:
• @ControllerAdvice와 @ExceptionHandler를 통해 전역 예외 처리를 구현하여 사용자에게 의미 있는 에러 메시지를 제공할 수 있습니다.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found");
}
}
톰캣의 리소스 관리와 스프링
• 커넥션 풀:
• 데이터베이스 연결을 효율적으로 관리하기 위해 커넥션 풀을 사용합니다.
• 톰캣의 context.xml 파일에서 데이터소스를 정의하고, 스프링에서 JNDI를 통해 이를 참조할 수 있습니다.
<Context>
<Resource name="jdbc/MyDB" auth="Container" type="javax.sql.DataSource"
maxTotal="100" maxIdle="30" maxWaitMillis="10000"
username="dbuser" password="dbpassword" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mydb"/>
</Context>
• 스프링에서 JNDI 데이터소스 사용:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() throws NamingException {
return (DataSource) new JndiTemplate().lookup("java:comp/env/jdbc/MyDB");
}
}
종합적인 요청 처리 흐름 예시
1. 클라이언트가 http://localhost:8080/users/1로 GET 요청을 보냅니다.
2. 톰캣의 HTTP Connector가 요청을 수신합니다.
3. 톰캣은 URL 패턴에 따라 DispatcherServlet에 요청을 전달합니다.
4. DispatcherServlet은 HandlerMapping을 사용하여 /users/{id}에 매핑된 컨트롤러 메서드를 찾습니다.
5. HandlerAdapter가 컨트롤러의 getUser() 메서드를 호출합니다.
6. UserService를 통해 사용자 정보를 조회합니다.
7. 결과를 ResponseEntity로 반환하고, DispatcherServlet은 이를 HttpServletResponse에 작성합니다.
8. 톰캣은 응답을 클라이언트에게 반환합니다.
마무리
톰캣은 스프링 애플리케이션이 동작하는 기반을 제공하는 서블릿 컨테이너입니다. 스프링은 DispatcherServlet을 통해 톰캣과 상호 작용하며, 클라이언트의 요청을 처리하고 응답을 생성합니다. 스프링 부트는 내장 톰캣을 사용하여 개발자가 톰캣 설정에 신경 쓰지 않고도 빠르게 애플리케이션을 개발하고 실행할 수 있게 합니다.
이러한 톰캣과 스프링의 연동을 깊게 이해함으로써, 성능 최적화, 보안 설정, 리소스 관리 등 다양한 측면에서 애플리케이션을 효과적으로 개발하고 운영할 수 있습니다.
'spring' 카테고리의 다른 글
[Spring] CustomError 작성법 (0) | 2024.10.20 |
---|---|
[Spring] 주요 Annotation (3) | 2024.10.19 |
[Spring] Bean (1) | 2024.10.12 |
[Spring] DTO (0) | 2024.10.04 |
[Spring] Lombok (2) | 2024.09.28 |