A A
필드주입과 생성자주입의 차이
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() {      }  }

 

 

마무리

위와 같은 여러 이유로 앞으로 스프링 프레임워크에서는 필드 주입이 아닌 생성자 주입을 권장한다. 생성자 주입의 장점만 작성되어있지만 실제로 필드주입과 비교하여 생성자 주입의 단점은 존재하지 않는다.

 

 

반응형
Copyright 2024. GRAVITY all rights reserved