[Spring] 스프링 DI

첫걸음 - 3

Posted by owin2828 on 2019-12-30 10:51 · 6 mins read

1. 의존이란?


  • DIDependency Injection의 약자로 우리말로는 의존주입이라 번역
  • 의존이란 객체간의 의존을 의미
  • 한 클래스가 다른 클래스의 매서드를 실행할 경우 이를 의존한다고 표현(변경에 의해 영향을 받는 관계)

2. About DI


  • OOP의 DIP와는 비슷한 개념
  • 디자인 패턴의 IoC(Inversion of Control) 제어 반전의 한 종류

    용어가 정확하게 구분되어 있지는 않지만, 대략 IoC > DIP > DI 정도로 정리가능
    자세한 사항은 링크 참조: https://bit.ly/2Zt9cnf

  • 객체지향 5대 원칙: SOLID
    1. 단일 책임 원칙(Single responsibility principle) - SRP
    2. 개방 폐쇄 원칙(Open/closed principle) - OCP
    3. 리스코프 치환 원칙(Liskov substitution principle) - LSP
    4. 인터페이스 분리 원칙(Interface segregation principle) - ISP
    5. 의존관계 역전 원칙(Dependency inversion principle) - DIP

2-1. DI를 통한 의존 처리

  • DI는 의존하는 객체를 직접 생성하는 대신, 의존 객체를 전달받는 방식을 이용
    // MemberRegisterService.java
    public class MemberRegisterService {
      private MemberDao memberDao;
    
          // MemberDao 객체를 전달 받아 DI를 구현
      public MemberRegisterService(MemberDao memberDao) {
          this.memberDao = memberDao;
      }
          ...
    }
    


2-2. Why DI?

  • DI를 사용하는 가장 큰 이유는 변경의 유연함
  • 객체를 전달받아 사용하게 되면, 코드의 수정을 최소한으로 줄이고 유연함을 가져갈 수 있음(주입 대상이 되는 객체를 생성하는 코드만 변경하면 됨)

2-3. DI 방식 1: 생성자 방식

// MemberRegisterService.java
public class MemberRegisterService {
	private MemberDao memberDao;

        // 생성자를 통해 의존 객체를 주입 받음
	public MemberRegisterService(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

	public Long regist(RegisterRequest req) {
                // 주입 받은 의존 객체의 매서드를 사용
		Member member = memberDao.selectByEmail(req.getEmail());
                ...
		memberDao.insert(newMember);
		return newMember.getId();
	}
}
  • 생성자를 통해 의존 객체를 주입받아 필드에 할당
  • 전달할 의존 객체가 두 개 이상이어도 동일한 방식으로 주입

2-4. DI 방식 2: setter 매서드 방식

// MemberInfoPrinter.java
public class MemberInfoPrinter {

	private MemberDao memDao;
	private MemberPrinter printer;
        ...
        // setter를 통해 의존 객체를 주입 받는다.
	public void setMemberDao(MemberDao memberDao) {
		this.memDao = memberDao;
	}

	public void setPrinter(MemberPrinter printer) {
		this.printer = printer;
	}

}
  • setter 매서드를 이용하여 객체를 주입

2-5. 생성자 VS setter?

  • 각 방식의 장점은 곧 다른 방식의 단점
  • 생성자 방식 장점: Bean 객체를 생성하는 시점에 모든 의존 객체가 주입, 그러나 생성자의 파라미터가 많을 경우 일일히 확인해 주어야 함
  • setter 매서드 방식: sstter 매서드 이름을 통해 어떤 의존 객체가 주입 되는지 알 수 있음, 그러나 정확한 전달이 안될 시 NullPointerException 발생가능

3. 싱글톤의 이해와 어노테이션


3-1. @Configuration 설정 클래스의 @Bean 설정과 싱글톤

// AppCtx.java
@Configuration
public class AppCtx {

	@Bean
	public MemberDao memberDao() {
		return new MemberDao();
	}
	
	@Bean
	public MemberRegisterService memberRegSvc() {
                // memberDao() 매서드 호출
		return new MemberRegisterService(memberDao());
	}
	
	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
                // memberDao() 매서드 호출
		pwdSvc.setMemberDao(memberDao());
		return pwdSvc;
	}
        ...
}

memberRegSvc() 매서드와 changePwdSvc() 매서드는 둘 다 memberDao() 매서드를 실행
그리고 memberDao() 매서드는 매번 새로운 MemberDao 객체를 생성해서 리턴
두 매서드는 과연 같은 MemberDao 객체를 사용하는가?

  • 스프링 컨테이너는 @Bean이 붙은 매서드에 대해 한 개의 객체만 생성
  • 이는 다른 설정에서 memberDao()를 몇 번을 호출하더라도 항상 같은 객체를 리턴
  • 이것이 가능한 이유는 스프링이 설정 클래스를 그대로 사용하지 않고,
    설정한 클래스를 상속한 새로운 설정 클래스를 만들어서 사용하기 때문
  • 따라서 런타임에 매번 생성하지 않고,
    한 번 생성한 객체를 보관했다가 이후에는 동일한 객체를 리턴
// 이해를 돕기 위한 가상의 코드. 실제 코드는 이보다 훨씬 복잡하다.
public class AppCtxExt extends AppCtx {
        private Map<String, Object> beans = ...;

	@Override
	public MemberDao memberDao() {
		if(!beans.containersKey("memberDao"))
                    beans.put("memberDao", super.memberDao());

                return (MemberDao) beans.get("memberDao");
	}
        ...
}
  • 매번 새로운 객체를 리턴하는 것이 아니라, 한 번 생성한 객체를 보관했다가 동일 객체를 리턴

3-2. 두 개 이상의 설정 파일을 사용하기

  • @AutoWired 어노테이션을 통해 필요한 Bean 객체를 자동 주입
  • @Import 어노테이션을 통해 함께 사용할 클래스를 지정