필드주입과 생성자주입의 차이

반응형
728x90
반응형

스프링 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

Designed by JB FACTORY