본문 바로가기

개발/Spring

#3 스프링 DI(Dependency Injection)

DI는 '의존 주입'이다. 여기서 말하는 의존은 객체 간의 의존을 의미한다.

 

한 클래스가 다른 클래스의 메서드를 실행할 때 이를 '의존'한다고 표현한다. 즉 변경에 의해 영향을 받는 관계를 의미한다. 예를 들어 A클래스에서 B클래스의 메서드를 실행한다면 A는 B에 의존하며, B클래스의 메서드 이름이 변경되면, A클래스의 소스코드도 변경된 메서드명으로 수정되어야 한다.

 

그렇다면 A클래스에서 의존하는 대상 B클래스가 있다면 그 대상을 구하는 방법이 필요하다. 가장 쉬운 방법은 의존 대상 객체를 직접 생성하는 것이다. 예를들면 A클래스에서 [private B b = new B();] 이렇게 B의 객체를 직접 생성해서 필드에 할당하는 것이다. 이렇게하면 A객체를 생성하는 순간에 B객체도 함께 생성된다. 이는 추후 유지보수 관점에서 문제점을 유발할 수 있다.

 이 외의 방법은 DI와 로케이터이다. 이 중 스프링과 관련이 있는것은 DI로서 이를 통한 의존 처리를 알아보자.

 

DI를 통한 의존 처리

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


package spring;

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 객체를 주입받은 것이다. 의존 객체를 직접 구하지 않고 생성자를 통해서 전달받기 때문에 이 코드는 DI패턴을 따르고 있다.

 

DI를 적용한 결과 MemberRegisterService 클래스를 사용하는 코드는 다음과 같이 객체를 생성할 때 생성자에 MemberDao객체를 전달해야 한다.

MemberDao dao = new MemberDao();
//의존 객체를 생성자를 통해 주입한다
MemberRegisterService svc = new MemberRegisterService(dao);

굳이 더 복잡하게 의존 객체를 주입하는 방식은 '변경의 유연함' 때문이다.

 

 

조립기(assembler)

main 메서드에서 의존 대상 객체를 생성하고 주입하는 방법이 나쁘진 않다. 하지만 이 방법보다 좀 더 나은 방법은 객체를 생성하고 의존 객체를 주입해주는 클래스를 따로 작성하는 것이다. 의존 객체를 주입한다는 것은 서로 다른 두 객체를 조립한다고 생각할 수 있는데, 이런 의미에서 이 클래스를 조립기하고도 표현한다. 

해당 메서드를 사용하려면 조립기 객체를 생성하고 get을 통해 메서드를 가져오면 된다.

 

*assembler.java

package assembler;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;

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

}

 

조립기를 설명한 이유는 스프링이 DI를 지원하는 조립기이기 때문이다. 이를 이해하는 것이 스프링을 이해하는 데 가장 기본 중 하나이다. 스프링은 Assembler 클래스의 생성자처럼 필요한 객체를 생성하고 생성한 객체에 의존을 주입한다. 또한 get*****()처럼 객체를 제공하는 기능을 정의하고 있다. 

 

스프링을 이용한 객체 조립과 사용

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberInfoPrinter;
import spring.MemberListPrinter;
import spring.MemberPrinter;
import spring.MemberRegisterService;
import spring.VersionPrinter;

@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;
	}
	
	@Bean
	public MemberPrinter memberPrinter() {
		return new MemberPrinter();
	}
	
	@Bean
	public MemberListPrinter listPrinter() {
		return new MemberListPrinter(memberDao(), memberPrinter());
	}
	
	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		infoPrinter.setMemberDao(memberDao());
		infoPrinter.setPrinter(memberPrinter());
		return infoPrinter;
	}
	
	@Bean
	public VersionPrinter versionPrinter() {
		VersionPrinter versionPrinter = new VersionPrinter();
		versionPrinter.setMajorVersion(5);
		versionPrinter.setMinorVersion(0);
		return versionPrinter;
	}
}

@Configuration 애노테이션은 스프링 설정 클래스를 의미한다. 

@Bean 애노테이션은 해당 메서드가 생성한 객체를 스프링 빈이라고 설정한다. 위 코드는 7개의 @Bean 애노테이션을 사용했는데 각 메서드마다 한개의 빈 객체를 생성한다. 이때 메서드 이름을 빈 객체의 이름으로 사용한다.

위에서 MemberRegisterService 생성자를 호출할 때 memberDao()메서드를 호출한다. 즉 memberDao()가 생성한 객체를 MemberRegisterService 생성자를 통해 주입한다.

 

AnnotationConfigApplicationContext 클래스를 이용해 스프링 컨테이너를 생성할 수 있고 이를 통해 객체를 생성하고 의존 객체를 주입할 수 있다.

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);

//컨테이너에서 이름이 memberRegScv인 빈 객체를 구한다
MemberRegisterService regSvc = ctx.getBean("memberRegSvc", MemberRegisterService.class);

컨테이너를 사용하면 getBean() 메서드를 통해 사용할 객체를 구할 수 있다.

 

 

 

 

 

 

반응형

'개발 > Spring' 카테고리의 다른 글

스프링 DI(의존주입) 간단 정리  (0) 2020.06.19
개발에 유용한 라이브러리  (0) 2020.06.16
#2 @Configuration과 @Bean의 의존 관계  (0) 2020.01.14
#1 Spring?  (0) 2020.01.14
redirect 시 파라미터값 넘기는 방법  (1) 2019.04.17