하나의 클래스가 다른 클래스의 메소드를 실행할 때 이를 '의존'한다고 표현합니다.
public class MemberRegisterService {
private MemberDao memberDao = new MemberDao();
public void register(RegisterRequest req) {
Member member = memberDao.selectByEmail(req.getEmail());
if (member != null) {
throw new DuplicateMemberException("dup email " + req.getEmail());
}
Member newMember = new Member(
req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now()
);
memberDao.insert(newMember);
}
}
여기서 MemberRegisterService
가 DB처리를 하기위해 MemberDao
의 메소드 사용하여 처리를 하고 있습니다.
그렇기에 '의존'한다고 표현할 수 있습니다. '의존'은 변경에 의해 영향을 받는 관계를 의미하기도 합니다. memberDao.insert()
에서 insert()
를 insertMember()
로 바꾸면 이 메소드를 사용하는 MemberRegisterService
또한 코드를 변경해 주어야 합니다.
'의존'은 의존하는 대상이 있으면 그 대상을 구하는 방법이 필요합니다.
첫 번째 방법인 직접 의존 대상을 객체로 생성하는 방법은 그다지 추천하지 않습니다. 나중에 유지보수 관점에서 보면 문제점을 유발할 수 있기 때문입니다.
간단하게 직접 생성 하였을 경우 의존 대상이 변경점이 많이 생기면 직접 생성한 모든 곳을 똑같이 변경해주어야 하기 때문입니다.
그렇기에 DI와 서비스 로케이터 방식을 씁니다. 이 중 스프링과 관련된 DI에 대해서 설명합니다.
DI 방식
그럼 무엇을 써야 하나. 답은 없다 상황별로 골라 사용하면 된다. 각 방식의 장점을 보자
각 방식의 장점은 곧 단점이 된다. 파라미터의 개수가 많다면 생성자로 한번에 의존 객체 주입이 가능하지만 어떤 의존 객체가 들어있는지는 생성자 코드를 확인해야하는 번거로움이 있다. 반면 세터 방식은 어떤 의존 객체가 들었는지 쉽게 유추할 수 있지만 파라미터가 많은 만큼 많은 set 메소드를 작성하게 될 것이다.
의존 주입 예제로 회원 관련된 DAO를 많이 작성하게 됩니다. 다음과 같은 설정 코드가 있습니다.
@Configuration
public class AppCtx{
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao());
}
@Bean
public ChangePasswordService changePwdSvc() {
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao());
return pwdSvc;
}
}
위 코드에서 memberRegSvc
와 changePwdSvc
메소드 둘 모두 memeberDao
메소드를 실행하고 memberDao
메소드는 매번 새로운 MemberDao
객체를 리턴합니다.
각각 다 다른 곳에서 메소드를 부르고 객체를 매번 리턴받으면 다른 객체가 아닌가 하는 의문점이 생깁니다. 실제로 만일 새로운 회원을 저장을하고 비밀번호를 바꾸게 되면 다른 memberDao
객체를 쓰게 되는 것이 아니냐는 말입니다.
당연한 의문이지만 스프링 컨테이너가 생성하는 빈 객체는 싱글톤 객체라고 하며, 스프링 컨테이너는 @Bean
이 붙은 메소드에 대해 한 개의 객체만을 생성합니다.
어떻게 가능한가? 스프링은 설정 클래스를 그대로 사용하지 않습니다. 대신 설정 클래스를 상속한 새로운 설정 클래스를 만들어서 사용합니다. 만들어진 새로운 설정 클래스는 매우 복잡합니다만, 간단하게 말하자면 한번 만들어진 빈 객체를 보관했다가 이후에는 동일한 객체를 리턴합니다. 다음은 새로 만들어진 가상의 유사코드입니다.
public class AppCtx extends AppCtx {
private Map<String, Object> beans = ...;
public MemeberDao memberDao() {
if(!beans.containsKey("memberDao")) {
beans.put("memberDao", super.memberDao());
}
return (MemberDao) beans.get("memberDao");
}
어디까지나 이해를 돕기위한 유사한 가상의 코드이다. 실제 스프링 코드는 이것보다 훨씬 더 복잡합니다.