[Spring5입문] 스프링 DI

2021. 8. 3. 23:37CSE/Spring

1. 의존이란?

- 의존 : 객체 간의 의존, 한 클래스가 다른 클래스의 메서드를 실행할 때, 이를 '의존'한다고 표현한다

ex) MemberRegisterService 클래스가 MemberDao 클래스의 메서드를 실행하면, MemberRegisterService 클래스가 MemberDao 클래스에 의존한다고 표현한다

- 가장 쉬운 방법은 의존 대상의 객체를 직접 생성하여 의존대상.메서드() 형식으로 사용하는 것

=> 유지보수 관점에서 문제점 유발 가능

 

2. DI를 통한 의존 처리

- DI (Dependency Injection) : 의존 주입, 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식 사용

public class MemberRegisterService{
	private MemberDao memberDao;
    
    public MemberRegister(MemberDao memberdao){
    	this.MemberDao = memberDao;
    }
}
public class Example{
	public static void main(String[] args){
    	MemberDao memberDao = new MemberDao();
        
        MemberRegisterService swc = new MemberRegisterService(memberDao);
        //의존 객체를 생성자를 통해 주입한다 => 의존 주입
    }
}

 

3. DI와 의존 객체 변경의 유연함

- 만약에 MemberDao클래스에 의존하는 객체가 많은데, MemberDao 클래스를 상속한 CachedMemberDao 클래스에 의존하도록 바꾸기 위해서는 DI가 편리하다.

=> DI가 없다면 모든 의존하는 클래스의 파일들을 열어서 다음과 같이 변경해야 하기 때문에 불편함

MemberDao memberDao = new MemberDao();
//위를 아래로 바꾼다
MemberDao memberDao = new CachedMemberDao();

=> DI를 사용하면 의존주입 시 사용하는 의존 객체만 바꿔주면 된다.

 

4. 객체 조립기

- 객체를 생성하고 의존 객체를 주입해주는 클래스를 따로 작성하고 이를 객체 조립기라고 함.

- 의존 객체를 주입한다는 것은 서로 다른 두 객체를 조립한다고 생각할 수 있기 때문

import MemberDao;
import MemberRegisterService;
import ChangePasswordService;

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(){
    	reutnr pwdSvc;
    }
}

 

5. 스프링의 DI 설정

- 스프링이 DI를 지원하는 조립기와 유사한 기능을 제공한다.

import MemberDao;
import MemberRegisterService;
import ChangePasswordService;

@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());
    	reutnr pwdSvc;
    }
}

- @Bean 애노테이션이 붙여진 각 메서드마다 한 개의 빈 객체를 메서드의 이름으로 생성한다.

 

-이 후, 컨테이너를 생성해서 getBean()메서드를 이용해서 객체를 사용한다.

public class MainForSpring {
    private static ApplicationContext ctx = null;

    public static void main(String[] args) throws IOException {
        ctx = new AnnotationConfigApplicationContext(AppCtx.class);
        MemberRegisterService regSvc = ctx.getBean("memberREgSvc", MemberRegisterService.class);
    }
}

=> AnnotationConfigApplicationContext를 사용해서 스프링 컨테이너를 생성

=> 스프링 컨테이너로부터 이름이 "memberRegSvc"인 빈 객체를 구한다.

 

6. DI 방식

(1) 생성자 방식

* 장점 : 빈 객체를 생성하는 시점에 모든 객체가 주입된다.

- 생성자를 통해 의존 객체를 주입받아 필드에 할당한다.

public class MemberRegisterService {
	private MemberDao memberDao;

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

 

(2) 세터 메서드 방식

* 장점 : 세터 메서드 이름을 통해 어떤 의존 객체가 주입되는지 알 수 있다.

- 세터 메서드는 다음 규칙에 따라 작성한다.

  • 메서드 이름이 set으로 시작한다.
  • set 뒤에 첫 글자는 대문자로 시작한다.
  • 파라미터가 1개이다.
  • 리턴타입이 void이다.
public class MemberInfoPrinter {
    private MemberDao memDao;
    private MemberPrinter printer;

    public void setMemberDao(MemberDao memberDao){
        this.memDao = memberDao;
    }

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

 

* 싱글톤 잊지 말기!

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

=>이 경우, memberRegSvc()와 ChangePwdSvc()가 둘다 새로운 MemberDao 객체를 만들어내지않나?

=> 스프링 컨테이너가 생성한 빈은 싱글톤 객체라서 memberDao()는 항상 같은 객체를 리턴함.

=> 왜? : 스프링은 설정 클래스를 상속한 새로운 설정 클래스를 만들어서 사용한다.

 

7. 두 개 이상의 설정 파일 사용하기

- 스프링은 한 개 이상의 설정 파일을 이용해서 컨테이너를 생성할 수 있다.

- 이 때, 한 설정파일에서 다른 설정파일에 있는 Bean을 사용하기 위해서는 다음과 같이 @Autowired 애노테이션을 사용해서 자동 주입 기능을 사용하면 된다.

@Autowired
private MemberDao memberDao;

@Autowired
private MemberPrinter memberPrinter;

@Bean
public MemberRegisterService memberRegSvc(){
	return new MemberRegisterService(memberDao);
}

- 스프링 컨테이너도 거의 동일한 방식으로 생성할 수 있다. 다음과 같이 파라미터에 모든 설정클래스를 전달하면 된다.

ctx = new AnnotationConfigApplicationContext(AppConf1.class, AppConf2.class);

- 또 다른 방법으로는 @Import 애노테이션을 사용하는 방법이다. 다음과 같이 함께 사용할 설정 클래스를 지정한다.

@Configuration
@Import(AppConf2.class)
public class AppConfirmport{
	//생략
 }

 

* 스프링은 @Configuration 애노테이션이 붙은 설정 클래스를 내부적으로 스프링 빈으로 등록한다. 그리고 @Autowired가 붙은 대상에 대해 알맞은 빈을 자동으로 주입한다.

 

8. getBean() 메서드 사용

- getBean() 메서드는 다음과 같은 방식으로 사용 가능하다.

 

(1) 빈의 이름과 빈의 타입을 통해서

ctx.getBean(빈의 이름, 빈의 타입);

 

(2) 해당하는 빈의 타입에 하나의 빈 객체만 있을 경우에는 빈의 타입만으로 가능

ctx.getBean(빈의 타입);

 

+ 주입 대상 객체는 모두 빈 객체여야하나?

=> 일반 객체로 생성해서 주입할 수 있다.

=> 객체를 스프링 빈으로 등록할 때와 등록하지 않을 떄의 차이는 스프링 컨테이너가 객체를 관리하는지 여부이다.

@Configuration
public class AppCtxNoMemberPrinterBeen{
	private MemberPrinter printer = new MemberPrinter();
    
    // 생략
    
    public MemberinfoPrinter infoPrinter(){
    	MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
        infoPrinter.setPrinter(printer); //빈이 아님
        return infoPrinter;
    }
}