Spring Ioc 컨테이너에 의해 관리되는, 자바 객체.
각 DI 방식의 설명에 앞서, @Autowired 어노테이션부터 설명하겠다.
해당 어노테이션이 달린 생성자, 메서드(setter), 필드는,
직접 해당 객체를 생성할 필요 없이 Spring IoC Container에 등록된 Bean을 자동 주입(DI) 해 준다.
해당 어노테이션이 달리는 위치에 따라 생성자, setter, 필드주입으로 나뉜다.
다만 생성자에 어노테이션을 달 경우, 생성자의 파라미터가 빈으로 등록되어 있어야 하며, setter와 필드 또한 빈으로 등록되어 있어야 알아서 IoC Container가 Bean을 자동 주입해준다.
가장 보편적으로 쓰이는 DI 방식이다.
@Configuration
public class AppConfig {
@Bean
public Item item1() {
return new ItemImpl1();
}
@Bean
public Store store() {
return new Store(item1());
}
}
위와 같이 Configuration 클래스에서 관리되는 빈들이 있다. 만약 Item 객체를 다른 클래스에서 사용하고 싶다고 가정한다.
public class Order {
private final Item item;
@Autowired
public Order(Item item) {
this.item = item;
}
}
특이한 점은, 클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체가 빈으로 등록되어 있다면 @Autowired를 생략 할 수 있다.
public class Order {
private Item item;
@Autowired
public void setItem(Item item) {
this.item = item;
}
}
public class Order {
@Autowired
private Item item;
}
3가지의 주입 방식을 비교해 보며, 가장 좋은 방식이 무얼까 한번 고민해 보자.
얼핏 보면 생성자 주입에 비해 필드 주입이 가장 코드량이 적고, 편리해 보여서 쓰고 싶어진다.
하지만 생성자 주입을 쓰는데, 그 이유는
결론은 생성자 주입 승.
이런 관점에서, 역시나 생성자 주입 승.
@Configuration
@ComponentScan("com.baeldung.constructordi")
public class Config {
@Bean
public Engine engine() {
return new Engine("v8", 5);
}
@Bean
public Transmission transmission() {
return new Transmission("sliding");
}
}
@Component
public class Car {
@Autowired
public Car(Engine engine, Transmission transmission) {
this.engine = engine;
this.transmission = transmission;
}
}
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Car car = context.getBean(Car.class);
위 코드 어디에서도 Car객체를 생성하기 위해 Engine, Transmission의 객체를 생성하는 부분이 없다.
이는 Car 객체를 생성할 때
이는 개발자가 직접 주입할 객체를 생성하고, 주입하는 코드를 작성할 필요가 없다는 편리함이 있다.
출처 : https://www.geeksforgeeks.org/bean-life-cycle-in-java-spring/
처음에 말했듯, 빈은 Spring Ioc 컨테이너에 의해 관리된다.
맨 처음 프로그램이 시작되면, Ioc 컨테이너가 시작되고, 컨테이너가 Bean 어노테이션 혹은 Component 어노테이션이 붙은 것들을 스캔하여 각각의 객체들이 필요한 의존성을 주입해 준다.
만약 빈이 만들어진 이후 실행시키길 원하는 메소드가 있다면
init메소드를 만든 이후에 @PostConstruct 어노테이션을 붙여준다면, 빈이 생성된 이후에 알아서 해당 메소드를 실행시킨다.
마찬가지로 빈이 사라지기 전에 실행시킬 원하는 메소드가 있다면 메소드를 만든 이후에 @PreDestroy 어노테이션을 붙여주면 된다.
이렇게 빈의 생명주기가 관리된다.
💡 참고 : 빈의 생성시기보통은 Spring 어플리케이션이 동작되자마자 Bean객체들이 생성 & 설정되지만, @Lazy어노테이션을 추가로 붙인다면, 해당 Bean이 호출되는 시점에 생성된다.
그렇다면 무조건 호출되는 시점에 Bean을 생성하는게 이득 아니야 ? 라고 생각할 수도 있지만, 아니다.
Bean과 관련된 잘못된 설정이나 환경을 프로그램 실행 시점에 잡을 수 있는 것이 아니라,
해당 Bean이 호출되는 시점에 잡을 수 있으므로, 이는 프로그램 운영 관점에서 보면 아주 치명적이다.
결론 : Lazy initialization 쓰지 말자.
빈의 생성방식. 6가지가 있다.
아래 4가지 방식은 web application에서만 쓰이는 방식이며, 스프링은 기본적으로 singleton scope로 Bean을 생성한다.
사용방법은 간단하다. 아래와 같이 Bean으로 등록할 메소드 위에 @Scope 어노테이션을 붙이고 원하는 scope을 명시하면 된다.
@Bean
@Scope("singleton")
public Person personSingleton() {
return new Person();
}
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
혹은 value = 와 함께 정의되어 있는 SCOPE상수를 써도 된다.
자주 사용하는 singleton, prototype 두가지의 기능만 간단히 설명하겠다.
우리가 아는 그 singleton 패턴이다.
다만 기존의 singleton 패턴은 Classloader마다 클래스의 인스턴스가 하나 만들어 지는 것이고,
Spring singleton은 IoC Conatiner마다 클래스에 대한 하나의 Bean 인스턴스만 만들어지고 쓰인다.
모든 Bean 요청에 대해 spring container는 같은 Bean을 리턴한다.
singleton과 달리, 해당 Bean에 대한 요청이 있을 때 마다(ex : DI, getBean()호출 등.) 새로운 Bean 인스턴스를 만드는 방식.
모든 Bean 요청에 대해 spring container는 각각 다른 Bean을 리턴한다.
Singleton Scope와 다르게, 생성된 Bean을 스프링 컨테이너가 관리하지 않으므로, 클라이언트에서 소멸 메소드 등을 별도로 관리해야 한다.
Singleton | Prototype | |
---|---|---|
인스턴스 갯수 | IoC 컨테이너당 하나의 Bean 저장.하나의 인스턴스는 컨테이너의 cache에 저장돼 있으며, Bean 요청때마다 캐싱된 빈이 리턴된다. | DI시점 / Bean 요청때마다 새로운 인스턴스가 생성된다. |
Bean 생성시기 | lazy옵션이 없다면, application context(spring container)가 실행될 때. | Bean이 호출될 때 |
State 유무 | 공유되는 Bean이므로, 기본적으로 stateless하다. | 생성되는 Bean마다 공유하는 자원이 없으므로, stateful하다. |
Singleton Scope의 단점은 기존 디자인 패턴에서의 Single톤 패턴의 단점과 비슷하다. 이에 대해 별도의 포스팅을 하겠다.