의존성 주입 (Dependency Injection, DI)
MVC 모델을 사용하여 개발한다고 가정할 때 Controller에서는 Service에 있는 메서드를 호출해야 하고, Service에서는 Repository(or DAO,...)에 있는 메서드를 호출해야 한다.
이런 상황에서 Controller에서는 Service에 의존한다 말하고, Service는 Repository에 의존한다고 얘기한다.
interface MemberRepository {...}
public class JdbcMemberRepository implements MemberRepository {...}
public class MyBatisMemberRepository implements MemberRepository {...}
public class MemberService {
private MemberRepository memberRepository;
public MemberService() {
this.memberRepository = new JdbcMemberRepository(); // 1
this.memberRepository = new MyBatisMemberRepository(); // 2
}
}
예시를 들어 MemberRepository 인터페이스를 구현하는 두 개의 클래스(JdbcMemberRepository, MyBatisMemberRepository)가 존재한다고 가정한다.
이때, JdbcMemberRepository 클래스에서는 JDBC를 사용하여 데이터베이스와 데이터를 주고받고, MyBatisMemberRepository에서는 MyBatis를 사용하여 데이터를 주고받는다.
만약 의존성 주입을 하지 않고 코드를 작성한다면 Service 클래스에서 JDBC를 사용하여 데이터를 가져오고 싶을 때는 JdbcMemberRepository 객체를 생성해야 하고, MyBatis를 사용하고 싶다면 MyBatisMemberRepository 객체를 생성해야 한다.
위 두 개의 MemberRepository 클래스를 사용하지 않고 또 다른 새로운 JpaMemberRepository라는 클래스를 생성하여 사용하고 싶다면 그때 또 new 하여 새로운 객체를 생성해야 할 것이다.
위와 같은 예시는 코드 수정을 조금만 하면 되지만, 실제 프로젝트를 진행할 때는 저렇게 간단한 코드가 나오지 않을 것이다. 복잡한 코드에서 의존성 주입을 사용하지 않는 경우 코드 수정이 빈번하게 발생할 수 있는 것이다.
만약 의존성 주입을 한다면 아래와 같은 깔끔한 코드를 만들 수 있다.
interface MemberRepository {...}
public class JdbcMemberRepository implements MemberRepository {...}
public class MyBatisMemberRepository implements MemberRepository {...}
public class MemberService {
private MemberRepository memberRepository;
// 의존성 주입
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository
}
}
필요한 클래스를 직접 new 하여 생성하지 않고, (MemberRepository memberRepository)와 같이 주입해주어 객체 간의 결합도를 줄일 수 있다.
의존성 주입 방법
스프링에서 의존성 주입 방법은 대표적으로 세 가지가 있다. 그리고 본 예제에서는 IoC(Inversion of Control, 제어의 역전)도 사용한다. IoC는 객체 전반에 걸친 제어권을 프레임워크의 컨테이너에 넘기는 개념이다. 스프링 프레임워크에서는 @Autowired 어노테이션을 사용하여 스프링 컨테이너에게 제어권을 넘긴다.
컴포넌트 스캔 방식을 사용해 컴포넌트를 스프링 컨테이너에 등록한다고 가정한다.
스프링 컨테이너가 관리하는 스프링 빈만 @Autowired 어노테이션이 동작한다.
필드 주입
@Service
public class MemberService {
@Autowired
private MemberRepository memberRepository;
}
필드 주입 방식은 필드 앞에 @Autowired 어노테이션을 명시한다.
필드 주입 방법을 사용하면 주입한 것을 중간에 바꿀 수 있는 방법이 없다. (변경하는 경우가 많지 않긴 하다) 주입되는 변수를 private로 선언하기 때문에 이후 외부 접근도 불가능하다. 따라서 추천되지 않는 방법이다.
setter 주입
@Service
public class MemberService {
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
setter 함수를 사용하는 방법이다. setter 함수에 @Autowried 어노테이션을 명시한다.
memberRepository는 private으로 선언하고, 이후 setter 함수가 호출되어 memberRepository가 주입된다.
단점은 setter 함수가 public 접근 권한자로 생성되어 있어야 한다는 것이다.
사실상 Repository는 한 번 주입되고 나면 바뀔 일이 없다. 오히려 중간에 바꾸게 된다면 문제가 발생할 수 있다. 바꾼다 해도 로딩 시점에 변경되는 것이지 이미 세팅되고 나면(로딩 후) 바꿀 일이 존재하지 않는다.
그러나 setter 주입 사용 시 setter 함수가 public으로 노출되어 있어야 하니 memberRepository가 변경될 수 있는 환경이 된다. 외부에서도 얼마든지 memberService.setMemberRepository()를 호출할 수 있기 때문이다.
생성자 주입 (권장)
@Service
public class MemberService {
private MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
생성자에서 주입하는 방식으로, 처음 애플리케이션이 조립되는 시점(스프링 컨테이너에 올라가는 등의 세팅이 될 때)에 주입된다. 생성자 앞에 @Autowired 어노테이션을 명시한다.
setter를 사용한 의존성 주입은 세팅 후 개발자가 접근할 수 있다고 했지만, 생성자를 사용하여 의존성 주입 시 세팅 후 주입한 것을 변경하는 게 불가능하다.
의존관계가 프로그램 실행 중에(런타임) 동적으로 변경되는 경우는 아예 없기 때문에 생성자 주입이 권장된다.
[인프런] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한)