스프링을 처음 공부할때 의존성 주입(DI), 제어의 역전(IoC)에 대해 이해하기 힘들었다. 의존성 주입은 단순히 new 이고 제어의 역전은 스프링 컨테이너가 등록된 빈을 주입해주는 것이라고 외웠다. 그 이유를 의존성에 대한 이해가 부족했기 때문이라고 생각한다.
내가 의존성을 접했던건 node 프로젝트에서 npm을 사용했을때이다. npm이라는 의존성 관리 매니저를 사용하면서 외부 라이브러리를 사용하는 것을 의존성이라고 은연중에 생각하게 되었다. 이런 상황에서 스프링의 의존성 주입과 제어의 역전이라는 단어를 들으니 당연히 이해가 안되었던것이다.
의존성이란 간단하게 코드에서 두 모듈간의 연결이고 객체지향의 관점에서는 두 클래스간의 관계라고 할 수 있다. 객체지향 관점의 의존성의 뜻을 알고 나니 IoC와 DI에 대해 이해할 수 있었다.
흔히 의존성 주입을 new 한다. 이게 틀린말은 아닌게 외부 의존성(클래스)를 사용하기 위해 인스턴스를 만들어 주입한다는 의미를 생략한게 저 문장이라고 생각된다.
// ex1
public class RegisterationService{
private MailSender mailSender;
public RegisterationService(){
this.mailSender = new MailSender();
}
// 기타 로직
}
예제 1에서 RegisterationService 클래스는 메일을 보내기 위해 MailSender 클래스에 의존한다. 그 MailSender 클래스의 인스턴스를 생성자 내부에서 new 연산자를 통해 의존성을 주입한다. 의존성 주입의 주체는 RegisterationService 클래스다.
// ex2
public class RegisterationService{
private MailSender mailSender;
public RegisterationService(MailSender mailSender){
this.mailSender = mailSender
}
// 기타 로직
}
예제 2에서 보면 외부에서 전달받아 mailSender 인스턴스로 의존성을 주입한다. 여기서 제어의 역전이 발생했다고 하는데 예제 1에서는 클래스의 생성자 내부에서 직접 의존하는 클래스의 인스턴스를 생성해 주입하는데 반해 예제 2에서는 전달받은 인스턴스를 단순히 멤버변수에 주입하기만 하는 것이다.
자신이 하던 일을 외부에서 해주기 때문에 의존성 주입에 대한 제어의 역전(IoC)이 일어났다고 하는 것이다.
스프링부트에서 의존성을 주입하는 방법은 3가지다.
@Autowired
, @Required
어노테이션을 이용해 의존성을 주입할 수 있다.
자바에서는 생성자에 @Autowired를 명시하거나 혹은 생략하여 의존성을 주입할 수 있다.
public class DiClass{
public Person person;
@Autowired // 생략가능
public DiClass(Person p){
this.person = p
}
}
코틀린은 클래스의 프로퍼티로 의존성을 주입할 클래스를 정의하면 생성자 기반 주입을 할 수 있다.
class DiClass(private val person: Person)
여기서 프로퍼티에 왜 private 접근지정자를 붙인 이유는 클래스 선언에서 프로퍼티에 private 접근자를 붙이면 게터와 세터를 생성하지 않기 때문이다. 스프링에 의해 주입되는 클래스는 읽기전용이고 일반적으로 클래스 외부에서 사용되지 않기 때문에 게터를 정의할 필요가 없기 때문에 private 접근 지정자를 붙인다.
스프링5부터 생성자 기반 주입을 권장하지만 필요에 따라 필드 기반의 주입이 필요한 경우도 있다. 자바에서는 단순히 필드에 @Autowired
를 붙여 구현할 수 있지만 코틀린에서는 생성자나 init 초기화 블럭 내에서만 프로퍼티의 초기화가 가능하기 때문에 필드 기반 주입을 사용하기 위해서는 lateinit을 사용해야한다.
public class DiClass{
@Autowired
public Person person;
}
class DiClass(){
@Autowired
private lateinit var person: Person
}
public class DiClass{
public Person person;
@Autowired or @Required
public void setterDi(Person person){
this.person = person
}
}
class DiClass(){
private var person: Person
@Autowired or @Required
fun setterDi(Person person){
this.person = person
}
}