1. Spring Container란?


▶ Spring Container는 java 객체의 life cycle를 관리하고, 생성된 객체들에게 추가적인 기능을 제공합니다. Spring Container에서 관리하는 객체를 Bean라고 부릅니다. bean에는 IoC, DI가 적용됩니다.

1-1. BeanFactory와 ApplicationContext


▶ BeanFactory와 ApplicationContext 모두 Spring Container입니다. 아래와 같이 구성됩니다. 

▶ BeanFactory는 bean을 생성, 등록, 조회 등등 빈을 관리하는 역할을 합니다. BeanFactory.getBean(빈 이름, 반환형)을 통해 인스턴스화 할 수 있습니다.

@SpringBootTest
public class MemberTest {


    @Test
    void test(){
        final BeanFactory beanFactory = new AnnotationConfigApplicationContext(MemberConfig.class);
        MemberRepository memberRepository = beanFactory.getBean("memberRepository", MemberRepository.class);
        System.out.println(memberRepository.toString());
    }
}
test 환경에서 진행했습니다. 

컨테이너에 등록된 모습을 볼 수 있습니다.

ApplicationContextBeanFactory와 같은 역할을 하지만 BeanFactory를 상속받아 부가적인 기능을 가지고 있고, BeanFactory의 경우 getBean() method를 실행해야 빈을 생성하지만 ApplicatipnContext는 컨테이너 초기화 시점에 bean을 모두 로드 하기 때문에 bean을 생성하는 과정없이 바로 사용할 수 있습니다. 따라서 ApplicationContext의 부가적인 기능과 바로 bean을 얻는 장점으로 많이 사용합니다. 

@SpringBootTest
public class MemberTest {


    @Test
    void test(){
        final ApplicationContext beanFactory = new AnnotationConfigApplicationContext(MemberConfig.class);
        MemberRepository memberRepository = beanFactory.getBean("memberRepository", MemberRepository.class);
        System.out.println(memberRepository.toString());
    }
}

1-2. singleton container

MeberConfig를 아래와 같이 bean이 컨테이너 등록될 때 출력하는 걸로 변경합니다.

@Configuration
public class MemberConfig {

    @Bean
    public MemberRepository memberRepository(){
        System.out.println("memberRepository bean 생성");
        return new MemberRepository();
    }

    @Bean
    public MemberService memberService(){
        System.out.println("memberService bean 생성");
        return new MemberService(memberRepository());
    }
}

여기서 의문점이 있습니다. memberRepository()는 bean이 생성될 때 한 번 memberService에 한 번 사용되는데 어째서 "MemberRepository bean 생성" 이 한 번만 출력될까요??

▶ 바로 singleton으로 컨테이너가 작용되기 때문입니다. 

@SpringBootTest
public class MemberTest {


    @Test
    void test(){
        final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MemberConfig.class);
        MemberConfig memberConfig = applicationContext.getBean("memberConfig", MemberConfig.class);
        System.out.println(memberConfig.toString());
    }
}

 

위와 같이 memberConfig 클래스를 출력했을 때 MemberConfig가 아닌 Enhancer~ 가 출력됩니다. 

이것은 우리가 만든 MemberConfig가 아닌 CGLIB라는 바이트 코드 조작 라이브러리를 통해서 임의의 클래스가 제작되고 그 클래스를 bean으로 등록되기 때문입니다. @Bean이 붙은 메소드가 스프링 컨테이너에 존재한다면 해당 bean을 반환하여 싱글톤이 유지됩니다. ( 단 @Configuration을 붙여줘야합니다. )

2. bean 생성법 


2-1. 자동 생성법

▶ bean으로 등록하고자 하는 객체에 @Component 어노테이션을 붙입니다. 

@Component
public class MemberService {

    private final MemberRepository memberRepository;
	
    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
@Component
public class MemberRepository {
}
@SpringBootApplication
public class BeanApplication {

    public static void main(String[] args) {
        SpringApplication.run(BeanApplication.class, args);
    }

}

SpringBoot는 @SpringApplication이 붙은 객체가 있는 패키지부터 하위 패키지까지 ComponentScan을 통해 bean으로 자동 등록해줍니다. 만약 Scan 범위가 패키지 밖이라면 옵션을 부여해주면 됩니다.

@SpringBootApplication(scanBasePackages = "com.bean2")
public class BeanApplication {

    public static void main(String[] args) {
        SpringApplication.run(BeanApplication.class, args);
    }

}

 

2-2. 수동 생성법

위에서 사용한 것처럼 @Configuration을 사용해주면됩니다. 

@Configuration
public class MemberConfig {

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

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

 

 

2-3 수동 vs 자동

▶ 수동으로 등록하는 bean과 자동으로 등록하는 bean의 우선순위는 수동 등록이 우선권을 가져가게 됩니다. 

3. 의존 관계 자동 주입


3-1. 생성자 주입

@Component
public class MemberService {


    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
생성자 호출 시 한 번만 호출되는 것이 보장된다. final을 붙일 수 있습니다. ( 불변, 필수 )

3-2. 수정자 주입

@Component
public class MemberService {


    private MemberRepository memberRepository;

    @Autowired
    public void setMemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
final이 붙지 않았기 때문에 선택, 변경이 가능합니다. 

3-3. 필드 주입

@Component
public class MemberService {
    
    @Autowired
    private MemberRepository memberRepository;

}
필드에서 바로 주입하는 형태이다. 코드는 간결하지만 외부에서 변경이 어렵고 테스트가 힘들어 사용하지 않습니다.

 

3-4. 생성자 vs 수정자 vs 필드

 

생성자 주입을 선택하는 것이 좋습니다. 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없기 때문입니다. 혹여나 누군가 실수로 변경하게 된다면 장애로 이어질 수 있습니다. 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없기 때문에 불변하게 설계할 수가 있습니다.

 

 

지금까지 스프링 컨테이너와 bean에 대하여 알아보았습니다. 감사합니다.