스프링에서 의존성주입(DI)
스프링에서 말하는 의존성 주입이란?
객체간 의존성을 개발자가 직접 객체 내부에서 new연산자를 이용하여 직접 호출하지 않고,
외부, 즉 스프링 컨테이너에서 객체를 생성해서 넣어주는 방식을 의존성 주입(DI) 이라고 합니다.
스프링 의존성 주입의 특징은 인터페이스를 사이에 두어 클래스 레벨에서는 의존관계까 고정되지 않도록 하고,
런타입 시 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 하는 것 입니다.
의존성 주입 3가지 방법
스프링에서는 @Autowired를 사용하여 의존성을 주입 할 수 있습니다.
의존성 주입 방법에는 생성자주입, 필드주입, 수정자주입이 있습니다.
1. 생성자 주입
생성자 주입은 스프링 팀에서 권장하는 방법이며, 요즘 가장 많이 사용 하는 방식입니다.
@Controller
public class HelloController {
private final HelloService helloService;
@Autowired // 스프링 4.3 부터는 단일생성자일 경우 생략가능
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
}
생성자에 @Autowired 어노테이션을 붙혀 의존성을 주입 할 수 있습니다.
또한, 스프링 4.3 이후 부터는 클래스내 생성자가 하나이고, 그 생성자로 부터 주입받을 객체가 Bean으로 등록되어 있다면,
생략 가능합니다.
생성자 주입은 인스턴스 생성시 1회 호출되는 것이 보장되기 때문에, 주입받은 객체가 변하지 않거나, 반드시 객체주입이 필요한 경우 강제하기 위해 사용됩니다.
2. 필드 주입
@Controller
public class HelloController{
@Autowired
private HelloService helloService;
}
필드 주입은 주입받을 객체가 Bean으로 등록되어 있을 경우 필드에 @Autowired 어노테이션을 붙혀 의존성을 주입합니다.
필드 주입은 코드가 간결하고 편리하기 때문에 사용하기 편합니다.
하지만 의존관계를 정확하게 파악하기 힘들고, 필드 주입 시 final 키워드를 선언할 수 없어 객체가 변할 수 있어
순환참조와 같은 에러가 발생 할 수 있습니다.
3. 수정자(Setter) 주입
@Controller
public class HelloController{
private HelloService helloService;
@Autowired
public setHelloService(HelloService helloService){
this.helloService = helloService;
}
}
Setter 나 사용자 지정 메서드에 @Autowired를 붙혀 의존성을 주입합니다.
메서드 이름은 아무거나 사용해도 되지만, 일관성과 명확성을 위해 setXXXX 을 붙혀 사용하는 것을 추천합니다.
setter주입은 객체가 변경될 필요성이 있을때 사용하지만, 주입하는 객체를 변경하는 경우는 매우 드물기 때문에 거의 사용하지 않는다고 보시면 됩니다.
3가지 방법 중 생성자 주입을 권장하는 이유
1. 순환참조를 방지 할 수 있다.
개발시 여러 컴포넌트 간에 의존관계가 맺어 질 수 있다.
예를 들어 A가 B를 참조하고, B가 다시 A를 참조 하는 경우이다.
@Service
public class AService {
@Autowired
private BService bService;
public void setAService() {
bService.setBService();
}
}
@Service
public class BService {
@Autowired
private AService aService;
public void satBService() {
aService.setAService();
}
}
이럴 경우 애플리케이션을 실행하게되면 아무런 에러없이 실행되게 된다. 하지만 해당 코드가 호출되면 에러가 발생한다.
하지만 아래와 같이 생성자 주입을 이용하게되면, 애플리케이션 구동과 동시에 에러가 발생하기 때문에 빠르게 에러를 잡을 수 있다.
@Service
public class AService {
private final BService bService;
@Autowired
public AService(BService bService){
this.bService = bService;
}
}
@Service
public class BService {
private final AService aService;
@Autowired
public BService(AService aService){
this.aService = aService;
}
}
이렇게 실행 결과가 차이나는 이유는 생성자 주입 방법은 필드주입이나 수정자 주입과는 Bean을 주입하는 순서가 다르기 때문입니다.
수정자(Setter) 주입
주입 받으려는 Bean의 생성자를 호출하여 Bean을 찾거나 Bean 팩터리에 등록한다.
그 후에 생성자를 인자에 사용하는 Bean을 찾거나 만들고, 그 후 주입하려는 Bean 객체의 수정자를 호출하여 주입합니다.
필드 주입
수정자 주입 방법과 동일하게 먼저 Bean을 생성한 후에 어노테이션이 붙은 필드에 해당하는 Bean을 찾아서 주입하는 방법이다. 먼저 Bean을 생성한 후에 필드에 대해서 주입한다.
생성자 주입
생성자로 객체를 생성하는 시점에 필요한 Bean을 주입한다.
먼저 생성자의 인자에 사용되는 Bean을 찾거나 Bean 팩토리에서 만든다. 그 후 찾은 Bean으로 주입하려는 Bean의 생성자를 호출한다.
즉, 먼저 Bean을 생성하지 않는다. 수정자 주입이나 필드 주입과는 방식이 다르다.!
그렇기 떄문에 순환 참조는 생성자 주입에서만 문제가 된다. 객체 생성 시점에 Bean을 주입하기 떄문에 서로 참조하는 객체가 생성되지 않은 상태에서 해당 Bean을 참조하기 떄문에 오류가 발생하게 된다.
2. 테스트에 용이하다.
3. 객체 불편성(Immutability)
필드 주입과 수정자(Setter) 주입은 해당 필드를 final로 선언할 수 없다. 따라서 초기화 후에 Bean객체가 변경될 수 있지만, 필드를 final로 선언할 수 있는 생성자 주입은 변경될 여지가 없다.
참고자료
https://madplay.github.io/post/why-constructor-injection-is-better-than-field-injection
'Spring' 카테고리의 다른 글
스프링 어노테이션 안됨 오류 (적용이 안될때) / Gradle (0) | 2023.11.05 |
---|---|
Spring 에서 mapper-location 설정 시 빨간줄 / URL is not registered (1) | 2023.05.08 |
[Spring] Maven pom.xml 에서 Missing artifact 오류 (0) | 2022.11.21 |