필드주입과 생성자주입의 차이
- Coding/Spring
- 2019. 2. 15.
스프링 DI (Dependency Injection)란?
자바에서 객체를 생성하기 위해서는 Test test = new Test(); 을 사용한다. new 연산자를 사용하여 인스턴스를 생성하는 행위이다. 이와 다르게, DI는 위 코드처럼 객체를 생성시키는게 아닌, 외부에서 객체를 생성해서 주입시켜주는 방식이다. DI는 필요한 객체를 해당 .java 파일 안에서 new 연산자를 통해 생성하는 것이 아닌, 외부에서 생성된 객체 중 필요한 객체를 연결하는 것이다.
필드 주입
결론적으로, 필드 주입을 사용하는 것은 추천하지 않는다.
@Autowired private Mapper mapper;
위 코드를 IntelliJ IDE에서 사용하면 @Autowired 에 경고가 뜨게된다.
Field injection is not recommended
Inspection info: Spring Team recommends: “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”
위와 같은 메세지를 보여주는데, “Always” 라는 단어를 쓸 정도로 강조하는 이유를 계속해서 알아보자.
생성자 주입일때의 장점
우선, 간단한 장점 3가지는 아래와 같다.
1. 변경이 불가능한 immutable 상태로 선언이 가능하다.
생성자의 파라미터를 통해 의존관계를 한눈에 파악할 수 있고 리팩토링의 필요성을 얻을 수 있다.
(Constructor의 파라미터가 많아짐과 동시에 하나의 클래스가 많은 책임을 떠안는다는 걸 알게된다.)
2. 필드 주입일때에 null 값이 되는게 가능하기 때문에 NullPointerException 발생 위험이 있는데, 생성자 주입은 null 이 불가능하다.
(Final 변수 선언)
지금부터 소개하는 장점은 우리가 필드 주입이 아닌 생성자 주입을 사용해야하는데에 좀더 설득력 있는 내용이다.
순환참조의 발생을 알 수 있다.
DeptServiceImpl.java @Service public class DeptServiceImpl implements DeptService { private final DeptRepository deptRepository; private final TestService testService; public DeptServiceImpl(DeptRepository deptRepository, TestService testService) { this.deptRepository = deptRepository; this.testService = testService; } @Override public List<DeptEntity> getDeptList() { return deptRepository.findAll(); } }
단일 생성자일때에, 생성자 코드 위에 @Autowired 는 생략이 가능하다. (Spring 4.3부터)
lombok을 사용하면 생성자 코드를 생략하고 클래스명 위에 @RequiredArgsConstructor를 사용할 수 있다.
@RequiredArgsConstructor public class DeptServiceImpl implements DeptService { private final DeptRepository deptRepository; private final TestService testService; }
TestServiceImpl.java @Service public class TestServiceImpl implements TestService { private final DeptService deptService; public TestServiceImpl(DeptService deptService) { this.deptService = deptService; } }
예제를 보면, DeptServiceImpl.java 에서 TestService.java를 생성자주입 하고있다.
그리고, TestServiceImpl.java에서 DeptService.java를 주입하고 있다.
DeptServiceImpl -> TestService
TestServiceImpl -> DeptService
위 코드를 실행하면 아래와 같은 에러가 발생한다.
The dependencies of some of the beans in the application context form a cycle: deptController defined in file [...\controllers\DeptController.class] ┌─────┐ | deptServiceImpl defined in file [...\services\impl\DeptServiceImpl.class] ↑ ↓ | testServiceImpl defined in file [...\services\impl\TestServiceImpl.class] └─────
생성자 주입을 사용하지 않으면, 해당 코드를 호출하기 전까지는 순환참조를 알수가 없다. 따라서 생성자 주입을 통해 런타임시 해당 에러를 잡을 수 있다. 컨테이너가 빈을 생성하는 시점에서 객체간 순환참조를 하고있음을 알기 때문이다.
final 을 사용하여 불변하다.
private final TestService testService;
위 final 연산자를 사용하였기 때문에 누군가가 해당 파일 안에서 testService를 조정할 수 없다. 예를들어, 드물겠지만 개발자가 실수로 testService = null; 이라는 코드를 넣으면 에러가 발생하여 미리 인지할 수 있다.
테스트 코드 작성에 용이하다.
@Service @RequiredArgsConstructor public class DeptServiceImpl implements DeptService { private final DeptRepository deptRepository; private final TestService testService; @Override public List<DeptEntity> getDeptList() { return deptRepository.findAll(); } }
DeptServiceImpl.java의 getDeptList() 메서드를 테스트하려고 한다. 필드주입을 사용한다면 객체 생성만으로 테스트코드 안에서 의존관계를 주입할 수가 없다. 필드주입을 사용할시, 테스트 코드에서는 아래 코드처럼 @Mock을 사용하여 주입을 해야한다.
@RunWith(MockitoJUnitRunner.class) { public class TestServiceImplTest @Mock private DeptService deptService; @Test public void methodTest() { } }
생성자 주입을 사용할시, 아래처럼 객체를 생성할때 주입이 가능하다.
public class TestServiceImplTest { private Deptservice deptService; private TestSerciceImpl testServiceImpl; @Before public void setUp() { deptService = new Deptservice(); testServiceImpl = new TestSerciceImpl(deptService); } @Test public void methodTest() { } }
Junit5 부터는 아래처럼 생성자 주입을 사용할 수 있게되었다.
@TestConstructor(autowireMode = AutowireMode.ALL) class TestServiceImplTest { private final DeptService deptService; public TestServiceImpl(DeptService deptService) { this.deptService = deptService; } @Test void methodTest() { } }
마무리
위와 같은 여러 이유로 앞으로 스프링 프레임워크에서는 필드 주입이 아닌 생성자 주입을 권장한다. 생성자 주입의 장점만 작성되어있지만 실제로 필드주입과 비교하여 생성자 주입의 단점은 존재하지 않는다.
'Coding > Spring' 카테고리의 다른 글
ResponseEntity에 대해 알아보기 (0) | 2020.11.06 |
---|---|
Reactive Spring / Reactive Programming (1) | 2020.01.11 |
싱글톤 레지스트리 (0) | 2018.05.08 |
코드 리팩토링 (0) | 2018.05.03 |
코드의 응집도와 결합도 (0) | 2018.05.03 |