Java/JavaSpring

Chap 3. 스프링 DI

펭킹 2022. 6. 20. 11:01

의존(DI)의 정의 :  DI는 'Dependency Injection'의 약자로 의존주입이다. 여기서 의존은 객체 간의 의존을 의미한다.

의존주입이라는 말이 생소 할 수도 있지만, 이전 chap2에서 잠깐 다룬적이 있다.

 

다음 그림은 의존주입을 쉽게 설명하기 위한 예시이다.

 

 

A, B, C가 다음과 같이 쌓여 있을 때 A와 C를 이어주는 B가 사라지게 된다면  다음 그림과 같이 무너지게 된다.

 

한 클래스가 다른 클래스의 메서드를 실행 할 때 이를 의존이라고 한다. 의존은 변경에 의해 영향을 받는 관계이고, 변경에 따른 영향이 전파된다.

클래스 내부에서 직접 의존 객체를 생성하는 것은 쉽지만 유지보수 관점(코드의 유연성)에서 문제점이 유발된다.

 

다음은 DI를 통한 의존처리 예제이다.

 

package spring;

import java.time.LocalDateTime;

public class MemberRegisterService {
	private MemberDao memberDao;
	
    //생성자라서 함수클래스 호출시 실행됨. 
	public MemberRegisterService(MemberDao memberDao)//MemberRegisterService 파라미터를 통해 다른곳에서 MemberDao를 new 할때 여기로 이동된다. 
	{
		this.memberDao = memberDao;//받은 memberDao값을 private 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();
	}
}

 

public MemberRegisterService(MemberDao memberDao) 함수(생성자) 부분에서 의존하고 있던 MemberDao 객체를 주입 받은것 이다.

 

의존을 사용하는 이유는 변경의 유연함(모듈화) 때문이다.

 

의존 객체를 직접 생성하는 방식은 필드나 생성자에서 new 연산자를 이용하여 객체를 생성한다. 이때 의존 객체를 직접 생성 할 수 있다.

데이터 베이스에 저장한다고 가정 했을때, 빠른 조회를 위해 캐시를 적용해야 될 때가 있다. 그래서 따로 캐시dao를 만들고 그  캐시를 사용할 dao를 확장 시켜서 사용한다.

ex) CashedMemberDao extends MemberDao

DB의 데이터 중 자주 조회하는 데이터를 메모리를 사용하는 캐시에 보관하면 조회속도를 향상 시킬 수 있다.

 

객체조립기 정의 : 의존 객체를 주입하는 것이다. 다시 말해 서로 다른 두 객체를 조립하는 것이다.

스프링은 DI를 지원하는 조립기이다. 객체 조립기는 특정 타입 클래스만 생성하는 반면 스프링은 범용 조립기이다.

 

@Bean이 붙은 메서드는 싱글톤 객체이다. 다른 설정 메서드에서  MemberDao를 아무리 호출해도 항상 같은 객체를 리턴한다. @Bean이면 싱글톤으로 생각하는 것이 편하다.

 

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

 

@Autowired 애노테이션은 스프링의 자동 주입 기능을 위한 것이다. @Autowired 애노테이션을 붙이면 해당 타입(데이터 타입)의 빈을 찾아서 필드에 할당 한다.

 

@Import 애노테이션은 함께 사용할 설정 클래스를 지정한다. 이때 배열을 사용하여 여러개의 설정 클래스를 지정 가능하다.

ex) Import(AppConf2.class) / Import({AppConf1.class, AppConf2.class})

 

getBean() 메서드는 사용할 빈 객체를 구할 때 사용한다.(getter 개념)

 

VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class);

 

getBean() 메서드의 첫 번째 인자("versionPrinter")는 빈의 이름이고 두 번째인자(VersionPrinter)는 빈의 타입이다.

빈의 이름을 지정하지 않고, 타입만으로도 빈을 구할 수도 있다.

 

VersionPrinter versionPrinter = ctx.getBean(VersionPrinter.class);

 

주입 대상 객체를 모두 빈으로 설정할 필요는 없다. 빈이 아닐 때 주입 방법은 두가지가 있다.

 

@Configuration
public class AppCtxNoMemberPrinterBean {
	private MemberPrinter printer = new MemberPrinter();//빈으로 생성하지 않음
	
	@Bean
	public MemberListPrinter listPrinter() {
		return new MemberListPrinter(memberDao(), printer);//생성자로 주입
	}

	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		infoPrinter.setMemberDao(memberDao());
		infoPrinter.setPrinter(printer);//set으로 주입
		return infoPrinter;
	}
}

 

첫번째는 생성자로 주입하는 방법과, 두번째는 set으로 주입하는 방법이 있다.

객체를 스프링 빈으로 등록유무의 차이는 스프링 컨테이너가 객체 관리 여부이다. 위 코드는 MemberPrinter를 빈으로 등록하지 않으므로 스프링 컨테이너에서 MemberPrinter를 구할 수 없다. 스프링 컨테이너는 자동 주입, 라이플 리사이클 관리등 객체관리를 위한 기능을 제공하되, 빈으로 등록한 객체에서만 사용가능하다.