거북이-https://velog.io/@violet_evgadn 이전완료

스프링 컨테이너와 스프링 빈 본문

웹 개발/Spring(이론)

스프링 컨테이너와 스프링 빈

VioletEvgadn 2022. 8. 3. 02:41

Spring Container

◎ Spring Conatiner란?

스프링 컨테이너는 스프링에서 자바 객체들을 관리하는 공간을 말한다.

 

앞에 IoC를 설명할 때 짧게 말했듯, Spring에서는 개발자가 직접 객체를 관리하지 않고 특별한 객체가 모든 객체들을 관리하게 된다. Spring Container는 개발자가 활용했던 new 연산자, 인터페이스 호출, 팩토리 호출 방식으로 객체를 생성하고 소멸시켰던 행위를 개발자 대신해주는 주체가 된다.

 

즉, Spring Container는 Spring의 IoC 역할을 수행하는 특수 공간이라고 생각하면 된다.

 

Spring Container는 내부에 Spring Bean 저장소를 가지고 있으며, Spring Bean 저장소에 Bean 이름 및 객체가 저장되는 것이다.

Spring Container에 Bean Instance가 하나씩 저장되어 있는 상황에서 프로그램이 Instance를 필요로 할 때 스프링 컨테이너가 스프링 빈 저장소에서 적절한 Instance(Spring Bean)를 꺼내 활용하는 방식으로 IoC 과정이 이루어지는 것이다.

 

그림으로 보면 아래와 같을 것이다.

 

◎ Spring Conatiner 종류

BeanFactory

BeanFactory는 빈을 등록하고 조회하는 등 빈을 관리하는 역할을 담당한다.

 

위 사진에서 봤듯이 스프링 컨테이너 내 스프링 빈 저장소가 존재하고, 이 곳에 수많은 스프링 빈이 들어가 있다.

 

ApplicationContext

ApplicationContext또한 BeanFactory처럼 빈을 관리하는 역할을 담당한다.

 

BeanFactory를 활용하지 않고 ApplicationContext로 바꾸어 실행시켜도 동일한 결과를 얻어올 것이다.

 

왜 굳이 BeanFactory와 ApplicationContext로 나눴을까?

위 설명만 보자면 BeanFactory와 ApplicationContext는 동일한 역할을 수행하는 것처럼 보인다. 그렇다면 굳이 나눠놓은 이유가 있을까?

 

먼저, ApplicationContext는 BeanFactory에서 빈을 관리하는 기능을 물려받은 인터페이스이다.

ApplicationContext는 국제화가 지원되는 텍스트 메시지 관리, 이미지와 같은 파일 Load, Bean에게 이벤트 발생 알림 등 BeanFactory보다 더욱 많은 기능을 가지고 있다.

그래서 일반적으로 Spring Container라고 하면 Application Context를 말한다.

 

두 번째로 빈 관리를 하는 방법이 살짝 다르다.

BeanFactory는 getBean() 메서드가 호출된 시점부터 빈을 생성하게 된다.

하지만 ApplicationContext는 초기화 시점에서 모든 싱글톤 빈을 미리 로드시켜 놓는다. 이후 애플리케이션 가동 후에는 미리 로드되어 있는 빈을 지연없이 바로 다운 받을 수 있는 것이다.

 

이런 관점에서 부가 기능이 존재하고 Bean을 지연 없이 받을 수 있는 ApplicationContext를 실제 개발에 주로 활용하게 되는 것이다.


Spring Bean

◎ Spring Bean이란?

계속 Spring Container에서 Spring Bean이라는 단어가 나온다. 그렇다면 이 Spring Bean이라는 것이 정확히 무엇일까?

 

스프링 빈은 "스프링 컨테이너에 의해 관리되는 자바 객체"를 의미한다.

이 개념을 바탕으로 스프링 빈과 스프링 컨테이너를 모두 이해한 개념을 말하면 아래와 같을 것이다.

 

Spring은 IoC 기반 프레임워크이다. 즉 개발자가 객체를 직접 관리하지 않고 Spring측에서 자동으로 객체를 관리해주는 것이다.

이를 위해 개발자는 Spring Bean 저장소에 원하는 객체를 Spring Bean 형식으로 등록하기만 해 놓으면 된다.

ApplicationContext를 활용한다고 하였을 때 Spring Bean에 등록된 여러 Bean들에 대한 Instance가 자동으로 생성될 것이며, 다양한 객체(Spring Bean)들이 Spring Bean 저장소에 저장될 것이다.

이후 프로그램 실행 중 원하는 Instance가 있다면 Spring Bean 저장소에 해당 Spring Bean이 있는지 찾는다. 없으면 에러를 발생시키고(IoC가 아닌 프로그램에서 선언되지 않은 객체를 활용하는 Case와 동일하다) 만약 존재한다면 그 객체를 꺼내 사용하는 흐름으로 IoC 기반 프로그램이 진행되는 것이다

 

◎ Spring Bean Scope

Bean Scope란 Application이 구동되는 동안 빈이 어떻게 활용될 것인지 결정해주는 것이라고 생각하면 된다.

