IoC는 Inversion of Control의 약어로 제어의 역전이라는 뜻을 가지고 있습니다. 다시 말해 객체를 제어한다는 이야기입니다.
그럼 이러한 객체의 제어를 누가 하느냐? 개발자가 직접적으로 객체의 생명주기와 객체와 객체들 간의 제어를 수행하는 것이 아니라 IoC 컨테이너가 이를 수행하는 것 입니다.
Ioc가 객체들을 관리해줌으로써 객체 간의 결합도를 줄일수 있고, 중복 코드를 줄이고 코드의 가독성을 높일 수 있습니다.
그래서 Spring의 IoC 컨테이너의 객체는 어떤 객체를 말하는 걸까요?
바로 Spring Container에 생성된 Bean들을 말하는 것입니다. Spring의 Bean을 생성하는 방법은 객체에 어노테이션을 붙여서 생성할 수 있습니다.
Spring 프로젝트를 생성하게 되면 최상위 클래스에 @SpringBootApplication을 보셨을 겁니다.
@SpringBootApplication 어노테이션을 타고 들어가게되면 수 많은 어노테이션 중에 @ComponentScan이 존재하는데요. @ComponentScan의 역할은 IoC 컨테이너의 등록될 Bean들을 탐색하는 역할을 합니다.
@ComponentScan의 탐색 범위는 @SpringBootApplication의 최상위부터 가장 최하위에 위치한 class들을 탐색하여 Bean을 등록하게 됩니다.
그렇다면 @ComponentScan이 탐색하는 대상은 어떠한 Class들 일까요?
개발하다보면 가장 많이 볼 수 있는 @Component, @Controller, @Service, @Repository 등 Stereotype으로 지정하는 모든 어노테이션이 대상입니다. Sterepotype의 어노테이션들은 @Component를 포함하고 있는데, 추가적으로 Bean을 등록할 객체가 있으면 @Component를 붙여서 사용하면 됩니다.
@Configuration을 붙힌 Class를 생성하여 Bean들을 정의할 수 있습니다.
다시 말해 @Controller, @Service처럼 하나의 Class를 Bean으로 생성할 수 있지만 @Configuration이 붙은 Class에서는 여러개의 객체를 Bean으로 등록할 수 있습니다.
예를 들어 특정 객체들을 Bean으로 생성하여 IoC 컨테이너에서 Singleton 객체로 사용하고 싶은 경우를 보겠습니다.
@Configuration
public class SpringConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
위와 같이 개발자가 특정 객체를 Bean으로 지정해서 사용할 수 있습니다.
DI는 의존성 주입이라는 뜻 입니다. 위에서 IoC에 등록한 Bean에게 또 다른 Bean을 주입 받아 사용하는 것을 의미 합니다. 의존성을 주입하는 방법에는 3가지 방법이 있습니다.
생성자 주입은 말 그대로 생성자를 통해서 의존성을 주입 받는 것입니다. 주입 받을 객체를 IoC로부터 Parameter로 전달 받아서 의존성을 부여 받을 객체에게 주입하는 방식입니다.
@Controller
public class SpringController {
private final SpringService springService;
public SpringController(SpringService springService) {
this.springService = springService;
}
}
생성자 주입은 Spring Reference에서 가장 권장하는 방법입니다. 앞에 final 타입을 붙이는 이유는 객체의 불변성을 뜻하고 Class 내부에서 의존성 주입받은 객체를 수정하는 문제를 방지할 수 있습니다.
생성자 주입을 할 경우, 객체와 객체간의 순환 참조를 방지할 수 있습니다. SpringService에서도 SpringController를 의존성 주입을 하게되면 컴파일시 오류를 발견하게 되고 Server가 실행되지 않습니다.
@Autowired 주입은 어노테이션을 이용하여 쉽게 의존성을 주입하는 방법입니다.
@Controller
public class SpringController {
@Autowired
private SpringService springService;
}
@Autowired 주입은 생성자 주입보다는 소스 코드도 줄어들고 어노테이션만 붙이면 의존성을 주입받을 수 있습니다.
하지만 위에서 생성자 주입시 말한 final 레퍼런스타입을 지정하지 못하여 불변성이 깨지게 되고,
순환 참조가 발생되어도 컴파일시 오류를 발견하지 않은 채로 서버가 실행되는 단점이 있습니다.
Setter주입은 객체의 생성자 호출 또는 팩토리 메서드를 호출을 통해서 객체의 의존성을 주입하는 방법입니다.
@Controller
public class SpringController {
private SpringService springService;
public void setSpringController(SpringService springService) {
this.springService = springService;
}
}
Setter 주입같은 경우는 특정 상황에 따라 사용하기 좋을 것 같습니다. 단점으로는 setter를 통해 주입하지 않아도 Controller 객체가 생성되기 때문에 주입하려는 대상이 setter로 인해 주입되지 않은 상태라면 Service 호출 시 NullpointException이 발생합니다.
그래서 Setter는 사용하려는 모든 시점에 Null 검사를 하여 사용해야 하는 번거로움이 있습니다.