의존이란?
Dependency Injection
의존 주입
예를 들어
public class MemberRegisterService {
private MemberDao memberDao = new MemberDao();
public void regist(RegisterRequest req) {
Member member = memberDao.selectByEmail(req.getEmail());
if(member != null) {
throw new DuplicatedMemberException("dup mem");
}
Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
memberDao.insert(newMember);
}
}
위와 같은 코드에서
MemberRegisterService 클래스가 DB 사용을 위해 MemberDao클래스의 메서드를 사용
이렇게 한 클래스가 다른 클래스의 메서드를 실행할 때, 이를 의존한다 라고 표현
의존하는 대상이 있으면, 그 대상을 구하는 방법이 필요
가장 쉬운 방법은 의존 대상 객체를 직접 생성하는것
public class MemberRegisterService {
private MemberDao memberDao = new MemberDao();
...
}
클래스 내부에서 의존 객체 직접 생성은 유지보수 관점에서 좋지 않은 방식
DI를 통한 의존 처리
DI는 의존 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식 사용
public class MemberRegisterService {
...
private MemberDao memberDao;
public MemberRegisterService (MemberDao memberDao){
this.memberDao = memberDao;
}
...
}
의존 객체를 직접 생성하지 않고
생성자를 통해 의존 객체를 전달받음
MemberRegisterService service = new MemberRegisterService(memberDao);
와 같이 생성자 생성 시, 의존 객체 함께 전달
DI와 의존 객체 변경의 유연함
의존 객체 직접 생성방식은 필드, 생성자에서 new 연산자로 객체 생성
public class MemberRegisterService{
private MemberDao memberDao = new CachedMemberDao; // MemberDao에서 변경
...
}
public class ChangePasswordService{
private MemberDao memberDao = new CachedMemberDao; // MemberDao에서 변경
...
}
이처러머 각각 클래스에서 생성자를 직접 생성하는 경우
memberDao 를 다른 객체로 바꾸고 싶을 때, 모든 클래스의 코드를 수정해야함
but
생성자 주입 방식을 사용할 경우
// 기존
// MemberDao dao = new MemberDao();
MemberDao dao = new CachedMemberDao();
MemberRegisterService svc = new MemberRegisterService(dao);
ChangePasswordService pwdSvc = new ChangePasswordService(dao);
기존 MemberDao 대신 MemberDao 를 상속받아 구현 된 CachedMemberDao 객체를 사용하고 싶은 경우
위처럼 한줄 수정으로 모든 클래스에 적용 가능
... 예제 프로젝트 import는 생략 ...
예제 프로젝트에서도 역시 의존성 생성자 주입을 통한 클래스 구현
객체 조립기라는것 사용
public class Assembler{
private MemberDao memberDao;
private MemberRegisterService regSvc;
private ChangePasswordService pwdSvc;
public Assembler() {
memberDao = new MemberDao();
regSvc = new MemberRegisterService(memberDao);
pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao);
}
...
}
처럼 의존성 연관된 객체들을 조립해주는 클래스를 별도로 만들어
Assembler assembler = new Assembler();
ChangePasswordService changePwdSvc = assembler.getChangePasswordService();
와 같이 간편하게 사용할 수 있도록 도와주는 클래스
Spring 에서는 이 조립기가 내부에 존재해서 별도로 작성 필요 X
@Configuration
public class AppCtx {
@Bean
public MemberDao memberDao(){
return new MemberDao();
}
@Bean
public MemberRegisterService memberRegisterSvc(){
// 생성자를 통한 의존주입
return new MemberRegisterService(memberDao());
}
@Bean
public ChangePasswordService changePasswordSvc(){
ChangePasswordService pwdSvc = new ChangePasswordService();
// set메서드를 통한 의존 주입
pwdSvc.setMemberDao(memberDao());
return pwdSvc;
}
...
}
@Configuration : 스프링 설정 클래스
@Bean : 스프링 빈 객체로 생성
직접 객체를 생성하고 등록하고 조립하는 것이 아니라
Spring Context 에 Bean을 등록하고 그 객체를 사용
ctx = new AnnotationConfigApplicationContext(AppCtx.class);
...
...
MemberRegisterService regSvc = ctx.getBean("memberRegisterSvc", MemberRegisterService.class);
...
이처럼 스프링 컨텍스트에서 memberRegisterSvc 라는 Bean을 꺼내서 사용
DI 방식
생성자 주입
private MemberDao membe Dao;
public MemberRegisterService(Membe Dao memberDao) {
this.memberDao = memberDao;
}
이처럼 생성자를 생성할 때, 객체를 전달받는 방식
주입받은 객체가 변하지 않고
생성자 실행 시 1회 실행 보장
수정자 주입 (Setter 메서드) 주입
public class MemberInfoPrinter {
private MemberDao memDao;
private MemberPrinter printer;
public void printMemberInfo(String email){
Member member = memDao.selectByEmail(email);
if(member == null){
System.out.println("데이터 없음\\n");
return;
}
printer.print(member);
System.out.println();
}
public void setMemberDao(MemberDao memberDao){
this.memDao = memberDao;
}
public void setPrinter(MemberPrinter printer){
this.printer = printer;
}
}
setXXX 메서드에 의존성 객체를 불러오는 방식
객체를 불러온 이후에도, set메서드를 통해 객체를 변경할 수 있기 때문에 위험요소 존재
필드 주입 방식
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private MemberService memberService;
}
책에는 안나와있지만, 클래스에 선언된 private 필드에 직접 주입하는 방식
필드주입은 외부에서 접근이 불가능하기 때문에 TDD에 어울리지 않는 방식
@Configuration 설정 클래스의 @Bean 설정과 싱글톤
스프링 컨테이너가 생성한 빈은 싱글톤 객체
Bean이 붙은 메서드에 대해 한 개의 객체만 생성하여 사용
>> 싱글톤으로 직접 구현 불필요, 매우 간편!
@Configuration
public lcass AppCtx {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
...
}
여러 클래스에서 MemberDao 메서드를 실행해도 같은 객체를 리턴
실제 스프링 내부적으로는 복잡하게 구현되어 있음
@Configuration 어노테이션, 빈, @Autowired 어노테이션
@Autowired 어노테이션이란?
스프링 빈에 의존하는 다른 빈을 자동 주입하는 어노테이션
public class MemberInfoPrinter{
@Autowired
private MemberDao memberDao;
...
}
필드에 @Autowired 어노테이션을 사용하면
위 Configuration 클래스를 내부에 하나하나 @Bean 메서드를 생성하지 않아도 의존성 주입 가능
@Import 어노테이션
은.. 넘어가자