예를 들어 스프링 앱이 구동될 때 1개의 클래스는 1개의 Bean만 가지도록 한 번에 모두 생성할 수도 있고, 아니면 클래스가 사용될 때마다 계속 빈(Instance)를 생성해서 활용하는 방식이 존재할 수도 있다.

 

Spring Bean Scope의 종류는 singelton, protoype, request, session, application, websocket으로 나뉜다.

개인적으로는 Singleton, Prototype은 중요하고 나머지는 개념 정도만 알고 있으면 된다고 생각해서 나머지 것들은 어떤 의미를 갖는지에 대해서만 설명하겠다.

 

  • request : HTTP Life Cycle에서 1개의 Bean을 활용. HTTP Request 하나가 들어와서 요청에 대한 Respond로 나갈 때까지 유지되는 Scope이다.
  • session : HTTP Session마다 1개의 Bean을 활용
  • application : ServletContext Life Cycle에서 1개의 Bean만 활용
  • websocket : Websocket Life Cycle에서 1개의 Bean만 활용

 

◎ Singelton Scope

Singleton Scope에 대해 알아보려면 "싱글톤"에 대해서 알아볼 필요가 있다.

디자인 패턴 중 "싱글톤 패턴"이라는 것이 존재하는데, 이는 하나의 인스턴스만 계속 사용하는 디자인 패턴이다.

인스턴스가 필요할 때 새로운 공간에 인스턴스를 새로 만들지 않고 처음 선언된 인스턴스를 계속해서 활용하는 기법이다.

 

즉, 싱글톤 패턴은 Instance가 필요할 때 마다 새롭게 공간을 할당해서(new 연산자를 활용하여 객체를 생성하는 것) 객체를 생성하여 활용하지 않고 처음 만든 Instance를 필요할 때마다 계속해서 반환해줌으로써 1개의 Instance로 모든 연산을 수행하는 것이다.

 

싱글톤 패턴은 Instance가 절대적으로 1개만 존재하는 것을 보증하고 싶을 때 활용한다.

반대로 Instance가 1개 이상 존재해야하는 상황에서는 활용해서는 안된다.

예를 들어, 이름과 주민번호를 호출하는 Instance가 필요 할 때 싱글톤 패턴을 활용하면 첫번째로 호출한 사람의 이름과 주민번호가 Instance에 저장되어 있을 것이기 때문에 보안상 문제가 발생할 수도 있는 것이다.

 

하지만 Spring Bean은 이런 문제에서 자유롭다. 결국 Spring Bean은 저장된 값을 활용한다기 보다는 로직에 집중하는 객체가 저장소에 올라간다.(데이터는 Model에 담겨 전달될 것이기 때문)

함수에 활용되는 값은 외부에서 주어지기 때문에 단순히 로직에만 집중하면 되고 이 로직은 모든 User에 대해 똑같은 Instance에서 실행되므로 동일한 로직이 적용된다는 것이 보장된다는 점에서 장점을 가지게 될 것이다.

 

기본적으로 별다른 설정이 없다면 모든 빈은 Singleton Scope를 가진다.

Singleton Scope에서는 처음 생성된 Instance를 Spring Beans Cache에 저장하여 해당 빈에 대한 요청이나 참조가 필요할 경우 캐시된 객체를 반환하는 방식으로 Bean 관리가 진행된다.

 

한 마디로 말하자면, Spring Container 1개당 하나의 인스턴스만 활용한다는 것으로써 Application이 구동되는 동안 클래스마다 1개의 Instance만 활용한다는 것이다.

 

<생명 주기>

  1. Spring Container 생성
  2. Spring Bean 생성
  3. 의존 관계 주입
  4. 초기화 Callback
  5. Spring Bean 활용
  6. 소멸(주로 스프링 종료 시점) 전 Callback
  7. Spring 종료

 

◎ Prototype Scope

Prototype 또한 디자인 패턴에서 설명하는 "프로토타입 패턴"에 대해 알 필요가 있다.

프로토타입 패턴이란 원본 객체를 새로운 객체에 복사하여 복사본을 필요에 따라 수정하는 패턴인 것이다.

 

Prototype은 싱글톤과 다르게 원본 객체를 "복사"하여 새로운 객체를 생성하여 활용한다.

즉, 등록된 Spring Bean을 활용해야 할 시기가 오면 등록되어 있는 Bean을 복사하여 새로운 객체를 만들어 이 복사본을 활용하는 것이다.

 

즉 싱글톤과 다르게 객체(Spring Bean)이 필요할 때마다 새롭게 선언(엄밀히 따지자면 깊은 복사를 통한 복사본)하여 활용하는 것을 말한다.

 

<생명 주기>

  1. Spring Container 생성
  2. Spring Bean 생성
  3. 의존 관계 주입
  4. 초기화 Callback
  5. Spring Bean 사용
  6. GC에 의해 사용이 완료된 Bean 수거

Prototype Scope에서는 필요할 때마다 Bean을 생성하여 활용하기 때문에 Bean의 사용이 완료될 때가 Spring 종료 시점이 아니다. 즉, 메모리 관리를 위해 사용이 완료된 Bean들은 GC에 의해 수거되어야 하며, 이 때문에 6번 과정이 추가된다.

 

