Spring - 생성자 Injection 사용 이유

우야·2021년 7월 9일
0

필드 주입

  • @Autowired 어노테이션을 붙여주면 자동으로 의존성이 주입이 됨
@Component
public class MadExample {

    @Autowired
    private HelloService helloService;
}

Setter 주입

  • 꼭 setter 메서드일 필요는 없다. 메서드 이름이 수정자 네이밍 패턴(setXXXX)이 아니어도 동일한 기능을 하면 된다
@Component
public class MadExample {

    private HelloService helloService;

    @Autowired
    public void setHelloService(HelloService helloService) {
        this.helloService = helloService;
    }
}

생성자 주입

  • 스프링 프레임워크 4.3 버전부터 단일 생성자인 경우에는 @Autowired 어노테이션 조차 붙이지 않아도 되지만 생성자가 2개 이상인 경우에는 생성자에 어노테이션을 붙여주어야 함
@Component
public class MadExample {

    // final로 선언할 수 있는 보너스
    private final HelloService helloService;

    // 단일 생성자인 경우는 추가적인 어노테이션이 필요 없다.
    public MadExample(HelloService helloService) {
        this.helloService = helloService;
    }
}

왜 생성자 주입을 권장하나?

  1. 순환 참조를 방지할 수 있다.
  • 순환 참조가 발생하는 경우 애플리케이션이 구동되지 않는다.
@Service
public class MadPlayService {

    // 순환 참조
    @Autowired
    private MadLifeService madLifeService;

    public void sayMadPlay() {
        madLifeService.sayMadLife();
    }
}

@Service
public class MadLifeService {
    
    // 순환 참조
    @Autowired
    private MadPlayService madPlayService;

    public void sayMadLife() {
        madPlayService.sayMadPlay();
    }
}

//위의 경우 실행은되나 runtime error발생됨
java.lang.StackOverflowError: null
	at com.example.demo.GreetService.sayGreet(GreetService.java:12) ~[classes/:na]
	at com.example.demo.HelloService.sayHello(HelloService.java:12) ~[classes/:na]
	at com.example.demo.GreetService.sayGreet(GreetService.java:12) ~[classes/:na]
	at com.example.demo.HelloService.sayHello(HelloService.java:12) ~[classes/:na]
	at com.example.demo.GreetService.sayGreet(GreetService.java:12) ~[classes/:na]
@Service
public class MadPlayService {
    private final MadLifeService madLifeService;

    public MadPlayService(MadLifeService madLifeService) {
        this.madLifeService = madLifeService;
    }

    // 생략
}

@Service
public class MadLifeService {
    private final MadPlayService madPlayService;

    public MadLifeService(MadPlayService madPlayService) {
        this.madPlayService = madPlayService;
    }

    // 생략
}

// 컴파일 단계에서 에러발생
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  madLifeService defined in file [~~~/MadLifeService.class]
↑     ↓
|  madPlayService defined in file [~~~/MadPlayService.class]
└─────┘
  • 생성자, setter, field 주입의 라이프사이클이 다르다.
    • setter, field는 빈을 먼저 생성하고 field나 setter에 주입해준다.
    • 생성자는 생성당시에 빈을 생성하려고 하는데, 이때 서로 생성되지 않은것을 참조하려고 하기때문에 Exception을 발생시킬수 있다.
  • 순환참조는 설계부터가 잘못된것이기때문에 다시 구조를 잡아야한다.
  1. 테스트 코드 작성이 편리하다.
  • 단순 POJO를 이용한 테스트 코드를 만들 수 있다.
  • Mockito(@Mock과 @spy 같은)를 적절히 섞어서 테스트를 할 수 있지만 물론 아래처럼 생성자 주입을 사용한 경우 매우 간단한 코드를 만들 수 있다.
SomeObject someObject = new SomeObject();
MadComponent madComponent = new MadComponent(someObject);
madComponent.someMadPlay();
  1. 코드 품질을 좋게 할 수 있다.
  • 조금 더 품질 좋은 코드를 만들 수 있다.
  • 너무 많은 의존성에 코드가 있는경우 필요 없는것을 없앨수도 있고, 구조를 다시 잡아야할필요성도 있기때문에 생성자로 구현해야하는 불편함 때문에 코드를 다시 확인해 볼수 있다.
@Component
public class MadComponent {
    // 물론 이런 경우는 거의 드물겠지만...
    @Autowired
    private FirstComponent firstComponent;
    @Autowired
    private SecondComponent secondComponent;
    @Autowired
    private NumberComponent numberComponent;
    @Autowired
    private SomeComponent someComponent;
    @Autowired
    private StrangeComponent strangeComponent;
}
  1. immutable 하다.
  • final을 사용할 수 있음
  • 실행 중에 객체가 변하는 것을 막을 수 있다.
  • 오류를 사전에 방지할 수 있다.
@Service
public class MadPlayService {
    private final MadPlayRepository madPlayRepository;

    public MadPlayService(MadPlayRepository madPlayRepository) {
        this.madPlayRepository = madPlayRepository;
    }
}


//또는 잘못된 변경을 막을수 있음
@Service
public class MadPlayService {
    @Autowired
    private MadPlayRepository madPlayRepository;

    public void someMethod() {
        // final이 아니기 때문에 값을 변경할 수 있다.
        madPlayRepository = null;
        madPlayRepository.call();
    }
}

//final 사용에 null을 넣으려고 해서 Error를 발견하기 쉬움
@Service
public class MadPlayService {
    private final MadPlayRepository madPlayRepository;

    public MadPlayService(MadPlayRepository madPlayRepository) {
        this.madPlayRepository = madPlayRepository;
    }

    public void someMethod() {
        // cannot assign a value to final variable
        madPlayRepository = null;
    }
}

https://madplay.github.io/post/why-constructor-injection-is-better-than-field-injection

profile
Fullstack developer

0개의 댓글