article thumbnail image
Published 2022. 4. 1. 17:31

Spring DI

DI는 'Dependency Injection'의 약자로 '의존 주입'을 의미한다.

여기서 말하는 '의존'은 객체 간의 의존을 말한다. 

import java.time.LocalDateTime;

public class MemberRegisterService {
	private MemberDao memberDao = new MemberDAO();

	public void regist(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클래스의 메소드를 사용하고 있다.

이처럼 한 클래스가 다른 클래스의 메소드를 실행할 때 이를 '의존'이라고 표현한다. 

여기서는 "MemberRegisterService 클래스가 MemberDAO 클래스에 의존한다"고 표현할 수 있다.

 

의존은 변경에 의해 영향을 받는 관계를 의미한다. 

MemberDAO의 insert() 메소드 이름을 변경하면 MemberRegisterService 클래스의 소스 코드도 변경해야 한다.

이렇게 변경에 따른 영향이 전파되는 관계를 '의존'한다고 표현한다. 

 

MemberRegisterService 클래스에서 의존하는 MemberDAO 객체를 직접 생성하기 때문에 MemberRegisterService 객체를 생성하는 순간에 MemberDAO 객체도 함께 생성된다. 

유지보수 관점에서 이 점은 문제점을 유발할 수 있다.

 

이렇게 의존 객체를 직접 생성하는 방식 외에 의존 객체를 구하는 다른 방법이 DI이다. 

DI는 의존 객체를 직접 생성하는 대신 의존 객체를 '전달'받는 방식을 사용한다.

 

앞선 코드에 DI 방식을 적용하면 다음과 같다. 

import java.time.LocalDateTime;

public class MemberRegisterService {
	private MemberDao memberDao;

	public MemberRegisterService(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

	public Long regist(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);
		return newMember.getId();
	}
}

직접 의존 객체를 생성했던 코드와 달리 바뀐 코드는 의존 객체를 직접 생성하지 않는다. 

대신 생성자를 통해서 의존 객체를 전달받는다. 

즉 생성자를 통해 MemberRegisterService가 의존하고 있는 MemberDAO 객체를 주입(Injection) 받은 것이다. 

 

DI를 적용했으므로 MemberRegisterService 객체를 생성할 때 생성자에 MemberDAO 객체를 전달해야 한다.

MemberDAO dao = new MemberDAO();
MemerRegisterService svc = new MemerRegisterService(dao);

 

DI를 사용하는 이유는 변경의 유연함을 위해서이다. 

만일 의존 객체를 직접 생성하는 경우 의존 클래스를 다른 클래스로 변경하려면 코드 수정이 필요한데 여러 클래스가 의존하고 있는 경우 각각 수정이 필요하다. 

하지만 DI를 사용하면 변경할 곳은 의존 주입 대상이 되는 객체를 생성하는 코드 한 곳 뿐이다.   

 

실제 객체를 생성하고 의존 객체를 주입해주는 클래스를 assembler(조립기) 클래스라고 한다. 

의존 객체를 주입한다는 것은 서로 다른 두 객체를 조립한다고 생각할 수 있는데 이런 의미에서 이 클래스를 조립기라고 표현한다. 

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);
	}

	public MemberDao getMemberDao() { return memberDao; }
	public MemberRegisterService getMemberRegisterService() { return regSvc; }
	public ChangePasswordService getChangePasswordService() { return pwdSvc; }
}

MemberRegisterService 객체와 ChangePasswordService 객체에 대한 의존을 주입하고 있다.

DI 방식은 생성자 방식과 세터 메소드 방식이 있다.

MemberRegisterService는 생성자를 통해 MemberDAO 객체를 주입받고, ChangePasswordService는 setter를 통해 주입받는다. 

 

Assembler 클래스를 사용하는 코드는 다음처럼 Assembler 객체를 생성한다.

그 다음에 get메소드를 이용해서 필요한 객체를 구하고 그 객체를 사용한다.

public class MainForAssembler { 
  public static void main(String[] args) {
    Assembler assembler = new Assembler(); 
    
    MemberRegisterService regSvc = assembler.getMemberRegisterService();
    RegisterRequest req = new RegisterRequest(…); 
    regSvc.regist(req); 
    
    ChangePasswordService pwdSvc = assembler.getChangePasswordService();
    pwdSvc.changePassword(…);
  }
}

 

정리하면 assembler는 객체를 생성하고 의존 객체를 주입하는 기능을 제공하고 특정 객체가 필요한 곳에 객체를 제공한다. 

예를 들어 Assembler 클래스의 getMemberRegisterService() 메소드는 MemberRegisterService 객체가 필요한 곳에서 사용한다. 

 

 

정리

DI는 객체 외부에 존재하는 assembler(container)가 run-time에 객체들 사이의 의존 관계를 파악하고 의존 객체를 객체에 제공해주는 것을 의미

  • 객체 생성 시점에 의존 객체가 객체에 제공된다. (Bean Wiring)
  • Inversion of Control (IoC): 객체가 의존 객체를 직접 생성하거나 찾을 필요 없음

