서론
개발을 하다 보면 빈(bean)과 의존성 주입, 그리고 여러 클라이언트 요청을 처리하는 과정에서 혼란스러울 수 있습니다. 특히, 여러 사용자의 요청이 하나의 빈으로 처리되는 방식과 그 이유가 궁금해질 수 있습니다. 이 포스팅에서는 이러한 궁금증들을 해결하기 위해, 빈이 어떻게 관리되고 여러 요청을 처리하는지 그 흐름에 대해 작성하고자 합니다.
빈과 의존성 주입의 기본 개념
빈(bean)이란 Spring 컨테이너에서 관리하는 객체를 말합니다. Spring 프레임워크는 애플리케이션에서 사용할 객체들을 빈으로 등록하고, 이들을 관리합니다. 빈은 기본적으로 싱글톤으로 관리되며, 이는 애플리케이션 내에서 해당 빈의 인스턴스가 단 하나만 존재한다는 뜻입니다. 이러한 구조는 메모리 사용을 최적화하고 성능을 높이는 데 유리합니다.
의존성 주입(Dependency Injection, DI)은 애플리케이션의 객체들이 필요로 하는 다른 객체(의존성)를 직접 생성하지 않고, Spring이 주입해주는 방식입니다. 이를 통해 객체 간 결합도를 낮추고 코드의 유연성을 높일 수 있습니다.
public class UserService {
private final UserRepository userRepository;
// 직접 생성
public UserService() {
this.userRepository = new UserRepository();
}
public String getUserName() {
return userRepository.findUserName();
}
}
UserService가 UserRepository를 직접 생성한다면, UserRepository의 변경이 UserService에 직접적인 영향을 미치게 됩니다.
위와 같은 방식은 UserService가 항상 UserRepository를 직접 생성해야 하기 때문에, 새로운 타입의 UserRepository를 사용할 때 UserService도 수정이 필요합니다. 이로 인해 결합도가 높아지고 유지보수가 어렵게 됩니다.
// Service 클래스 - 비즈니스 로직을 담당하는 클래스
@Service
public class UserService {
private final UserRepository userRepository;
// 생성자 주입을 통해 UserRepository 의존성을 주입
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getUserName() {
return userRepository.findUserName();
}
}
그래서 위와 같이 의존성 주입을 사용하면 UserService가 UserRepository의 구체적인 구현에 의존하지 않게 됩니다. 새로운 UserRepository 구현체가 추가되더라도 UserService는 변경되지 않아, 유지보수가 더 쉬워집니다.
애플리케이션의 작동 흐름
1. 빈설정
- 스프링 애플리케이션이 실행되면 ApplicationContext가 생성되고, 빈들이 스캔되고 초기화됩니다. 이때, 기본적으로 빈은 싱글톤으로 관리되어 하나의 인스턴스만 여러 요청을 처리할 수 있습니다.
- ApplicationContext는 스프링 IoC 컨테이너로서 애플리케이션 내 객체들의 생명주기를 관리하고, 객체 간의 의존성을 해결해줍니다.
- 이후, 톰캣 서버가 실행되어 클라이언트의 요청을 기다립니다.
2. 요청 처리
- 클라이언트가 웹 애플리케이션에 요청을 보내면, 해당 요청은 스프링의 DispatcherServlet에 의해 처리됩니다. DispatcherServlet은 요청을 적절한 컨트롤러에 전달하는 역할을 합니다.
- 컨트롤러는 싱글톤으로 생성된 빈이므로, 여러 클라이언트 요청을 동일한 컨트롤러 인스턴스가 처리합니다. 이는 메모리 효율성을 높이는 중요한 특징입니다.
- 컨트롤러가 비즈니스 로직을 처리하기 위해 서비스 빈을 호출하며, 서비스에서 실제 데이터는 리포지토리를 통해 데이터베이스에서 가져옵니다.
DispatcherServlet가 무엇일까?
DispatcherServlet은 Spring MVC 프레임워크의 핵심 요소로, 클라이언트 요청을 처리하고 적절한 컨트롤러에 전달하는 역할을 하는 프론트 컨트롤러입니다. 웹 애플리케이션에서 들어오는 HTTP 요청을 받아서 그 요청을 어떤 컨트롤러(controller)가 처리할지를 결정하고, 그 결과를 뷰(view)에 전달하여 응답을 생성합니다.
3. 응답
- 비즈니스 로직이 처리된 후, 컨트롤러는 다시 서블릿을 통해 클라이언트에게 데이터를 응답합니다. 이 응답은 일반적으로 JSON 형식으로 클라이언트에게 전달됩니다.
싱글톤 빈이 여러 요청을 처리하는 이유
// Stateless Service - 상태를 유지하지 않는 서비스
@Service
public class StatelessService {
// 상태를 저장하지 않고 요청마다 처리
public int process(int value) {
return value * 2; // 간단한 예시로 입력값을 두 배로 만드는 처리
}
}
Spring 프레임워크에서 빈(bean)은 기본적으로 싱글톤(Singleton)으로 관리됩니다. 이는 애플리케이션 내에서 단 하나의 인스턴스만 생성되고, 해당 인스턴스가 모든 요청에 대해 재사용된다는 것을 의미합니다. 이러한 구조는 메모리 사용을 최적화하고 성능을 향상시키는 데 중요한 역할을 합니다. 싱글톤 빈이 여러 요청을 처리할 수 있는 이유는 그 자체가 상태를 유지하지 않는 객체이기 때문입니다.
각 요청에 대해 고유한 데이터나 상태를 유지하지 않기 때문에, 여러 요청에 대해서도 동일한 방식으로 처리할 수 있습니다. 즉, 각 요청마다 새로운 상태를 저장하거나 관리하지 않고, 입력으로 주어진 데이터를 처리하여 결과를 반환하는 형태입니다. 이러한 방식의 로직은 비즈니스 계층(Service)에서 주로 많이 사용되며, 여러 클라이언트의 요청을 동시에 처리하더라도 데이터를 공유하지 않으므로 안전하게 재사용할 수 있습니다.
// Stateful Service - 상태를 유지하는 서비스
@Service
public class StatefulService {
// 상태를 유지하는 필드
private int state;
// 상태를 변경하는 메소드
public void saveState(int value) {
this.state = value; // 상태를 저장
}
// 현재 상태를 반환하는 메소드
public int getState() {
return this.state; // 저장된 상태를 반환
}
}
반면, 상태를 유지하는 객체의 경우, 여러 요청이 동일한 인스턴스에 접근할 때 데이터 충돌이 발생할 수 있습니다. 예를 들어, 각 사용자의 로그인 정보나 세션 데이터가 한 인스턴스에 저장되면, 한 사용자의 상태가 다른 사용자에게 영향을 미치는 문제가 발생할 수 있습니다. 만약 요청마다 또는 세션마다 서로 다른 상태를 유지해야 하는 경우라면, 다음과 같은 스코프를 고려해야 합니다.
// Stateful Service - 상태를 유지하는 요청 범위 서비스
@Service
@RequestScope // 각 HTTP 요청마다 새로운 인스턴스 생성
public class StatefulService {
private int state;
public void saveState(int value) {
this.state = value; // 상태를 저장
}
public int getState() {
return this.state; // 저장된 상태를 반환
}
}
- @RequestScope: 각 HTTP 요청마다 새로운 빈 인스턴스를 생성하여 요청마다 독립된 상태를 유지합니다.
- @SessionScope: 각 사용자의 세션마다 빈 인스턴스를 생성하여 세션 간 상태를 독립적으로 유지합니다.
마치며
Spring의 빈은 기본적으로 싱글톤으로 관리되며, 이를 통해 메모리 효율성과 성능을 높일 수 있습니다. 다만, 상태를 유지해야 하는 경우에는 싱글톤 빈 대신 @RequestScope나 @SessionScope를 사용하여 요청별 또는 세션별로 독립된 객체를 생성할 수 있습니다. 이러한 빈과 의존성 주입의 작동 원리를 이해하면, 애플리케이션 설계 시 결합도를 낮추고 유지보수를 쉽게 할 수 있습니다.
이 글을 통해 Spring의 빈과 의존성 주입, 그리고 요청 처리의 흐름을 이해하고, 실무에서 이러한 개념들을 어떻게 적용할 수 있는지 고민할 수 있기를 바랍니다.
'Backend Programming' 카테고리의 다른 글
JPA 영속성 컨텍스트 (3) | 2024.11.01 |
---|---|
HTTP란? (0) | 2024.10.05 |
인터넷의 작동 원리 (0) | 2024.10.04 |
HTTPS에 대해서 (0) | 2024.09.29 |
JWT을 사용하여 로그인 서비스 만들기 (2) | 2024.09.22 |