들어가기에 앞서 본 포스팅은 백기선님의 인프런 강의를 기반으로 작성된 포스팅임을 알려드립니다.
처음 스프링을 배우는 입장이라 정확하지 않은 정보가 있을 수 있습니다. 댓글을 통해 알려주신다면 최대한 빨리 피드백하도록 하겠습니다!
IoC ( Inversion of Control ) / DI ( Depencency Injection )
IoC ( 제어의 역전 ) 라는 말 자체는 개념적인 성격을 지닙니다.
public class BookService {
private BookRepository bookRepository = new BookRepository();
public Book save(Book book){
book.setCreated(new Date());
book.setBookStatus(BookStatus.DRAFT);
return bookRepository.save(book);
}
}
위 코드의 경우 IoC가 적용되지 않은 상태입니다.
BookService 클래스 내부의 BookRepository 객체를 직접 생성하여 사용하는 모습을 확인할 수 있습니다.
이렇게 생성된 bookRepository객체는 BookService를 생성할 때 마다 새롭게 생성되므로, 항상 동일한 객체임을 보장할 수 없습니다.
Repository가 DB에 직접 데이터를 주고받는 역할을 수행하는 것을 생각해봤을 때, 아무래도 위험하게 느껴집니다.
BookService bookService = new BookService();
BookService bookService1 = new BookService();
assertThat(bookService.bookRepository).isEqualTo(bookService1.bookRepository);
실제로 테스트를 작성하여 돌려보면 두 객체 속의 bookRepository가 서로 다른 객체임을 확인할 수 있습니다.
이렇게 항상 같은 객체여야하는 참조변수가 서로 다르게 나타나는 경우를 해결하기 위해 도입된 개념이 IoC입니다.
외부에서 객체를 주입 받음으로써 항상 같은 객체임을 보장받는 것이죠.
주 목적은 하나의 객체를 모든 영역에서 재사용하기 위함이기 때문에 어딘가에 해당 객체들을 모아두고 이를 관리해주는 공간이 필요하다는 생각이 듭니다.
의존성을 주입하는 작업은 굳이 스프링을 사용하지 않더라도 가능합니다.
public BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
이렇게 생성자를 통하여 외부에서 객체를 받아서, 내 필드의 참조변수에 넣어주는 작업을 의존성 주입이라고 부릅니다.
BookRepository bookRepository = new BookRepository();
BookService bookService = new BookService(bookRepository);
BookService bookService1 = new BookService(bookRepository);
assertThat(bookService.bookRepository).isEqualTo(bookService1.bookRepository);
테스트 코드를 작성하고 확인해보면 두 객체가 서로 같은 객체임을 알 수 있습니다.
IoC 컨테이너와 빈
앞서 하나의 객체를 모든 영역에서 재사용하기 때문에 그러한 객체를 모아둘 공간이 필요하다. 라고 말한게 기억나시나요?
바로 스프링에서 이 객체들을 모아두는 공간을 IoC 컨테이너라고 부릅니다.
스프링에서 Bean이라고 불리는, 특정 표식이 달린 객체들을 IoC 컨테이너라는 공간에 모아두고 관리하는거죠.
왜 쓸까?
위에서 생성자를 통한 의존성 주입을 하는 코드를 살펴보았습니다.
이렇게 개발자가 직접 코드를 통해서 의존성을 주입하고, 사용할 수 있는데 왜 IoC 컨테이너와 Bean을 사용하는 걸까요?
그 이유는 편의성과 안정성이라고 생각합니다.
이후에 살펴볼테지만 스프링의 기능을 통하여 객체를 관리하면 애노테이션으로 정말 편리하게 객체들을 관리할 수 있습니다.
게다가 다수의 개발자들이 그간 쌓여온 지식들을 모아서 Spring이라는 프레임워크의 한 기능으로 구현되어 있기 때문에 직접 코드를 작성하면서 실수가 생길 수 있다는 것에 비하여 안정성이 뛰어나다고 생각됩니다.
이 외에도 IoC 컨테이너에 등록된Bean들은 싱글톤 스코프로 등록되기 때문에항상 동일한 객체임을 보장받습니다.
이제 IoC컨테이너의 내부에 Bean라는 이름의 관리받는 객체가 들어있다는 사실을 알게 되었습니다.
이어서 IoC컨테이너 내부에 있는 Bean을 꺼내기 위해서 어떻게 해야하는지를 알아봅시다.
실제로 Bean을 확인하기 위해 사용되는 인터페이스는 ApplicationContext이며, BeanFactory를 상속하고 있습니다.
지금은 기능이 확장된 BeanFactory 정도로 생각하면 되겠습니다.
Bean 등록 방법
앞에서 특정한 표식이 붙은 객체를 Bean이라고 부른다 배웠습니다.
이번엔 이 특정한 표식을 붙히는 방법들에 대해 알아보려고 합니다.
고전적인 방법부터 모두 알아볼 생각이니 원하시는 내용이 아니면 건너뛰시면 될 것 같습니다.
xml 파일을 이용한 방법 ( 사용할 일 적음 )
public class BookService {
public BookRepository bookRepository;
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public Book save(Book book){
book.setCreated(new Date());
book.setBookStatus(BookStatus.DRAFT);
return bookRepository.save(book);
}
}
public class BookRepository {
public Book save(Book book){
return null;
}
}
resource 디렉토리 밑에 application.xml 파일을 만들어 bean을 설정하는 방법입니다.
이런식으로 Component의 하위 애노테이션들을 찾아서 Bean으로 등록하고, Autowired가 붙은 생성자나 필드, 메소드를 찾아서 IoC컨테이너의 Bean을 주입하는 방식으로 작동합니다.
ApplicationContext를 사용하여 Bean들을 확인해보면 제대로 등록되어있는 것을 확인할 수 있습니다.
자바 설정파일을 이용한 방법
@Configuration
public class ApplicationConfig {
@Bean
public BookRepository bookRepository(){
return new BookRepository();
}
@Bean
public BookService bookService(){
return new BookService(bookRepository());
}
}
자바 설정파일을 이용하여 Bean을 등록하는 방법입니다.
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
System.out.println(Arrays.toString(beanDefinitionNames));
}
마찬가지로 bean을 확인해보면 등록되어 있는 것을 확인할 수 있습니다.
ComponentScan을 이용한 방법 ( xml 아님 )
스프링 부트기준으로 프로젝트를 생성하면 만들어지는 @SpringBootApplication의 경우 이 방법을 사용하고 있습니다.
xml파일에서와 마찬가지도 @Component가 붙은 클래스들을 모두 Bean으로 등록해주며, 기본적으로 생성되는 @SpringBootApplication가 붙은 클래스의 하위클래스들을 탐색하여 Bean으로 등록합니다.
@Autowired
생성자로 Bean을 주입받는다.
@Autowired는 IoC컨테이너에 등록된 Bean을 꺼내오는 애노테이션입니다.
이 애노테이션을 달 수 있는 위치는생성자, 메소드, 파라미터, 필드가 있으며런타임 시 까지 유지되는 애노테이션입니다.
@Autowired는 required 속성을 통해 해당 의존성을 반드시 주입받아야하는가? 를 설정할 수 있습니다.
이 옵션을 false로 지정하면, 생성자가 아닌 필드 or 메소드를 통합 주입의 경우 해당 의존성이 IoC컨테이너에 없더라도 객체를 생성할 수 있습니다.