또한 Callback도 Bean 수거 과정이 GC에 의해 수행되므로 소멸 전 Callback 함수은 PreDestory Callback 함수가 호출되지 않고 오직 초기화 Callback 함수만 존재한다.

 

◎ @Confiuration

Spring Bean을 활용 해보며 '@Configuration' 어노테이션을 많이 활용할 것인데, 이 어노테이션을 활용하면 @Bean에 추가 설정을 주지 않는 이상 무조건 Bean을 싱글톤 패턴으로 관리한다.(싱글톤 패턴이 깨지지 않는다)

 

예를 들어보자(코드 출처 : https://steady-coding.tistory.com/594)

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

위 코드를 보면 MemberService 객체와 OrderService 객체에 'memberRepository()'가 들어 있는 것을 볼 수 있다.

대충 생각해본다면 2개의 다른 Bean에 같은 객체가 요구되므로, 결과적으로 총 2개의 'memberRepository()'가 생성되어 싱글톤이 깨진다고 생각할 수도 있다.

 

@Configuration 어노테이션을 활용할 경우 Spring에서는 CGLIB를 활용해 이런 상황에서도 싱글톤을 보장한다.

CGLIB는 JAVA 클래스의 프록시를 생성해주는 기능을 제공하는 라이브러리이다. CGLIB는 AppConfig가 빈으로 등록될 때 AppConfig를 그대로 올리지 않고 AppConfig@CGLIB라는 AppConfig를 상속받은 형태인 Proxy 객체를 등록한다.

 

이 CGLIB는 memberRepository()에 대한 Bean을 제공할 때 먼저 Spring Container에 해당 Bean이 존재하는지 검사하게 된다. 만약 Container에 빈이 존재한다면 존재하는 빈을 제공하고 없다면 새롭게 빈을 생성하여 컨테이너에 등록 후 반환하게 되는 것이다.

 

'memberService()'부터 Bean에 등록한다고 가정하면, 'memberService()'가 등록될 때 'memberRepository()'가 Container에 처음 등록되며 이후 등록된 객체를 반환해준다. 다음으로 orderService()가 Spring Bean에 등록될 때 이전에 등록되었던 'memberRepository()' Bean을 꺼내서 활용함으로써 최종적으로 싱글톤 패턴을 깨지 않고 Spring Bean 등록이 가능해지는 것이다.

 

그런데 만약 Singelton을 보장하고 싶지 않다면 어떻게 해야할까?

이 때는 @Configuration 어노테이션 대신 @Component 어노테이션을 활용하면 된다.

이 방식을 Bean Lite Mode라고 하는데 CGLIB를 이용하여 바이트 코드 조작을 하지 않는 방식을 의미하며 CGLIB를 활용하지 않기 때문에 싱글톤 패턴이 지켜지지 않을 수 있다.


요약

스프링 컨테이너는 스프링에서 자바 객체들을 관리하는 공간을 말한다.

그리고 스프링 컨테이너에 의해 관리되는 자바 객체들은 스프링 빈이라고 한다.

 

스프링 컨테이너는 내부에 스프링 빈 저장소를 가지고 있으며, 스프링 빈 저장소에는 여러개의 Spring Bean이 저장되어 있다.

Spring의 IoC는 결국 스프링 컨테이너에 의해서 수행되는데 개발자는 Spring Bean을 스프링 빈 저장소에 등록만 해 놓는다면 해당 Instance(스프링 빈)이 필요할 때 스프링 컨테이너가 스프링 빈 저장소에서 해당 빈을 반환시켜 객체를 활용하는 방식이다. 결국 개발자는 Spring Bean에 1번만 객체를 등록시켜 놓는다면 이후부터 객체 사용에 대해 고려하지 않아도 된다는 장점을 가진다.

 

Spring Container는 BeanFactory와 ApplicationContext가 있는데, ApplicationContext는 BeanFactory가 가지고 있는 빈 관리를 위한 기능을 상속받았을 뿐만 아니라 BeanFactory보다 더 많은 기능을 가지고 있으며 초기화 시점에 빈을 모두 등록시켜 놓기 때문에 Bean을 지연 없이 받을 수 있다. 그래서 일반적으로 Spring Container라고 하면 ApplicationContext를 말한다.

 

Spring Bean Scope에 따라 빈을 어떻게 활용하는지에 대해 달라지는데, 예를 들어 Singleton Scope에서는 Spring Bean Cache에 1개의 Bean만 등록시켜놓고 해당 빈을 계속해서 활용하는 방식을 채택했으며 Prototype Scope에서는 객체가 필요할 때 마다 해당 Bean을 새로 생성하여 활용하는 방식을 채택했다.

'웹 개발 > Spring(이론)' 카테고리의 다른 글

AOP  (0) 2022.08.04
IoC와 DI  (0) 2022.08.03
Spring  (0) 2022.08.02
IntelliJ 단축키 + 추천 코딩 방법  (0) 2022.08.02
IntelliJ에 Github 연동하기  (0) 2022.08.02
Comments