spring에서 DI(의존성 주입)이란, 객체간의 의존성을 개발자가 직접 호출(new연산자)하는 대신
외부(스프링 컨테이너)에서 객체를 생성해서 넣어주는 방식이다.
외부에서 두 객체 간의 관계설정을 해주는 디자인 패턴으로, 인터페이스를 사이에 두어
클래스 레벨에서의 의존관계가 고정되지 않도록하고,
런타임시 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 한다.
public class Controller{
private Service service;
service.test();
}
이렇게 Controller에서 Service를 사용하고 있는경우, Controller는 Service에 의존성이 있다고 할수 있다.
의존성이 있다는 말의 뜻은, 의존대상 즉 Service가 변하면 그것이 Controller에게 전달된다는 뜻이다.
만약 Service의 test() 메소드가 변경되면 Controller에서도 그에 따른 수정이 필요하게 되거나 형식은 그대로지만
로직이 변경되면 결과적으로 Controller의 기능에도 영향을 미친다는 뜻이다.
이러한 두 객체간의 관계를 맺어주는 것을 의존성 주입(DI)라고 하며 스프링에선 3가지 방법이 존재한다.
1.생성자 주입
@Controller
public class Controller{
private Service service;
@Autowired
public Controller(Service serivce){
this.service = service;
}
}
생성자에 @Autowired를 붙혀서 의존성을 주입 받을수 있다.
Spring 4.3이후로는 클래스 내 생서자가 하나이고, 그 생성자로 주입받을 객체가 빈으로 등록되어 있다면,
@Autowired 생략가능하다.
생성자 주입은 인스턴스 생성시 1회 호출되는 것이 보장되기 때문에, 주입받은 객체가 변하지 않거나,
반드시 객체주입이 필요한 경우 강제하기 위해 사용됨
2.필드 주입
@Controller
public class Controller{
@Autowired
private Service service;
}
코드가 간결하고 편하지만 의존관계를 정확히 파악하기 힘들다.
필드 주입시 final 키워드를 선언할 수 없어서 객체가 변할 수 있음.
주입이 동시에 일어나 겹치는 경우 순환참조 에러가 난다.
3.수정장(setter) 주입
@Controller
public class Controller{
private Service service;
@Autowired
public setService(Service service){
this.service = service;
}
}
setter 혹은 사용자정의 메소드를 통해 의존관계 주입.
setter의 경우 객체가 변경될 필요성이 있을때만 사용한다.
스프링 팀에서는 생성자 주입을 권장한다.
---생성자 주입을 권장하는 이유---
1.객체 불변성 확보
객체의 생성자는 객체 생성시 최초 1회만 호출된다. 때문에 주입받은 객체가 불변객체여야 하거나
반드시 해당 객체의 주입이 필요한 경우 사용한다. (불변객체 = 재할당은 가능하나, 할당하면 데이터가 바뀌지않는 객체)
@Controller
public class Controller{
private Service service;
@Autowired
public Controller(Service service){
this.service = service;
}
}
위 코드에서 Service객체를 변경할수 있는 부분은 해당 클래스의 생성자 밖에 없다.
즉, 메소드나 어떤 부분에서 Service객체를 변경하는 코드가 없다는것을 의미한다.
객체를 한번 생성자를 통해 주입받으면, 해당 객체는 더 이상 변경되지 않는 불변 객체로 취급된다.
2.null을 주입하지 않는 한 NullPointerException이 발생하지 않는다.
필드 주입으로 작성된 경우, 순수 자바 코드로 단위테스트를 싱행하는 것이 불가능하다.
메인코드는 Spring과 같은 DI프레임워크 위에서 동작하는데 단위테스트시 단독적으로 실행되기 때문에,
의존관계 주입이 null상태여서 NullPointerException이 발생하게 된다.
생성자 주입시 단독으로 실행할때도 의존관계 주입이 성립된다.
3.순환참조 에러방지
순환참조란 A객체가 B객체를 참조하고, B객체가 A객체를 서로 동시에 참조하고 있을때 발생한다.
@Service
public class ServiceA{
@Autowired
private ServiceB serviceB;
public void test(){
serviceB.test(); //ServiceA가 ServiceB의 메소드 호출
}
}
@Service
public class ServiceB{
@Autowired
private ServiceA serviceA{
public void test(){
serviceA.test(); //ServiceB가 ServiceA의 메소드 호출
}
}
이렇게 서로 메소드를 계속해서 호출하다보면 StackOverFlow가 발생하면서 프로그램이 다운된다.
컴파일시에는 오류가 없다가 메소드 호출시에 발생한다는것이 문제가 된다.
순환참조는 생성자주입, 필드주입, setter주입 3가지 방법론에서 모두 발생하는데 에러 시점이 다르다.
필드주입과 setter주입은 프로그램 실행중에 runtime에러가 발생하고,
생성자 주입시에는 프로그램 실행 시점에 compile에러가 발생한다. 즉, 실행 시점에 컴파일 에러 발생시
프로그램 실행 자체가 되지 않기 때문에, 개발자 입장에서는 실제 서비스 되기 전, 순환 참조 문제를 해결 가능하다.
이러한 이유들 때무문에 여러 DI 방법 중 생성자주입 방식을 권장하고 있는것이다.