IoC 컨테이너 (1) : IoC 컨테이너, BeanFactory, ApplicationContext

1. IoC(Inversion of Control), IoC 컨테이너

Inversion of Control란 의존 관계 주입(Dependency Injection)이라고도 하며, 어떤 객체가
사용하는 의존 객체를 직접 만들어 사용하는게 아니라, 주입 받아 사용하는 방법을 말 한다.

스프링 IoC 컨테이너가 하는 주요한 기능은 "빈(bean)을 만들고 엮어주며 제공해주는 것"(빈의 생성, 빈의 의존성 주입, 빈의 스코프 관리)이다.

2. BeanFactory

스프링 IoC 컨테이너의 가장 최상위에 있는 인터페이스는 BeanFactory이다. 스프링 공식 문서에 보면 BeanFactory의 요점을 다음과 같이 정의한다.

"이 접근 방식의 요점은 BeanFactory가 애플리케이션 구성 요소의 중앙 레지스트리이며 애플리케이션 구성 요소의 구성을 중앙 집중화한다는 것이다(예를 들어 개별 개체가 더 이상 속성 파일을 읽을 필요가 없음)."

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactory.html

즉, 요약하면 다음과 같은 역할을 한다.

  • 애플리케이션 컴포넌트의 중앙 저장소.
  • 빈 설정 소스로 부터 빈 정의를 읽어들이고, 빈을 구성하고 제공한다.

그렇다면 빈은 무엇인가?

  • 스프링 IoC 컨테이너가 관리 하는 객체.

또한 빈을 등록하는 이유는 다음과 같다.

  • 의존성 관리 : 의존성 관리, 주입을 해주기 위해서는 스프링 IoC 컨테이너에 빈으로 등록되어야 한다.
  • 빈의 스코프 관리 :
    - 싱글톤(default) : 하나, 객체를 재사용하므로 메모리나 런타임시 성능 측면에서 좋음 특히 데이터베이스나 리포지토리 객체를 싱글톤으로 만든다.
    - 프로토타입 : 매번 다른 객체
  • 라이프사이클 인터페이스를 지원 : 빈이 만들어졌을 때 추가적인 작업을 할 때, 또는 스프링자체에서 라이프사이클 콜백을 사용해 부가적인 기능을 만들어내기도 함

라이프사이클 인터페이스 예시


@Service
public class BookService {

    private BookRepository myBookRepository;

    public BookService(BookRepository bookRepository){
        this.bookRepository = bookRepository;
    }
    
    public Book save(Book book) {
        book.setCreated(new Date());
        book.setBookstatus(BookStatus.DRAFT);
        return bookRepository.save(book);
    }

    @PostConstruct
    public void postConstruct(){
        System.out.println("=================");
        System.out.println("Hello");
    }
}

빈 팩토리 라이프 사이클
https://velog.io/@jsj3282/IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-ApplicationContext-BeanFactory

3. ApplicationContext

ApplicationContext는 BeanFactory를 상속받아서 BeanFactory의 역할을 할 수 있으며 그 외에 추가적인 기능을 할 수 있다.

(https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html)

다음은 스프링 공식 문서에 나와있는 ApplicationContext가 제공하는 기능이다.

  1. 애플리케이션 컴포넌트에 액세스 하기 위한 Bean 팩토리 메소드
  2. 일반적인 방식으로 파일 리소스를 로드하는 기능
  3. 등록된 리스너에 이벤트를 게시하는 기능
  4. 국제화를 지원하기 위해 메시지를 분석하는 기능
  5. 상위 컨텍스트에서 상속

4. ApplicationContext와 다양한 빈 설정 방법

스프링 IoC 컨테이너의 역할

  • 빈 인스턴스 생성
  • 의존 관계 설정
  • 빈 제공

빈 설정 파일

  • 빈 명세서
  • 빈에 대한 정의를 담고 있다.
    - 이름
    - 클래스
    - 스코프
    - 생성자 아규먼트 (constructor)
    - 프로퍼티 (setter)

빈 설정 파일 만드는 방법
1. ClassPathXmlApplicationContext (XML)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="bookService" class="com.example.springtest.BookService" scope="singleton" autowire="default">
        <property name="bookRespository" ref="bookRepository"></property>
    </bean>
    <bean id="bookRepository" class="com.example.springtest.BookRespository" scope="singleton" autowire="default"></bean>
