스프링 빈의 요소와 고려 사항을 알아본다.
1. 빈의 요소
1-1. Bean Class
스프링 컨테이너가 빈을 생성할 때 참고하는 실제 클래스 타입을 의미한다.

빈 인스턴스 생성 이전에 BeanDefinition에 Class<?> 타입으로 setting된다.

리플렉션을 통한 클래스 로딩 후 Class 객체를 얻어올 때는 부팅이 느려질 수 있다.(클래스 로딩 등 이유)
때문에, ASM 바이트 코드 스캔을 통해 먼저 Bean Definition을 만들고, 이후 빈 인스턴스 생성 시 위의 resolveBeanClass()가 수행된다.
한마디로, 빈 객체 생성 시 필요한 필드이다.
1-2. Scope
빈 스코프는 빈이 생성되고 유지되는 범위를 의미한다.
1-2-0. 스코프는 왜 만들었을까?
스프링 컨테이너가 기본적으로 Singleton을 쓰는 이유는 메모리 절약을 위함이다.
하지만, 실제 서비스에서는 항상 하나의 인스턴스만 사용하면 안 되는 객체들 또한 존재한다.
그렇다고, new로 생성해서 사용하기에는 스프링의 기능을 붙이고 싶은 객체가 존재할 수 있다.
때문에,
하나의 인스턴스만 사용하지 않고, 스프링의 기능을 붙이고 싶은 객체를 위한 스코프들을 만들었다.
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionUser {
private Long userId;
private String email;
private String nickname;
public void login(Long userId, String email, String nickname) {
this.userId = userId;
this.email = email;
this.nickname = nickname;
}
public void logout() {
this.userId = null;
this.email = null;
this.nickname = null;
}
public boolean isLoggedIn() {
return this.userId != null;
}
}
============================================================================================
/**
* 로그인 시 sessionUser에 값 주입
* 만약, 세션 만료 시 해당 빈은 삭제되어 null이 될 것이다.
*/
@Service
public class AuthService {
private final SessionUser sessionUser;
public AuthService(SessionUser sessionUser) {
this.sessionUser = sessionUser;
}
public void login(String email, String password) {
User user = userRepository.findByEmail(email);
validate(password, user.getPassword());
sessionUser.login(user.getId(), user.getEmail(), user.getNickname());
}
}
위의 클래스를 보면, SESSION 스코프를 사용하고 있음을 볼 수 있다.
파라미터로 넘기거나 Util을 사용하지 않고, 의존주입 받아 사용하는 것을 볼 수 있다.

