저번 포스팅에서 일반적인 상황의 의존성 주입에 대해 공부했다. 이번에는 Spring Framework
에서 사용하는 의존성 주입에 대해 집중하여 다룰 예정이다.
저번 포스팅에서 간단히 다뤘었지만, Spring Framework
에서 의존성 주입을 공부하기 전에 다시 IoC
에 대해 언급하려고 한다.
Inversion of Control(제어의 역전)
, IoC는 객체의 흐름, 생명주기(Life-Cycle) 등을 관리하는 것을 위임하는 방식의 프로그래밍 설계를 의미한다. 결국, 개발 주체가 되는 개발자가 직접 위와 같은 것을 관리하는 것이 아닌 제 3자 Spring
같은 경우엔 Spring Container
에게 이 권한을 위임하여 IoC를 이루게 된다. 이러한 기능을 하는 Container를 IoC Container
라고 일컫는다.
Spring
에서 이 두 개념이 같게 사용이 되고 있고 실제로 같다고 생각하는 사람도 많다. 하지만 이는 사실이 아니고, 따지고 보면 DI가 IoC의 분류 중 하나이다.
DL를 사용하게 된다면 종속성이 많이 생기게 되므로 DI를 많이 사용한다. 그래서 많은 개발자들이 IoC와 DI를 겸해 Spring에서 그 의미를 사용하고 있는 것 같다.
Spring
에서 의존성을 주입하는 방식은 대표적으로 3가지를 들 수 있다.
@Service
public class HelloService {
@Autowired // field injection
private HelloRepository helloRepository;
...
}
@Autowired
어노테이션은 말 그대로 스프링 IoC 컨테이너 안에 있는 객체인 bean
들이 자동으로 연결(wire)이 되게 하는 Spring Framework
의 기능이다.
이러한 Field Injection은 짧은 줄만 기입하면 되기 때문에 편리함을 주지만, Mock 을 주입하기가 어려운 구조이기 때문에 UnitTest가 어려워 사실상 DI의 장점을 살리지 못한다.
그래서 Field Injection은 지양하는 의존성 주입 방식이다.
@Service
public class HelloService {
// setter injection
private HelloRepository helloRepository;
@Autowired
public void setHelloRepository(HelloRepository helloRepository) {
this.helloRepository = helloRepository;
}
...
}
그러나 Spring 3.x
까지만 권장된 방식이고, 그 다음 버전에서는 권장하지 않는 방식이다. 여러 이유가 있을 수 있지만 개발 중간에 어떠한 공격자가 위 코드의 helloRepository
를 교체할 수도 있고, Setter로 인한 의도치 않은 변경이 이루어질 수도 있기 때문에 보안상 보기 좋지가 않다. 그렇기 때문에 Spring 4.x
버전을 사용하고 있는 지금 권장하는 방식은 아니라 할 수 있다.
@Service
public class HelloService {
// Constructor injection
private HelloRepository helloRepository;
@Autowired // Spring 4.3 이후 없어도 spring이 알아서 injection을 해줌.
public HelloService(HelloRepository helloRepository) {
this.helloRepository = helloRepository;
}
...
}
생성자를 이용한 DI는 현재 존재하는 의존성 주입 방법 중 가장 권장되어 있는 방식이다. 생성자가 존재함으로써 Field Injection이 가지는 문제점과 Setter Injection이 가지는 보안적인 문제점을 모두 해결하고 있다.
또한 Spring 4.3
이후 가장 권장되는 방법으로, @Autowired
어노테이션도 생략이 가능하다. 생략을 하더라도, IoC 컨테이너
가 알아서 bean
을 연결해준다.
여기서 lombok
을 사용하면 더 간단한 코드로 정리할 수 있는데, lombok
의 AllArgsConstructor
나 RequiredArgsConstructor
가 생성자를 이용한 DI 조차 해주기 때문에 어노테이션만 클래스에 달아주면 코드가 더 간단해진다.
@Service
@AllArgsConstructor
public class HelloService {
// Constructor injection
private HelloRepository helloRepository;
/* -> 이 과정을 lombok의 AllArgsConstructor가 대체해줌.
@Autowired // Spring 4.3 이후 없어도 spring이 알아서 injection을 해줌.
public HelloService(HelloRepository helloRepository) {
this.helloRepository = helloRepository;
}
*/
...
}
이렇게 기존 Constructor Injection 보다 훨씬 간편한 코드로 작성할 수 있다. 그러나 웬만하면 (상황에 따라 다를 수 있지만) Repository
같은 저장소는 변하지 않아야 하는 성격을 가지기 때문에 final
로 사용하기도 한다.
그런 경우에는 AllArgsConstructor
대신, 초기화 되지않은 final
필드나, @NonNull
이 붙은 필드에 대해 생성자를 생성해 주는 RequiredArgsConstructor
를 사용하는 것이 더 권장된다.
@Service
@RequiredArgsConstructor
public class HelloService {
// Constructor injection
private final HelloRepository helloRepository;
...
}
RequiredArgsConstructor
를 사용한 Constructor Injection은 이러한 모습으로 코드를 단순화할 수 있다.
향후 @Autowired
어노테이션에 대해 자세히 다뤄볼 포스팅을 할 기회가 오면 좋겠다.