Google Developer Student Clubs 1기/GDSC 백엔드 스터디

[백엔드 스터디] 3주차 - Bean Scope 그리고 Bean 생명주기

Razelo 2023. 1. 7. 14:50

1, 2주차 스터디에서 애매했던 질문에 대한 탐색은 다음과 같다. 

 

1. IoC/ DI를 통해 응집도가 높아지는 이유?

 

2. 필드 주입의 단점 중 불변성이 있는데 왜죠?

https://shanepark.tistory.com/368

-> 필드 인젝션은 immutable 불가라는 단점이 있다.이건 setter 주입에서도 마찬가지인데 필드 인젝션으로 주입받는 클래스는 final로 선언 할 수 없기 때문에 state safe하지 않다고 한다. (오직 Constructor Injection만 final 선언이 가능하다고 합니다. 그외의 방법들은 주입되는 필드에 대해 mutable한 상태를 만든다고 한다. 그래서 생성자주입이 권장되는 이유이기도 하다. https://sightstudio.tistory.com/20)

그래서 개발자가 해당 객체를 새로 할당해도 눈치채기가 어렵다고 한다. final로 선언한 불변 객체였다면 코드 작성과정에서 바로 알아차릴 수 있는 버그임에도 알기 어렵다고 한다. 

 

3. 수정자 주입을 사용하면 왜 널포인트 예외가 발생할까?

 

 

이번 3주차는 빈의 스코프와 생명주기에 대해서 알아보기로 한다.


Bean 스코프란? 

우선 Spring에서 POJO를 Beans라고 부른다. Beans는 애플리케이션의 핵심을 이루는 객체이고 Spring IoC 컨테이너에 의해 인스턴스화, 관리, 생성된다. (이 내용은 이전 주차에서 알아보았다.)

 

즉 빈 스코프는 빈이 사용되는 범위를 뜻한다. (빈이 스프링 컨테이너에서 존재할 수 있는 범위를 말한다.)

 

스프링에서 빈은 앱이 구동되는 순간 ApplicationContext에서 한 번에 모두 생성해서 하나의 클래스는 한 개의 빈만 가지도록 하는 것이 기본이다. 싱글톤으로 유지되는 것을 이전 주차에서 살펴보았다. 그런데 때에 따라 이 원칙을 HTTP요청마다 다른 빈을 생성해서 쓸건지, 매번 사용될 때마다 빈을 생성해서 쓸 건지 등을 내가 정해서 쓸 수 있다고 한다. 

 

Bean 스코프의 종류는 어떻게 될까?

Scope Description
singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. (스프링 IoC 컨테이너 당 하나의 인스턴스만 사용 즉 앱이 구동되는 동안 하나만 쓴다.)
prototype Scopes a single bean definition to any number of object instances. (매번 새로운 빈을 정의해서 사용한다.)
request Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext. (HTTP 라이프 사이클 마다 하나의 빈을 사용한다.)
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.(HTTP 세션마다 하나의 빈을 사용한다.)
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.(ServletContext 라이프 사이클 동안 하나의 빈을 사용한다.)
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext. (websocket 라이프사이클 동안 하나의 빈을 사용한다.)

참고로 request, session, application은 웹 관련 스코프로 분류된다.

 

Global Session : global Http Session의 생명 주기 내 단 하나의 객체만 존재

 

실제로 코드 상에서 스프링 빈 스코프를 어떻게 정의할까?

@Scope를 활용하여 아래와 같이 선언할 수 있다. 

  • @Scope(value = "prototype")
  • @Scope(value = "singleton")
  • @Scope(value = "request")
  • @Scope(value = "session")
  • @Scope(value = "application")
  • @Scope(value = "websocket")

다만 이렇게 쓰는 방법 중 주의사항이 한 가지 있다. 

Singleton 스코프의 빈이 Prototype의 빈을 주입받는 경우이다. 

싱글톤 스코프의 빈이 프로토타입 빈을 주입받으면 싱글톤의 프로토타입 빈은 매번 바뀌지 않고 같은 빈이 쓰인다. 왜냐하면 싱글톤 빈은 ApplicationContext가 처음 앱을 구동할 때 빈을 만들고 빈을 주입해서 앱이 종료될 때까지 계속 사용되기 때문에 싱글톤 빈 안에 있는 프로토타입 빈도 처음 주입된 채로 그대로 사용되기 때문이다. 

 

이 문제를 해결하기 위해서 @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)를 사용한다. 또는 ObjectProvider 객체를 사용해서 매번 빈을 주입하는 방법도 있다고 한다. 

 

 