스코프들은 위와 같은 생성단계를 거친다.
1-2-1. Singleton(기본)
어플리케이션과 같은 생명주기
기본 스코프이다.
애플리케이션 전체에서 딱 1개의 인스턴스만 유지한다.
즉, 모든 쓰레드가 공유하는 객체를 만든다는 것이다.
이로 인해, 공유 필드를 두면 안된다.
1-2-2. Prototype
어플리케이션과 같은 생명주기
프로토 타입 스코프이다.
싱글톤과 달리 항상 다른 인스턴스를 생성/반환한다.
중요한 점은 스프링 컨텍스트가 해당 스코프 빈을 관리하지 않는다는 것이다.
그저, 생성, 의존주입, 초기화, 반환만 해줄 뿐이다.
1-2-2. WebApplicationContext.SCOPE_REQUEST
HTTP 요청 하나 당 별도의 빈 인스턴스가 생성, 관리된다.
스프링 ApplicationContext 내부에 저장되는 것이 아닌, ThreadLocal의 attributes에 빈을 추가한다.
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
//1.ThreadLocal에서 attributes를 꺼내
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
//2. 빈을 가져온다.
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null) {
//3. 만일 빈이 없다면, 새로 만들어 ThreadLocal의 attributes에 빈을 추가한다.
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
Object retrievedObject = attributes.getAttribute(name, getScope());
if (retrievedObject != null) {
scopedObject = retrievedObject;
}
}
return scopedObject;
}
RequestContextHolder 내부 ThreadLocal에서 requestAttributes를 가져와서 request 스코프 빈을 생성해준다.
중요한 점은 ThreadLocal을 사용하여, 생명주기를 추적한다는 것이다.
때문에, 비동기 방식에서 Thread가 달라지면, 사용할 수 없다는 단점이 있다.
1-2-3. WebApplicationContext.SCOPE_SESSION
HTTP Session과 동일한 생명주기를 갖는다.
Request 스코프와 비슷하게 스프링 ApplicationContext에 빈이 저장되는 것이 아닌, HttpSession에 저장된다.
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
RequestAttributes attrs = RequestContextHolder.currentRequestAttributes();
Object bean = attrs.getAttribute(name, RequestAttributes.SCOPE_SESSION);
if (bean == null) {
bean = objectFactory.getObject();
attrs.setAttribute(name, bean, RequestAttributes.SCOPE_SESSION);
}
return bean;
}
세션에 저장되기 때문에, stateful하다.
1-2-4. WebApplicationContext.SCOPE_APPLICATION
서블릿 컨텍스트와 동일한 생명주기를 갖는다.
스프링이 빈을 관리한다.
1-2-5. WebApplicationContext.SCOPE_WEBSOCKET
웹 소켓과 동일한 생명주기를 갖는다.
스프링이 빈을 관리한다.
1-3. Lazy Initialization
1-4. Autowire mode
1-5. Contructor arguments
1-6. Property values
1-7. Factory method
1-8. Init method
1-9. DependsOn
1-10. Qualifier/Primary
1-11. Description/Resource metadata
1-12. Role
2. 빈의 계층
스프링은 빈을 통해 ApplicationContext를 관리한다고 해도 무방하다.
이때, 우리가 직접 사용하는 @Component, @Configuration, @Bean이러한 Annotation 기반 빈 말고도 여러가지 빈의 종류가 존재한다.
2-0. Bean Definition(Bean의 설계도)
빈을 생성하기 위한 설계도이다. 즉, 빈으로 등록될 클래스의 메타 데이터를 저장한 것이다.
위의 빈의 요소들이 저장된다.

기본적으로 beanClass라는 객체를 컴포지션하고 있으며, Class<?>의 형태이다.
빈을 생성하기 위해서 사용된다.

