본문 바로가기

스프링 부트

📋 3. 스프링 DI

2021-05-28글

스프링은 객체 컨테이너

BeanFactory

  • 객체 생성과 검색에 대한 기능 정의
  • getBean() : 생성된 객체를 검색하는데 필요한 메서드
  • 객체 검색 이외에도 싱글톤인지 프로토타입 빈인지 확인하는 기능도 있음

AnnotationContext

  • 메시지, profile, 환경 변수 등을 처리할 수 있는 기능을 추가로 쩡의

의존이란?

DI를 통한 의존 처리

의존이란?

  • 한 클래스가 다른 클래스의 메서드를 실행할 때 의존한다고 표현

  • 변경에 의해 영향을 받는 관계를 의미

  • DI는 의존하는 객체를 직접 생성하지 않고, 의존 객체를 주입받는 방식을 사용

  • DI를 통해 객체를 주입하면 유지보수성이 더 올라간다.

스프링의 DI 설정

  • 스프링은 DI를 이용해서 객체를 서로 연결해주는 조립기이다.
    • 특정 타입의 클래스만 생성하는 조립기가 아닌 범용 조립기이다.

설정 클래스

@Configuration
public class AppCtx {

    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDao()); // memberDao()    가 생성한 객체를 주입
    }

    @Bean
    public ChangePasswordService changePwSvc() {
        ChangePasswordService pwsSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao());
        return pwdSvc;

    }
}
  • @Configuration : 스프링 설정 클래스
  • @Bean : 해당 메서드가 생성한 객체를 스프링 빈으로 설정
    • 메서드 이름을 빈 객체의 이름으로 사용

설정 클래스만 만들어서 끝나는 것이 아닌, 설정 클래스로 컨테이너를 생성해야 한다.

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class)

// ...
MemberRegisterService regSvc = ctx.getBean("memberRegSvc", MemberRegisterService.class)
  • 컨테이너를 생성하면 getBean() 를 이용해 사용할 객체를 구할 수 있음
  • 스프링 컨테이너 ctx 로터 이름이 "memberRegSvc"인 빈 객체를 구함

DI 방식 - 생성자 방식

public class StationService {
    private StationDao stationDao;

    public StationService(StationDao stationDao) {
        this.stationDao = stationDao;
    }
}

DI 방식 - 생성자 방식

public class StationService {
    private StationDao stationDao;

    public void setStationDao(StationDao stationDao) {
      this.stationDao = stationDao;
    }
}

@Configuration 설정 클래스의 @Bean 설정과 싱글톤

@Configuration
public class AppCtx {

    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDao()); // memberDao()    가 생성한 객체를 주입
    }

    @Bean
    public ChangePasswordService changePwSvc() {
        ChangePasswordService pwsSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao());
        return pwdSvc;

    }
}
  • memberDao()는 매번 새로운 MemberDao 객체를 생성해서 반환한다.

그러면 memberRegSvc() 가 반환하는 객체와 changePwSvc()가 반환하는 객체는 서로 다른 MemberDao 객체를 사용하는 것인가?

🙅‍♀️ 스프링 컨테이너가 생성한 빈은 싱글톤 객체이다.
@Bean이 붙은 메서드에 대해 한 개의 객체만 생성하고 이를 보관했다가, 동일한 객체를 반환한다.


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

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

@Configuration, @Autowired

AppConf1

@Configuration
public class AppConf1 {

    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberPrinter();
    }

}

AppConf2

@Configuration
public class AppConf2 {
    @Autowired
    private MemberDao memberDao;
    @Autowired
    private MemberPrinter memberPrinter;

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

    @Bean
    public ChangePasswordService changePwdSvc() {
        ChangePasswordService pwdSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao);
        return pwdSvc;
    }

    @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;
    }
}
  • @AutoWired : 스프링의 자동 주입 기능. 스프링 설정 클래스 필드에 해당 어노테이션을 붙이면 해당 타입 빈을 찾아서 필드에 할당한다.
    • AppConf2의 memberDao 필드에는 AppConf1 클래스에서 설정한 빈이 할당된다.
  • 스프링 컨테이너는 AppConf2 객체를 빈으로 등록하고 @AutoWired 가 붙은 대상에 대해 알맞은 빈을 자동으로 주입한다.

설정 클래스가 여러개일 때 스프링 컨테이너를 생성하는 방법은?

ctx = new AnnotationConfigApplicationContext(AppConf1.class, AppConf2.class)
  • AnnotationConfigApplicationContext의 생성자의 인자는 가변 인자이다.

@Import 어노테이션 사용

  • 함께 사용할 설정 클래스를 지정
@Configuration
@Import({AppConf2.class})
public class AppConfImport {

    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberPrinter();
    }
}
  • 지정한 클래스도 함께 사용하기 때문에 스프링 컨테이너를 생성할 때 AppConfi2를 지정할 필요가 없다.

getBean()

VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class)
  • 첫번째 인자는 빈의 이름
  • 두번째 인자는 빈의 타입
  • 빈의 이름을 지정하지 않고 타입만으로 빈을 구할 수도 있다.
    • 대신 해당 타입의 빈 객체가 한 개만 존재해야한다.

주입 대상 객체를 모두 빈 객체로 설정해야하나?

🙅‍♀️ 스프링 컨테이너가 객체를 관리하는지 여부만 다를 뿐이다.
참고로 스프링 컨테이너는 자동 주입, 라이프사이클 관리 등 객체 생성 외에도 객체 관리를 위한 다양한 기능을 제공한다.