스프링 입문-컨테이너와 빈

스프링 컨테이너

우리가 컴포넌트 스캔을 사용하던, 코드를 사용하여 수동 등록을 하던 스프링 컨테이너에 등록된 빈들은 ApplicationContext를 사용하여 꺼내올 수 있다.
ApplicationContext는 인터페이스다. 이것을 구현한 AnnotationConfigApplicationContext 등의 클래스에 설정 정보를 넣어 사용하게 된다. 이렇게 스프링 컨테이너에 빈을 등록하면 스프링이 부팅될 때 각 빈의 객체를 생성하여 컨테이너에 보관하게 된다. 이 과정에서 의존관계를 주입하기가 용이하고, 싱글톤이 보장되며 IoC를 구현할 수 있는 등 여러 장점이 있다.

스프링 빈의 조회

스프링 빈은 기본적으로 만들어놓은 ApplicationContext에서 getBean()을 사용하여 조회할 수 있다. 이름과 타입으로 조회가 가능하고 타입만으로도 조회가 가능한데 만약 같은 타입이 둘 이상 있다면 충돌하여 오류가 발생한다. getBeansOfType()을 사용하면 같은 타입의 빈들을 Map의 형태로 반환받아 조회할 수 있다.
스프링 빈을 조회할 때 상속 관계가 있다면 부모 타입을 조회할 때 자식 타입들도 모두 조회된다. 따라서 getBean()으로 부모 타입을 조회한다면 자식 타입과 충돌하여 오류가 발생할 수 있다. 반면에 getBeansOfType()으로 부모 타입을 조회한다면 부모와 자식 타입의 빈들을 모두 반환받을 수 있다.

스프링 컨테이너의 구조

컨테이너

스프링 컨테이너는 위와 같은 구조로 이루어져 있다. BeanFactory는 getBean()과 같은 빈을 조회하고 관리하는 역할을 한다. 우리가 자료구조를 사용할 때 Collection을 사용하지 않고 하위 클래스들을 사용하듯이 BeanFactory보다는 여러 기능이 추가된 자식 클래스인 ApplicationContext를 주로 사용한다.
ApplicationContext는 BeanFactory의 기능에 추가로 여러 기능들을 제공한다. 예를 들면 국제화 기능, 환경변수, 애플리케이션 이벤트 등등의 기능이 있다. ApplicationContext는 설정 정보의 타입에 따라 해당 인터페이스를 구현한 AnnotationConfigApplicationContext, GenericXmlApplicationContext 등의 구현체를 통해 사용한다.

BeanDefinition

이렇게 타입이 다른 여러 설정 정보를 스프링 컨테이너가 이용할 수 있는 이유는 설정 정보를 BeanDefinition으로 추상화하여 읽어들이기 때문이다. 스프링 컨테이너의 구현체들은 모두 각자의 타입에 따라 설정 정보를 읽고 BeanDefinition으로 생성해내는 기능을 가지고 있다. 그러므로 스프링 컨테이너는 설정 정보의 타입이 무엇인지 알지 못해도 bean들의 메타정보인 BeanDefinition만 있으면 bean을 생성하여 저장할 수 있는 것이다.

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 컨테이너라고도 불린다. 그 이유는 스프링이 객체의 싱글톤을 보장해주기 때문이다.
싱글톤은 객체를 단 하나만 생성되도록 보장하는 디자인 패턴이다. 요청에 따라 객체를 생성하고 파괴하는 것은 많은 요청이 오가는 웹 애플리케이션에서 메모리와 시간 상 매우 비효율적이기 때문에 만들어졌다.
싱글톤 패턴은 객체를 공유하여 효율적으로 사용할 수 있는 장점이 있지만 많은 단점들도 있는데, 코드를 통해 싱글톤을 구현할 경우 싱글톤 패턴을 매번 추가해주어야 되기에 코드가 길어지고, 의존관계 상 클라이언트가 싱글톤을 통해 반환된 구현화된 객체에 의존하기 때문에 DIP와 OCP를 위반할 가능성이 있다. 이외에도 코드의 유연성이 크게 떨어지는 단점이 있다.

하지만 스프링 컨테이너를 사용하면 위의 여러 단점을 해결하면서 싱글톤의 장점만을 사용할 수 있다. @Configuration 애노테이션이 붙은 설정 정보를 받은 스프링 컨테이너에서는 CGLIB과 같은 바이트코드 조작 라이브러리를 사용하여 기존 클래스를 상속받은 임의의 클래스를 사용하여 싱글톤을 보장한다. 이미 객체가 컨테이너에 등록되어 있다면 해당 객체를 찾아 반환하는 식으로 작동한다.
이러한 싱글톤 패턴을 사용할 때에는 반드시 클래스를 Stateless하게 만들어야 한다. 이전 RESTful API를 공부할 때 배웠던 그 Stateless이다. 즉 클래스에 클라이언트가 접근하여 값을 조정할 수 있는 변수가 있어서는 안된다는 뜻이다. 변수가 필요하다면 로컬 변수와 Threadlocal 등을 사용해야만 한다. 그렇지 않으면 여러 클라이언트가 변수에 동시에 접근하며 요청이 atomic하게 처리되지 않을 수 있다.

스프링 빈 자동 등록

수동으로 Config 설정 파일을 만들어 등록하는 방법 외에도 자동으로 스프링이 빈을 컨테이너에 등록하게 만드는 방법도 있다. 스프링의 컴포넌트 스캔 기능을 사용하면 되는데 컴포넌트 스캔은 이름대로 @Component 애노테이션, 또는 @Component 애노테이션을 상속받은 애노테이션이 붙은 클래스들을 빈으로 자동 등록해준다.
여러 필요한 클래스들에 @Component 애노테이션을 붙여준 뒤 @ComponentScan 애노테이션이 붙은 클래스를 스프링 컨테이너에 매개변수로 입력하여 사용하면 된다. 또한 @Autowired 애노테이션을 생성자 등에 사용하면 자동으로 해당 타입의 빈을 컨테이너에서 찾아 의존관계를 주입해준다. 매우 편리하다.
컴포넌트로 등록한 빈은 해당 클래스 이름의 첫글자를 소문자로 한 것을 이름으로 하여 빈으로 등록된다. 우리는 basePackages를 지정하거나, Filter 기능을 사용하여 자동 등록된 빈들 중 원하는 것들만 조회하는 것도 가능하다.