</beans>
@SpringBootApplication
public class SpringtestApplication {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println(Arrays.toString(beanDefinitionNames));
        BookService bookService = (BookService) context.getBean("bookService");
        System.out.println(bookService.bookRespository != null);
    }
}

일일이 빈으로 등록하는 것이 번거롭기 때문에 xml 파일에서 <context:component-scan/>를 사용하기도 한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.example.springtest"></context:component-scan>
</beans>

그러면 <context:component-scan/>에 작성된 패키지의 하위 클래스들에서 @Component라는 메타 애노테이션이 붙은 애노테이션(ex) @Service)이 붙은 클래스 파일을 스캐닝해서 빈으로 등록해준다. 또, 의존성 주입은 @Autowired라는 애노테이션을 사용한다.

//@Component
@Service
public class BookService {

    @Autowired
    BookRespository bookRespository;

    public void setBookRespository(BookRespository bookRespository){
        this.bookRespository = bookRespository;
    }
}
@SpringBootApplication
public class SpringtestApplication {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println(Arrays.toString(beanDefinitionNames));
        BookService bookService = (BookService) context.getBean("bookService");
        System.out.println(bookService.bookRespository != null);
    }
}
  1. AnnotationConfigApplicationContext (Java)
@Configuration
public class ApplicationConfig {
    
    @Bean
    public BookRespository bookRepository(){
        return new BookRespository();
    }
    
    @Bean
    public BookService bookService(){
        BookService bookService = new BookService();
        bookService.setBookRespository(bookRepository());
        return bookService;
    }
}

또는

@Configuration
public class ApplicationConfig {
   
    @Bean
    public BookRespository bookRepository(){
        return new BookRespository();
    }
    @Bean
    public BookService bookService(BookRespository bookRespository){
        BookService bookService = new BookService();
        bookService.setBookRespository(bookRespository);
        return bookService;
    }
    
}
@SpringBootApplication
public class SpringtestApplication {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println(Arrays.toString(beanDefinitionNames));
        BookService bookService = (BookService) context.getBean("bookService");
        System.out.println(bookService.bookRespository != null);

    }
}

그렇다면 자바 설정 파일에서는 일일히 빈들을 번거롭게 등록해줘야 하는가?

그럴 필요 없이 @ComponentScan이라는 애노테이션을 사용하면 된다.

컴포넌트 스캔

  • 설정 방법
    - XML 설정에서는 context:component-scan
    - 자바 설정파일에서 @ComponentScan
  • 특정 패키지 이하의 모든 클래스 중에 @Component 애노테이션을 사용한 클래스를 빈으로 자동으로 등록 해 줌.
@ComponentScan(basePackages = "com.example.springtest")
@Configuration
public class ApplicationConfig {

}

위에서는 basePackages를 사용해 이 패키지부터 이 패키지의 하위패키지까지 컴포넌트 스캐닝 했다. 또는 basePackages 말고 basePackageClasses를 사용할 수도 있다. basePackageClasses를 사용하면 명시한 클래스가 위치한 곳부터 컴포넌트 스캐닝을 하게 된다. 마찬가지로 @Component 메타애노테이션이 붙은 애노테이션이 붙은 클래스를 스캐닝한다.

@Configuration
@ComponentScan(basePackageClasses = SpringtestApplication.class)
public class ApplicationConfig {
}

//@Component
@Service
public class BookService {

    @Autowired
    BookRespository bookRespository;

    public void setBookRespository(BookRespository bookRespository){
        this.bookRespository = bookRespository;
    }
}
@Repository
public class BookRespository {
}

물론 스프링부트를 사용한다면 빈 설정 파일, ApplicationContext를 직접 만들지 않고 @SpringBootApplication 애노테이션을 사용하여 스프링 부트가 ApplicationContext를 만들어준다.

@SpringBootApplication
public class SpringtestApplication {

    public static void main(String[] args) {

    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

따라서 이 자체가 빈 설정 파일이 된다.

참고

  • 인프런 : 스프링 프레임워크 핵심 기술(백기선)

좋은 웹페이지 즐겨찾기