의존성 주입(Dependency Injection, DI)
의존성 주입을 해야 하는 이유는 아래와 같다.
외부에서 객체를 생성하여 가져오기 때문에 객체 간의 의존성을 줄이거나 없앨 수 있다.
또한 객체 간의 결합도를 낮추면서 유연한 코드를 작성할 수 있다. 자세한 내용은 아래를 참고해주세요
https://okimaru.tistory.com/113
Spring에서는 @Autowired 를 사용하는 필드 주입이나 수정자 주입 방법보다 생성자 주입을 권장하는 이유가 있다.
일단 의존성을 주입하는 3가지 방법에 대해 알아본다.
생성자 주입(Constructor Injection)
아래는 메뉴 정보를 불러오기 위한 테스트 코드이다.
생성자로 주입 받을 객체가 빈으로 등록되어 있고 클래스의 생성자가 하나라면 @Autowired 어노테이션 없이
생성자 주입을 받을 수 있다.
@Controller
@RequestMapping(value = "/manager")
public class ManagerMainController {
// final 선언
private final MenuListService menuListService;
public ManagerMainController(MenuListService menuListService) {
this.menuListService = menuListService;
}
}
필드 주입(Field Injection)
필드에 @Autowired 어노테이션을 사용하여 필드 주입을 한다.
같은 타입의 빈이 존재할 경우 @Qualifier, @Resource 어노테이션을 통해 빈 이름을 호출하여 주입할 수 있다.
생성자 주입과 달리 필드를 final로 정의할 수 없다.
@Controller
@RequestMapping(value = "/manager")
public class ManagerMainController {
@Autowired
private MenuListService menuListService;
}
단점
- 코드가 간결하지만 외부에서 변경하기 힘들다.
- 프레임워크에 의존적이고 객체지향적으로 좋지 않다.
수정자 주입(Setter Injection)
Setter 메소드에 @Autowired 어노테이션을 붙이는 방법이다. 또한 이 주입방식도 필드에 final을 붙일 수 없다.
@Controller
@RequestMapping(value = "/manager")
public class ManagerMainController {
private MenuListService menuListService;
@Autowired
public void setMenuListService(MenuListService menuListService) {
this.menuListService = menuListService;
}
}
생성자 주입을 권장하는 이유
1. 순환 참조를 방지할 수 있다.
개발을 하다보면 여러 컴포넌트 간에 의존성이 생긴다. A 클래스가 B 클래스를 참조하고 B가 다시 A를 참조하는 코드가
있다고 가정해봅니다.
아래와 같이 순환 참조 구조가 있습니다.
TestServiceA 와 TestServiceB 는 @Service 어노테이션으로 인해 Bean 을 등록한다.
@Autowired 로 인해 필드 주입을 통해 TestServiceA 와 TestServiceB의 객체를 서로 생성한다.
그 후에 메서드가 호출되면 서로가 서로를 호출하기 때문에 순환 참조 형태가 된다.
서로 계속해서 호출하기 때문에 Stack 이 가득 차 StackOverFlowError 를 발생시키고 어플리케이션은 죽는다.
@Service
public class TestServiceA {
// 순환 참조
@Autowired
private TestServiceB Serviceb;
public void HelloA() {
Serviceb.HelloB();
}
}
@Service
public class TestServiceB {
// 순환 참조
@Autowired
private TestServiceA Servicea;
public void HelloB() {
Servicea.HelloA();
}
}
여기서 문제가 되는건 어플리케이션이 구동될 때 아무런 오류가 나지 않는다. 실제 코드에 의해 호출되기 전까지 문제를
찾을 수 없다.
이유는 필드 주입과 수정자 주입은 생성자 주입과 빈을 주입하는 순서가 다르다.
필드 주입과 수정자 주입은 먼저 객체를 생성한 후 주입하려는 빈을 찾아 주입한다.
하지만 생성자 주입은 어플리케이션이 구동되는 시점에 클래스가 Bean 등록 되고
생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 만든다.
그 후에 찾은 인자의 빈으로 주입하려는 빈의 생성자를 호출한다.
즉, 빈을 먼저 생성하지 않고 주입하려는 빈을 먼저 찾는다.
따라서 생성자 주입 방식은 순환 참조를 어플리케이션 구동과 동시에 찾을 수 있다.
서로 참조하는 객체가 생성되지 않은 상태에서 그 빈을 찾기 때문에 오류가 발생한다.
이러한 순환 참조를 방지 하기 위해선 생성자 주입을 사용하는것이 좋다.
2. final 키워드 선언 가능
필드 주입과 수정자 주입은 필드를 final 로 선언할 수 없다. 이 말은 나중에 변경될 수 있다는 뜻이다.
하지만 생성자 주입은 final 로 선언할 수 있으므로 객체 불변성을 보장한다.