1. Singleton

singleton bean은 Spring 컨테이너에서 한 번 생성된다. 즉 컨테이너가 사라질 때 bean도 제거된다.

생성된 하나의 인스턴스는 single beans cache에 저장되고 해당 bean에 대한 요청과 참조가 있으면 캐시된 객체를 반환한다. 기본적으로 모든 bean은 scope가 명시적으로 지정되지 않으면 singleton으로 관리된다. 

싱글톤 스코프는 컨테이너의 시작과 종료, 모든 생명주기를 함께 하기 때문에 가장 넓은 범위를 가진 스코프이다. 

 

2. Prototype 

prototype bean은 모든 요청에서 새로운 객체를 생성하는 것을 의미한다. 즉 prototype bean은 의존성 관계의 bean에 주입될 때 새로운 객체가 생성되어 주입된다. 그리고 정상적인 방식으로 GC에 의해 bean이 제거된다. 

 

초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출 

소멸전 콜백: 빈이 소멸되기 직전에 호출 

 

싱글톤으로 적합한 객체

  • 상태가 없는 공유 객체
  • 읽기용으로만 상태를 가진 공유 객체
  • 공유가 필요한 상태를 지닌 공유 객체
  • 쓰기가 가능한 상태를 지니면서도 사용빈도가 매우 높은 객체

비싱글톤으로 적합한 객체 

  • 쓰기가 가능한 상태를 지닌 객체
  • 상태가 노출되지 않은 객체
싱글톤으로 적합한 객체
1. 상태가 없는 공유 객체: 상태를 가지고 있지 않은 객체는 동기화 비용이 없다. 따라서 매번 이 객체를 참조하는 곳에서 새로운 객체를 생성할 이유가 없다.
2. 읽기용으로만 상태를 가진 공유 객체: 1번과 유사하게 상태를 가지고 있으나 읽기 전용이므로 여전히 동기화 비용이 들지 않는다. 매 요청마다 새로운 객체 생성할 필요가 없다.
3. 공유가 필요한 상태를 지닌 공유 객체: 객체 간의 반드시 공유해야 할 상태를 지닌 객체가 하나 있다면, 이 경우에는 해당 상태의 쓰기를 가능한 동기화 할 경우 싱글톤도 적합하다.
4. 쓰기가 가능한 상태를 지니면서도 사용빈도가 매우 높은 객체: 애플리케이션 안에서 정말로 사용빈도가 높다면, 쓰기 접근에 대한 동기화 비용을 감안하고서라도 싱글톤을 고려할만하다. 이 방법은 1. 장시간에 걸쳐 매우 많은 객체가 생성될 때, 2. 해당 객체가 매우 작은 양의 쓰기상태를 가지고 있을 때, 3. 객체 생성비용이 매우 클 때에 유용한 선택이 될 수 있다.

비싱글톤으로 적합한 객체
1. 쓰기가 가능한 상태를 지닌 객체: 쓰기가 가능한 상태가 많아서 동기화 비용이 객체 생성 비용보다 크다면 싱글톤으로 적합하지 않다.
2. 상태가 노출되지 않은 객체: 일부 제한적인 경우, 내부 상태를 외부에 노출하지 않는 빈을 참조하여 다른 의존객체와는 독립적으로 작업을 수행하는 의존 객체가 있다면 싱글톤보다 비싱글톤 객체를 사용하는 것이 더 나을 수 있다.

 