객체들 간의 의존 관계는 XML 파일이나 Java 설정 클래스, annotation 등을 통해 설정 됨

  • Assembler(container)가 설정 내용을 참조하여 DI 수행

         

스프링은 DI를 지원하는 조립기이다.

실제로 스프링은 앞의 Assembler 클래스와 유사한 기능을 제공한다. 

즉 스프링은 Assembler 클래스의 생성자 코드처럼 필요한 객체를 생성하고 생성한 객체에 의존을 주입한다. 

또한 스프링은 객체를 제공하는 기능을 정의하고 있다. 

차이점이라면 Assembler는 MemberRegisterService나 MemberDAO와 같이 특정 타입의 클래스만 생성한 반면 스프링은 범용 조립기라는 점이다. 

 

Spring Container

객체(bean)들의 라이프 사이클 관리 및 Assembler 역할 수행

  • 객체들을 생성 및 초기화
  • DI를 통해 객체들 사이의 의존 관계 형성

 

 

앞서 구현한 Assembler 대신 스프링을 사용하려면 스프링이 어떤 객체를 생성하고 의존을 어떻게 주입할지를 정의한 설정 정보를 작성해야 한다.                   

설정 정보를 작성할 때는 XML, Annotation, JavaConfig를 이용하여 설정한다.          

 

1. XML을 이용한 객체 생성 및 의존 관계 설정 (appContext.xml)

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="…"
  xsi:schemaLocation="http://www.springframework.org/schema/beans  
  http://www.springframework.org/schema/beans/spring-beans.xsd">

  <!-- MemberDao 타입 객체 생성 -->
  <bean id="memberDao" class="spring.MemberDao" /> 

  <!-- MemberRegisterService 타입 객체 생성 -->
  <bean id="memberRegSvc" class="spring.MemberRegisterService"> 
    <constructor-arg> <!-- 생성자를 이용한 MemberDao 객체 주입 --> 
      <ref bean="memberDao" />
    </constructor-arg>
  </bean> 
  …
</beans>

→ Spring Container가 생성 및 실행하는 코드

MemberDao = new MemberDao();
MemberRegisterService memberRegSvc = new MemberRegisterService(memberDao);

Test Program

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class MainForSpringXML {
  public static void main(String[] args) {
    ApplicationContext context = new GenericXmlApplicationContext("classpath:appContext.xml");
    
    MemberRegisterService regSvc = context.getBean("memberRegSvc", MemberRegisterService.class);
    RegisterRequest req = new RegisterRequest(…);
    regSvc.regist(req);

    ChangePasswordService pwdSvc = context.getBean("changePwdSvc", ChangePasswordService.class);
    pwdSvc.changePassword(…);
    …
  }
}

 

2. Annotation을 이용한 설정

  • 클래스 소스 코드 내에서 annotation을 사용하여 객체 생성 및 DI 설정
@Component // Bean 객체 자동 생성 (id: “memberDao”)
public class MemberDao {
  private Map<String, Member> map = new HashMap<>(); …
  public Member selectByEmail(String email) {
    return map.get(email);
  }
  …
}
@Component("memberRegSvc") // Bean 객체 자동 생성 (id: “memberRegSvc”)
public class MemberRegisterService {
  @Autowired // MemberDao 타입의 객체를 찾아 자동 주입
  private MemberDao memberDao;
  public MemberRegisterService() { } 
  …
}
  • XM 설정 파일 (appContext.xml): 클래스 파일 스캔 및 annotation 기반 설정 및 활성화
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=“…"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="spring.annotation" />
  
  <!-- bean 설정 생략 ! -->
</beans>

<context:component-scan base-package="spring.annotation" />

→ 지정된 base package 내의 클래스 파일들을 스캔, Bean 자동 생성 및 DI 실행

 

3. Java code를 이용한 설정 (JavaConfig)

@Configuration // 설정 클래스 정의
public class AppCtx {
  @Bean // Bean 객체 생성 (id=“memberDao”)
  public MemberDao memberDao() {
    return new MemberDao();
  }
  @Bean // Bean 생성 (id=“memberRegSvc”)
  public MemberRegisterService memberRegSvc() {
    return new MemberRegisterService( memberDao() ); // 생성자를 이용한 DI 실행
  }
  @Bean // Bean 생성
  public ChangePasswordService changePwdSvc() {
    ChangePasswordService pwdSvc = new ChangePasswordService();
    pwdSvc.setMemberDao(memberDao()); // 기존 “memberDao” bean을
    return pwdSvc; // Setter method를 통해 주입
  }
  …
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainForAppCtx {
  public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext("AppCtx.java");
    // 이하 Test Program과 동일
  }
}

📌 아래 서적의 내용을 정리한 글입니다. 

 

 

'프로그래밍 > Spring' 카테고리의 다른 글

[Spring] Auto-wiring  (0) 2022.04.25
[Spring] DI - XML 기반 설정  (0) 2022.04.24
[Spring] Spring MVC(2)  (0) 2022.04.22
[Spring] Spring MVC(1)  (0) 2022.04.17
복사했습니다!