스프링 프레임워크 IoC Container와 bean 설정
12 Feb 2022백기선님의 ‘스프링 프레임워크 핵심 기술’ 강의 정리
IoC container
IoC(Inversion of Control)
- 의존 관계 주입(Dependency Injection)
- 어떤 객체가 사용하는 의존 객체를 직접 만들어 사용하지 않고, 주입받아 사용하는 방법
- 꼭 스프링이 없어도 그런 기능을 제공하는 장치가 있으면 가능
Spring IoC Container
- BeanFactory: 스프링 빈 컨테이너의 핵심적인 기본 인터페이스
- 애플리케이션 컴포넌트의 중앙 저장소
- bean 설정 소스로부터 정의를 읽어들이고 구성하고 제공
- 초기에는 xml 파일로 설정했지만, 현재는 어노테이션으로 설정하고 주입 받음
@Service
,@Repository
,@Controller
,@Compnent
,@Autowired
등
Bean
- 스프링 IoC 컨테이너가 관리하는 객체
- 어노테이션으로 등록된 객체만 스프링 빈
- 의존성 주입이 필요한 객체는 빈으로 등록해야 함
- 빈으로 등록된 객체만 IoC 컨테이너에 의해 의존성 주입을 받을 수 있음
- 빈으로 등록되면 scope 설정 가능
- singleton: 하나의 객체(메모리 효율적 사용, 런타임 시 성능 최적화 유리)
- prototype: 매번 다른 객체
- 빈으로 등록하는 어노테이션의 기본 설정값은 singleton
- 하나의 객체만 필요한 경우에도 스프링 빈으로 등록
- Lifecycle 인터페이스 지원
- 빈에 대한 추가적인 작업 가능
@PostConstruct
,@PreDestroy
ApplicationContext
- BeanFactory를 상속받은 인터페이스
- BeanFactory 기능을 가지면서 추가적인 기능 제공
- 메세지 소스 처리 기능(i18n)
- 메세지 다국화
- 이벤트 발행 기능
- 리소스 로딩 기능
- 클래스 패스에 있는 특정 파일, 파일 패스에 있는 특정 파일 등을 읽어오는 기능
xml으로 spring bean 설정
스프링 앱을 실행할 수 있는 파일에서 @SpringBootApplication
어노테이션과 main 메소드에서 스프링 관련 코드를 제거한다. BookRepository, BookService 클래스를 만들고 이 두 객체를 xml에서 직접 스프링 빈으로 등록한다.
public class SpringCoreApplication {
public static void main(String[] args) {
}
}
// BookService
public class BookService {
BookRepository bookRepository;
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
- Xml 설정
<bean/>
으로 등록id
는 camel caseclass
는 클래스의 패스scope
는 기본 설정은 singleton이고 prototype, request, session도 가능autowire
는 기본 설정은 default이고 byName, byType, constructor, no도 가능<property/>
로 의존성 주입name
은 BookService의 setter에서 가져옴ref
는 bean의 id 값
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookService" class="com.example.springcore.BookService">
<property name="bookRepository" ref="bookRepository"/>
</bean>
<bean id="bookRepository" class="com.example.springcore.BookRepository"/>
</beans>
Main 메소드에서 직접 스프링 빈을 가져와서 확인해보도록 한다. bookService.bookRepository가 null이 아니라는 것을 확인함으로서 의존성이 주입된 것을 알 수 있다.
import java.util.Arrays;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringCoreApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
System.out.println(Arrays.toString(beanDefinitionNames));
// [bookService, bookRepository]
BookService bookService = (BookService)context.getBean("bookService");
System.out.println(bookService.bookRepository != null);
// true
}
}
의존성 주입은 잘 되지만 너무나 번거로운 방법이다. 그래서 빈을 하나씩 설정하지 않고 특정 범위를 스캔해서 빈으로 주입하는 설정을 할 수 있다. <context:component-scan base-package=""/>
로 위의 <bean/>
을 대체하면 base-package 안에서 어노테이션을 스캔해서 빈으로 등록하게 된다.
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.springcore"/>
</beans>
그리고 BookService, BookRepository에는 어노테이션을 추가해주도록 한다. 위의 설정으로 인해 @Component
어노테이션이 있으면 빈으로 등록하게 되는데 이 어노테이션을 확장한 @Service
, @Repository
, @Controller
등의 어노테이션을 적절하게 추가하도록 한다. 그리고 BookService에서는 BookRepository를 주입받을 수 있도록 @Autowired
어노테이션을 추가한다.
// BookRepository
@Repository
public class BookRepository {
}
// BookSerice
@Service
public class BookService {
@Autowired
BookRepository bookRepository;
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
Java 파일로 spring bean 설정
컴포넌트 스캔을 해도 xml을 작성하는 일은 매우 번거롭다. 자바 파일로 빈 설정을 해보도록 한다. 위에서 추가한 @Service, @Repository, @Autowired 어노테이션은 모두 지우고 자바 설정 파일에서 빈을 추가한다. @Configuration
어노테이션이 있어야 스프링이 자바 파일을 기반으로 설정하게 된다. xml과 비슷하게 여기서는 @Bean
어노테이션으로 스프링 빈을 등록한다. 의존성 주입 또한 setter 메서드를 이용해 쉽게 할 수 있다. BookRepository가 빈으로 등록되었기 때문에 BookService에서 @Autowired
어노테이션을 통해 의존성을 주입해도 된다.
// ApplicationConfig
@Configuration
public class ApplicationConfig {
@Bean
public BookRepository bookRepository() {
return new BookRepository();
}
@Bean
public BookService bookService() {
BookService bookService = new BookService();
bookService.setBookRepository(bookRepository());
return bookService;
}
}
Main 메소드에서 xml에서 설정을 읽어오게 되어 있는데 위의 파일을 읽어오도록 수정한 후 앱을 실행하면 동일하게 동작한다.
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringCoreApplication {
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.bookRepository != null);
}
}
Xml 작성보다는 덜 번거롭지만 그래도 @Bean으로 일일이 등록하는 것은 여전히 귀찮은 일이다. xml에서 컴포넌트 스캔을 했던 것처럼 자바 파일에서도 컴포넌트 스캔을 해보도록 하자. @ComponentScan
어노테이션을 추가해서 basePackages에 패키지 경로를 작성해도 좋지만 오타의 가능성이 있으므로 좀 더 편리하게 basePackageClasses를 사용해 해당 클래스가 있는 곳부터 컴포넌트 스캔을 하도록 한다. 이번에도 특정 어노테이션들을 스캔해서 빈으로 등록하게 된다. BookService, BookRepository에는 다시 어노테이션을 추가해주도록 한다.
// ApplicationConfig
@Configuration
@ComponentScan(basePackageClasses = SpringCoreApplication.class)
public class ApplicationConfig {
}
// BookService
@Service
public class BookService {
@Autowired
BookRepository bookRepository;
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
// BookRepository
@Repository
public class BookRepository {}
스프링 부트에서는 자바 파일로 설정하고 컴포넌트 스캔한 것도 @SpringBootApplication
어노테이션을 이용해 해결할 수 있다. @Configuration
, @ComponentScan
어노테이션을 모두 포함하고 있기 때문이다. 그래서 이 어노테이션 하나만 있으면 위의 ApplicationConfig 파일은 필요없다.
@SpringBootApplication
public class SpringCoreApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCoreApplication.class, args);
}
}