빈 생명주기란?

컨테이너는 초기화될 때 Bean 객체들을 등록, 생성, 주입하고 종료할 때 Bean 객체들을 소멸시키면서 그 생명 주기를 관리한다. 스프링 컨테이너는 이런 Bean 객체의 생명 주기를 컨테이너의 생명 주기 내에서 관리하고 객체 생성이나 소멸 시 호출될 수 있는 콜백 메서드를 제공한다. 

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백(Event) -> 앱 본연의 동작 수행 ->  소멸전 콜백(Event) -> 스프링 종료의 단계를 거친다. 

그리고 이때 초기화시 사용되는 콜백과 소멸 단계에서 사용되는 콜백이 존재한다. 

 

먼저 콜백이란 콜백함수를 부를 때 사용되는 용어이고 콜백함수를 등록하면 특정 이벤트가 발생했을 때 해당 메서드가 호출된다. 

 

스프링 2.5 이후부터는 메서드에 어노테이션을 붙여서 해당 메서드를 생명주기 메서드로 지정할 수 있다고 한다. 

@PostConstruct 어토네이션은 객체의 생성 및 초기화가 끝나고 이 객체를 의존하는 곳에 주입하기 직전에 호출된다고 한다. 그리고 @PreDestroy 어노테이션은 이 객체가 스프링 컨테이너에서 소멸되기 직전에 호출된다고 한다. 

 

이런 이벤트들에서 초기화 콜백을 이용해서 테스트로 사용할 데이터를 앱의 사전 동작 전에 미리 저장하거나 소멸 전에 콜백을 사용해서 스프링이 종료되는 상황에서 데이터를 백업하는 동작을 수행할 수 있다. 

 

사용할 수 있는 세가지 방식

  1. 인터페이스 방식: 클래스가 InitializingBean과 DisposalBean을 상속받고 각각 afterPropertiesSet()과 destroy() 메서드를 구현하는 방식이다. 이때 초기화 콜백 시 afterPropertiesSet, 소멸 전에 destroy가 호출되는 형식이다. (이 인터페이스는 스프링 전용 인터페이스이기 떄문에 코드가 종속되어 스프링 없는 환경에서는 단위 테스트가 힘들다. 또한 초기화, 소멸 메서드의 이름을 변경할 수 없고, 외부 라이브러리에 적용할 수 없어서 최근에 거의 쓰이지 않는 방식이라고 한다. )
  2. 빈 등록 초기화, 소멸 메서드 지정 방식: @Bean 어노테이션이 내장된 설정 방식으로 각각 initMethod필드와 destroyMethod필드의 생성과 소멸 시 적용될 메서드 명을 정해주면 된다. (@Bean(initMethod="start", destroyMethod="end")) 메서드 이름을 자유롭게 줄 수 있다. 스프링 빈이 스프링 코드에 의존하지 않는다고 한다. 그리고 코드가 아니라 설정 정보를 이용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 적용할 수 있다. 
  3. @PostConstruct, @Predestroy 어노테이션 방식: @PostConstruct와 @Predestroy 를 사용한다. (가장 일반적인 방식이라고 한다. 참고로 스프링에 종속적인 기술이 아니라 자바 표준이라고 한다. 그래서 스프링이 아닌 다른 컨테이너에서도 동작할 수 있는 방식이라고 한다. 외부 라이브러리에 적용하지 못한다는 점이 단점이라고 한다. 그래서 외부 라이브러리에서 진행하려면 @Bean의 기능을 활용해야한다.) 최신 스프링에서 가장 권장하는 방식이다.

 

싱글톤 빈은 Thread-Safe한가?

