스프링 빈(Bean)에 대해 설명을 읽기 전에
스프링과 IoC, DI에 대해서
읽어보는 것을 추천한다.
Bean을 설명하기 전에..
먼저 스프링 컨테이너(Spring Container)에 대해 언급하려고 한다. 스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공하는 역할을 한다. 여기서 자바 객체를 스프링에서는 bean이라고 부른다. 그리고 위에 링크에서 다룬 IoC와 DI의 원리가 스프링 컨테이너에 적용된다.
개발자는 new 연산자, 인터페이스 호출, 팩토리 호출 방식으로 객체를 생성하고 소멸시킬 수 있는데, 스프링 컨테이너가 이 역할을 대신해준다. 즉, 제어 흐름을 외부에서 관리하는 것이다. 객체들 간에 의존 관계를 스프링 컨테이너가 런타임 과정에서 알아서 만들어 준다. DI는 생성사, setter, filed를 통해 적용된다.
객체를 new 연산자로 생성했을 때 해당 객체는 빈(Bean)이 아니다. ApplicationContext.getBean()으로 얻어질 수 있는 객체를 빈이라고 한다. 즉, 스프링에서는 빈은 ApplicationContext가 알고 있는 객체, 즉 ApplicationContext가 만들어서 그 안에 담고 있는 객체를 의미한다.
Spring Bean을 Spring IoC Container에 등록하는 방법
1. 어노테이션(Annotation)
어노테이션은 사전상으로는 주석의 의미를 갖지만, 자바에서는 주석 이상의 기능을 가지고 있다. 어노테이션은 자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종이다.
스프링 부트의 경우 @Component, @Service, @Controller, @Bean, @Configuration,... 등으로 필요한 빈들을 등록하고 필요한 곳에서 @Autowired를 통해 주입받아 사용하는 것이 일반적이다.
다음 예제는 @Compoent와 @Autowired 어노테이션을 사용해서 빈 객체를 등록해 보자.
public interface animal{
public void eat();
public void sleeping();
}
@Component
public class Comdolidoli1 implements animal{
@Override
public void eat(){
System.out.println("냠냠");
}
@Override
public void sleeping(){
System.out.println("쿨쿨");
}
}
@Component
public class Comdolidoli2 implements animal{
@Override
public void eat(){
System.out.println("냠냠냠?");
}
@Override
public void sleeping(){
System.out.println("쿨쿨쿨!");
}
}
@SpringBootApplication
public class ComdolidoliTest implements CommandLineRunner{
@Autowired
Animal animal;
public static void main(String[] args){
SpringApplication.run(ComdolidoliTest.class,args);
}
@Override
public void run(String ... args) throws Exception{
animal.eat();
}
}
Autowired은 자동으로 스프링 빈 객체를 특정 참조 변수에 매핑해주는 것을 뜻하며 @Autowired라는 어노테이션을 사용한다. ComdolidoliTest.java 파일을 다음과 같이 CommandLineRunner 인터페이스를 구현하는 것으로 작성하였다. CommandLineRunner는 스프링 부트 애플리케이션이 시작되고 Command Line 인자를 받아 실행되는 코드를 구현하기 위해 사용한다. 반드시 run() 메서드를 오버라이딩 해야한다.
위에 코드를 실행하면 한 개의 빈을 사용하는데 두 개의 빈이 사용되어서 에러가 발생하게 된다. 스프링은 싱글톤이기 때문에 comdolidoli1, comdolidoli2 인스턴스를 동시에 불러올 수 없다. 그래서 어떤 것을 선택해야 할지 지정을 해줘야 한다.
2. Bean Comfiguration
@Configuration과 @Bean 어노테이션을 이용하여 Bean을 등록할 수 있다. 아래 코드와 같이 @Configuration을 이용하면 스프링 프로젝트에서는 COnfiguration 역할을 하는 클래스를 지정할 수 있다. Bean으로 등록하고자 하는 클래스에 @Bean 어노테이션을 사용해주면 간단하게 Bean을 등록할 수 있다.
@Configuration
public class HelloConfiguration {
@Bean
public animal test1(){
comdolidoli1 com1 = new comdolidoli1();
return com1;
}
@Bean
public animal test2(){
comdolidoli2 com2 = new comdolidoli2();
return com2;
}
}
위에 코드도 1번 예제와 같이 비슷하게 구현하였다. 그렇기 때문에 1번 예제와 동일한 에러가 발생할 것이다.
3. @Primary, @Qualifier
Primary : @Bean 혹은 @Component에 함께 사용하며 객체 생성의 우선권을 부여한다.
Qualifier : @Autowired에 함께 사용하며 Bean의 이름이 같은 객체를 찾는다.
Bean에는 @Bean(name = "name")으로 이름을 지정할 수 있다.
Primary, Qualifier 예제
1. Qualifier를 사용하는 경우
@Bean 이름을 지정하고 animal 객체의 어노테이션에 Qualifier을 "comdolidol1"로 지정하면 오류 없이 "냠냠"이 출력된다.
@Configuration
public class HelloConfiguration {
@Bean(name = "comdolidol1")
public animal test1(){
...
@Bean(name = "comdolidol2")
public animal test2(){
...
}
....
@SpringBootApplication
public class ComdolidoliTest implements CommandLineRunner{
@Autowired
@Qualifier("comdolidoli1")
Animal animal;
...
}
2. Primary를 사용하는 경우
@Qualifier를 사용하지 않고 @Primary를 사용해도 객체 생성의 우선권이 부여된다. 오류 없이 동일하게 "냠냠"이 출력된다.
@Configuration
public class HelloConfiguration {
@Bean(name = "comdolidol1")
@Primary
public animal test1(){
...
@Bean(name = "comdolidol2")
public animal test2(){
...
}
@SpringBootApplication
public class ComdolidoliTest implements CommandLineRunner{
@Autowired
Animal animal;
...
}
3. Qualifier과 Primary를 같이 쓸 경우 누가 더 우선순위가 높은가?
마지막으로 @Qualifier와 @Primary를 동시에 사용하면 @Qualifier가 @Primary보다 우선권이 높기 때문에 "냠냠"이 출력된다.
@Configuration
public class HelloConfiguration {
@Bean(name = "comdolidol1")
public animal test1(){
...
@Bean(name = "comdolidol2")
@Primary
public animal test2(){
...
}
...
@SpringBootApplication
public class ComdolidoliTest implements CommandLineRunner{
@Autowired
@Qualifier("comdolidoli1")
Animal animal;
...
}