[Spring] 의존성 주입이란? ( Dependency Injection )
Spring의 핵심개념은 의존성 주입(DI)이다.
의존이란,A의 기능이 동작하려면 객체B가 존재해야 함을 의미한다.
예를들어,
Controller는 웹화면을 구성하는 역할을 한다. 화면을 구성하려면 서비스에 맞는 데이터를 전달 받아야 한다. 그러므로 Controller는 데이터를 서비스에 맞게 처리하는 로직을 가지고 있는 Service에 의존한다. Service는 데이터가 필요하므로 데이터를 가져오는 기능을 갖는 Repository에 의존한다.
이처럼, 객체지향구조는 서로 '의존'하고 있는 구조이다. 그러나 의존을 주입하는 코드를 개발자가 직접 구현하면 객체를 직접 관리해야 하고 객체간 결합도가 증가시킬 수 있다. Spring 프레임워크 존재 이유는 여기에 있다. Spring은 컨테이너라는 공간이 있다. 객체를 저장하는 공간이다. 컨테이너에 저장된 객체를 Bean이라 부른다. 많은 객체가 의존하는 객체는 Bean으로 등록하고 필요할 때마다 컨테이너에서 빼오면 된다. 컨테이너에 등록하는 방법은 간단하다. 개발자는 Bean으로 만들고 싶은 객체를 어노테이션으로 '표시'한다. ( @Component, @Bean ) 스프링 프레임워크는 어노테이션을 스캔하여 Bean으로 등록한다.
컨테이너에 Bean이 등록되면 Bean에 의존하는 객체에 주입해야 한다. 이때 주입하는 방법은 3가지가 있다.
1. 생성자 주입
2. 필드 주입 ( @Autowired )
3. 수정자 주입 ( Setter )
3가지 방법에 대한 자세한 내용은 다음 포스팅에서 다루어보겠다. 이번 포스팅에서는 실제로 빈을 생성해서 컨테이너에 등록하고 의존성을 부여하는 과정을 다루어 보겠다.
HelloController
@RequiredArgsConstructor // Service 객체를 생성자 주입방식으로 DI
public class HelloController {
private final HelloService helloService; // Service 객체에 의존!
}
HelloController는 HelloService에 의존한다. 이때 HelloService 객체는 생성자 주입방식으로 주입된다.
Config 클래스
@Configuration
public class configClass {
@Bean
public HelloController helloController(HelloService helloService){
return new HelloController(helloService);
}
@Bean
public HelloService helloService(){
return new HelloService();
}
}
Bean으로 등록하는 Config 클래스이다. 보통 스프링은 컴포넌트 스캔으로 내부에 있는 Bean을 탐색하여 등록하는데, 컴포넌트 스캔 범위 밖에 있는 Bean의 경우, Config 클래스를 만들어 import하는 방식을 사용한다. HelloController와 HelloService는 @Bean으로 등록된다.
HelloController 객체를 Bean으로 등록하려면 HelloService 객체가 필요하다. Spring은 HelloService()를 호출하여 HelloService 객체를 생성하여 Bean으로 생성하고 이를 HelloController 생성자에 주입한다.
Main 클래스
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); // 스프링 컨테이너 생성
ac.register(configClass.class); // 빈등록 컨피그 파일 등록
ac.refresh(); // 스프링 컨테이너 초기화
String[] beanNames = ac.getBeanDefinitionNames(); // 컨테이너에 등록된 빈이름 가져오기
for( String beanName : beanNames ){
BeanDefinition beanDefinition = ac.getBeanDefinition(beanName);
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){ // 개발자가 등록한 어플리케이션 빈만 필터링하기
System.out.println(" Bean 이름 : " + beanName);
}
}
}
}
Main클래스에서 Spring 컨테이너를 생성하고 Cofig 클래스를 등록하여 읽을수 있도록 하였다. 그리고 refresh()를 하여 Bean을 등록해보았다. 그럼 등록된 Bean에 무엇이 있는지 알아보자. 참고로 BeanDefinition 객체는 Bean의 메타정보가 들어있다. BeanDefinition이 ROLE_APPLICATION의 의미는 개발자가 등록한 Bean이라는 의미이다. 그럼 Main클래스를 실행해보겠다.
스프링컨테이너에 Bean이 생성되어 있다. 이제 개발자는 new 연산자로 객체를 직접 생성하지 않아도 생성자 주입, 필드 주입, 수정자 주입으로 원하는 Bean을 냉장고에서 음료수 꺼내 먹듯이 꺼내어 사용하면 된다. 이처럼 Spring Framework는 자칫 결합도를 높힐 수 있는 객체의 생성 및 관리를 담당하여 객체지향설계의 핵심적인 역할을 한다.
다음 포스팅에서는 생성자 주입, 필드 주입 ( @Autowired ), 수정자 주입 ( Setter )에 대해서 다루어 보고 왜 생성자주입방식이 권고되는지 알아보겠다.