해당 메서드에서 BeanClass를 통해 생성자를 받아오고 생성자로 인스턴스화를 시키는 것을 볼 수 있다.
즉, 다시 강조할 점은 BeanDefinition을 통해 빈을 생성한다는 것이다.
2-1. Infrastructure Bean
스프링 내부 동작을 위한 시스템 빈
- AutowiredAnnotationBeanPostProcessor
- CommonAnnotationBeanPostProcessor
- ConfigurationClassPostProcessor
등등이 존재하겠다.
즉, 스프링이 빈을 처리하기 위해 자체적으로 등록하는 빈들이다.
2-1-1. BeanFactoryPostProcessor
Bean Definition을 수정하는 역할이다.
빈 객체가 생성되기 전에 수행된다.
2-1-2. BeanPostProcessor
빈 객체를 생성한 후 커스터마이징한다.
2-5. 일반 Bean
서비스, 리포지토리, 컨트롤러 같은 실제 어플리케이션 빈이다.
여기에 BeanPostProcessor가 적용된다고 보면 된다.
위의 내용을 이해하기 위해서는 빈 초기화 단계를 알아야 한다.
3. 빈 초기화 순서
빈을 생성할 때는 아래의 그림과 같은 빈 인스턴스 초기화 파이프라인을 거친다.
우리가 빈으로 스프링 기능을 사용할 수 있는 이유는 초기화 시 여러 기능들이 붙기 때문이다.
3-1. 싱글톤 캐시 확인
우선, 싱글톤 빈 캐시부터 확인한다. 미리 만들어져 있던 빈이라면, 그냥 return하면 되고, 아니라면 아래로 진행한다.
3-2. 빈 인스턴스 생성
빈 인스턴스 생성 방식은 스프링 프레임워크에서 크게 2가지로 나뉜다.
3-2-1. @Component, @Configuration의 경우
해당 빈들은 생성자를 통해 생성된다.
3-2-2. @Bean의 경우
해당 빈들은 메서드 invoke를 통해 생성된다.
3-3. 의존성 주입
@Component, @Configuration의 경우 생성자를 통해 생성된다고 하였다.
만일, 의존성이 생성자가 아닌, setter, 필드 를 통한 주입이 될 시 populateBean()이 호출될 수 있다.
3-4. 초기화 콜백
BeanPostProcessor들을 수행한다.
이때, AOP 등의 기능이 붙을 수 있겠다.
3-5. 빈 등록
빈을 등록한다.
자세한 과정은 아래의 글에 포스팅한다.
https://icanchangeworld.tistory.com/312
4. 스코프 별 빈 생성과 관리법
싱글톤 스코프가 아닐 시 생성, 관리, 삭제의 생명주기가 어플리케이션과 다르다.
잘 이해가 가지 않는다. 예시를 들어본다.
@Component
@Scope("request")
public class RequestScopeInstance {
}
===============================================
@Component
@RequiredArgsConstructor
public class Singleton {
private final RequestScopeInstance requestScopeInstance;
}
Singleton 스코프인 객체 내부에서 request 스코프인 객체를 의존주입 받고자 한다.

해당 예외가 터진다.
둘 다 스프링이 관리하는 스코프인데, 왜 이러한 예외가 터질까?
빈들은 생명주기가 맞지 않으면 의존관계 설정 시 예외가 터질 수 있다.
스프링 관리로 사용하기 쉽게 만들기 위해 빈으로 만들고 스코프를 설정해줬는데, 이러한 예외가 터진다면 당황스럽기 그지 없다.
위에서는 Singleton으로 런타임 로딩 시 한 번 인스턴스를 생성하는데, request 스코프를 final 키워드로 받아주었으니, 당연한 결과이다.
4-1. 스코프 생명주기가 맞지 않는다면...
2가지 해결 방법이 있다.
4-1-1. 프록시를 통한 지연 주입
프록시를 의존 주입 해 줌으로 필드로 다른 스코프를 가진 빈을 둘 수 있게 한다.
@Component
@Scope(
value = "request",
proxyMode = ScopedProxyMode.TARGET_CLASS
)
public class RequestScopeInstance {
}
===============================================
@Component
@RequiredArgsConstructor
public class Singleton {
private final RequestScopeInstance requestScopeInstance;
}
이런 식으로 프록시 모드 설정을 해주면 된다.
getBean() 시 스코프에게 빈을 가져오는 메서드를 위임
4-1-2. ObjectProvider 사용
ObjectProvider란 빈을 그때그때 가져오도록 도와주는 객체이다.
어떤 스코프라도 모두 안전하게 가져올 수 있다.
@Autowired
private ObjectProvider<PrototypeBean> provider;
public void logic() {
PrototypeBean bean = provider.getObject(); // 호출할 때마다 새 빈을 생성한다.
}'스프링 프레임 워크' 카테고리의 다른 글
| [Spring 기능 정리] @Valid (1) | 2026.03.01 |
|---|---|
| [JAVA + SpringBoot] 어노테이션과 리플렉션 (0) | 2025.12.06 |
| 클래스 로더와 파일 읽어오기 (0) | 2025.11.28 |
| 스프링 빈 관리 프로젝트 (0) | 2025.11.23 |
| 빈과 빈 컨텍스트 그리고 @Transactional에 대하여 (0) | 2025.03.18 |