Bean에서 싱글톤을 사용할때 동기화도 당연히 같이 처리해준다고 착각할 수 있다. 하지만 스프링은 싱글톤 레지스트리를 통해 private, static 변수 등의 코드 없이 비즈니스 로직에 집중하고 테스트 코드에 용이한 싱글톤 객체를 제공해주는 것 뿐으로 동기화 문제는 개발자가 처리해야한다고 한다. 그래서 만약 싱글톤 빈이 상태를 가지고 아무런 동기화 처리를 하지 않는다면 멀티스레드 환경에서 부작용이 발생할 수 있다. 

 

알아두기

  1. 싱글톤 빈은 생성 시점에만 의존관계를 주입받는다. 
  2. 의존관계를 외부에서 주입(DI)받는 것이 아니라 직접 필요한의존관계를 찾는 것을 Dependency Lookup이라고 부른다. 의존관계 조회이다.
  3. 웹 스코프는 웹 환경에서만 동작한다. 웹 스코프는 해당 프로토타입과는 다르게 스프링이 해당 스코프의 종료 시점까지 관리한다. 그래서 종료 메서드가 호출된다. 
  4. 객체가 상태값을 가지고 단순 조회가 아닌 write 작업이 이루어질 경우에는 bean 스코프를 알맞게 선택해서 사용하는 게 좋다. 
  5. 코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야만 하면 @Bean의 initMethod, destroyMethod를 활용하자. 

 

면접 예상 질문 

1. 빈 스코프 중에서 가장 넓은 범위를 가진 스코프는 무엇인가요?

싱글톤 스코프

 

2. 싱글톤으로 적합한 객체의 특징은 무엇인가요? 

읽기용으로만 상태를 가진 공유 객체

 

3. 싱글톤으로 적합하지 않은 객체의 특징은 무엇인가요? 

쓰기가 가능한 상태를 지닌 객체

 

4. 빈 스코프의 종류에 대해 두 가지 이상 말해보세요.

singleton scope, prototype scope

 

5. 프로토타입 빈은 어떤 방식에 의해 제거되나요?

정상적인 GC방식에 의해 제거된다.

 

 6. 빈 생명주기를 순서대로 말하보세요.

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백(Event) -> 앱 본연의 동작 수행 ->  소멸전 콜백(Event) -> 스프링 종료

 

7. @PostConstruct와 @PreDestroy은 각각 언제 호출되나요?

초기화할 때와 소멸 전

 

8. @PostConstruct, @PreDestroy 어노테이션을 사용하는 방법 말고도 초기화 콜백과 소멸 전 콜백을 사용할 수 있는 방법에 대해 말해보세요. 

인터페이스 방식, @Bean 활용 방식

 

9. @PostConstruct와 @PreDestroy 어노테이션의 단점을 말해보세요.

외부 라이브러리에 적용할 수 없음

 

10. 초기화 작업이 유용하게 쓰일 수 있는 예를 하나 들어보세요.

Database Connection Pool, Socket)

 

11. 종료 작업이 유용하게 쓰일 수 있는 예를 하나 들어보세요.

연결 해제 작업)

 


도움받은 자료의 출처는 아래와 같습니다. 

 

감사합니다. 

 

빈 스코프

- https://velog.io/@probsno/Bean-%EC%8A%A4%EC%BD%94%ED%94%84%EB%9E%80 

- https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes

- https://code-lab1.tistory.com/186

- https://yhmane.tistory.com/221

 

빈 생명주기 

- https://haruhiism.tistory.com/186

- https://velog.io/@zenon8485/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EC%9B%90%EB%A6%AC-4.-%EC%8A%A4%ED%94%84%EB%A7%81-Bean%EC%9D%98-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0

- https://devlog-wjdrbs96.tistory.com/321

- https://code-lab1.tistory.com/180

-  https://velog.io/@guri_coding/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88

 

빈 생명주기 영상자료

- https://www.youtube.com/watch?v=5CBZPb3o0XI 

- https://www.youtube.com/watch?v=cf1EyJbKuSg 

 

 

반응형