public class MemberService {
private MemberDao memberDao = new MemberDao();
public void joinMember(JoinRequest req) {
Member member = memberDao.insert(req);
}
}
위의 MemberService
클래스는 회원가입 처리를 위해 MeberDao
클래스의 메서드를 실행하는데, 이처럼 한 클래스가 다른 클래스의 메서드를 실행할 때 의존한다고 표현한다. 의존은 변경에 의해 영향을 받는 관계를 의미하는데, 만약 MemberDao
클래스의 insert()
메서드를 join()
이라 변경한다면 MemberService
클래스의 소스코드 역시 변경되어야 한다. 이렇게 변경에 따른 영향이 전파되는 관계를 '의존' 한다고 표현한다.
의존하는 대상이 있으면 그 대상을 구하는 방법이 필요한데 가장 쉬운방법은 위의 예시처럼 의존 대상 객체를 직접 생상하는 것이다. MemberService
라는 클래스에서는 의존하는 MemberDao
객체를 직접 생성하기 때문에 MemberService
객체를 생성하는 순간 MemberDao
객체도 함께 생성된다. 이러한 방법이 간단하긴 하지만 유지보수 관점에서 문제점을 유발할 수 있기 때문에 스프링에서는 DI를 사용하여 의존 객체를 구한다.
DI 는 Dependency Injection
의 약자로 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식을 사용한다. DI를 사용하면 앞선 예시를 아래의 코드로 변경할 수 있다.
public class MemberService {
private MemberDao memberDao;
public MemberService(MemberDao memberDao) {
this.memberDao = memberDao;
}
public void joinMember(JoinRequest req) {
Member member = memberDao.insert(req);
}
}
첫번째 예시와 다르게 의존 객체를 직접 생성하지 않고 전달받는다. 즉 생성자를 통해 의존하고 있는 객체를 주입받은 것이다. 의존 객체를 직접 구하지 않고 생성자를 통해서 전달받기 때문에 이 코드는 DI패턴을 따르고 있다. 직접 의존 객체를 생성하는 것이 더 간단하게 느껴질 수 있지만 코드 유지보수 측면에서는 DI가 훨씬 유리하다. 예를 들어 MemberDao
클래스에 어떠한 내용을 추가하기 위해 MemberDao
클래스를 상속받는 클래스를 만들고 상속받은 클래스의 내용을 다른 클래스들에서 사용하고자 한다. 이 경우 DI를 사용하지 않는 경우라면 각각의 클래스에 새로운 파생클래스의 이름으로 new
를 해주어야하는 반면 DI를 사용한다면 주입전 한줄의 코드에만 새로운 파생클래스의 이름으로 변경해주면 되어 더욱 편리하다.
public class JoinMemberService {
private MemberDao memberDao = new MemberDao();
}
public class LoginMemberService {
private MemberDao memberDao = new MemberDao();
}
public class JoinMemberService {
private MemberDao memberDao = new SubMemberDao();
}
public class LoginMemberService {
private MemberDao memberDao = new SubMemberDao();
}
MemberDao memberDao = new MemberDao();
JoinMemberService(memberDao);
LoginMemberService(memberDao);
MemberDao memberDao = new SubMemberDao();
JoinMemberService(memberDao);
LoginMemberService(memberDao);
위의 경우처럼 DI를 사용하면 한줄의 수정으로 코드변경을 할 수 있다.
@Autowired
어노테이션을 이용하여 의존성을 자동으로 주입할 수 있다. 이때 주입 대상에 일치하는 빈은 오직 하나만 존재해야 예외없이 주입이 된다.
public class MemberService {
@Autowired
private MemberDao memberDao; //MemberDao 는 빈으로 등록되어있어야 한다.
public void joinMember(JoinRequest req) {
Member member = memberDao.insert(req);
}
}
자동 주입 가능한 빈이 두 개 이상이면 자동 주입할 빈을 지정할 수 있는 방법이 필요한데 이때 사용하는 것이 @Qualifier
어노테이션이다. @Qualifier
어노테이션을 사용하면 자동 주입 대상 빈을 한정할 수 있다.
@Configuration
public class ApplicationConfig {
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
}
@Qualifier("printer")
라는 어노테이션으로 해당 빈의 한정값으로 'printer' 를 지정한다.
public class MemberListPrinter {
private MemberDao memberDao;
private MemberPrinter printer;
@Autowired
@Qualifier("printer")
public void setMemberPrinter(MemberPrinter printer) {
this.printer = printer;
}
}
setMemberPrinter() 메서드에 @Autowired
어노테이션으로 빈을 자동주입하는데 이때 @Qualifier()
의 값이 'printer' 이므로 한정 값이 'printer'인 빈을 의존 주입 후보로 사용한다.
@Autowired
어노테이션은 기본적으로 @Autowired
어노테이션을 붙인 타입에 해당하는 빈이 존재하지 않는다면 예외를 발생시킨다. 이러한 경우 자동 주입할 대상이 필수가 아닌 경우에도 예외를 발생시키므로 @Autowired
어노테이션의 required
속성을 false
로 지정하면 된다. 이렇게 지정하게 된다면 매칭되는 빈이 없더라도 예외가 발생하지 않으며 자동 주입을 수행하지 않는다.