본문 바로가기

DI

의존 주입

Dependency Injection

  1. 의존
    : 한 클래스가 다른 클래스의 메서드를 실행 하는 것을 뜻한다.
  2. 주입
    : 의존 하는 객체를 직접 생성하지 않고 의존 객체를 전달 받는 방식
  3. ex)  MemberDao 클래스의 메소드가 수정이 된다면 MemberfindService 영향을 받는다.
    → 즉, MemberfindService는 MemberDado에 의존한다.
public class MemberfindService {
  private MemberDao memberDao;

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

  public Long findMemberId(RegisterRequest req) {
    Member member = memberDao.selectByEmail(req.getEmail());
    return member.getId();
  }
}

조립기

스프링을 사용하지 않은 조립

ex ) Assembler 클래스 생성

 public class Assembler {

     private MemberDao memberDao;
     private MemberRegisterService regSvc;

     public Assembler() {
         memberDao = new MemberDao();
         regSvc = new MemberRegisterService(memberDao);
     }

     public MemberDao getMemberDao() {
         return memberDao;
     }

     public MemberRegisterService getMemberRegisterService() {
         return regSvc;
     }

 }

ex ) Assembler 클래스 생성

private static Assembler assembler = new Assembler();
MemberRegisterService regSvc = assembler.getMemberRegisterService();

스프링을 사용한 조립

  1. 스프링에서 annotation 방식으로 주로 사용, 과거에는 XML로 설정하는 방법도 사용하였다.
    1) @Configuration : 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스
    2) @Bean : 스프링 컨테이너에 의해서 객체가 만들어짐
@Configuration
public class BeanConfig{

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

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

    @Bean
    public MemberInfoPrinter infoPrinter() {
        MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
        infoPrinter.setMemberDao(memberDao());
        infoPrinter.setPrinter(memberPrinter());
        return infoPrinter;
    }
}

스프링에서의 DI의 방법

필드 주입

Field -Based Dependency Injection - 지양하는 방법

장점

  1. 선언이 가장 간단하다.

단점

  1. 의존 주입이 많이 질 수록 관계가 잘 보이지 않는다.
    순환 참조를 Compile 과정에서 발견하지 못한다.
  2. SRP - 단일 책임 원칙에 반한다
    1. SRP (Single Responsibility Principle)
      : 클래스는 단 한개의 책임을 가져야한다.
    2. Autowired 선언 아래 개수 제한 없이 무한히 추가가 가능하기 때문이다.
  3. 강한 결합
    : 의존 되고 있는 클래스를 수정하려면 의존 하는 클래스도 수정해줘야한다.

생성자 주입

Constructor - Based Dependency Injection - 권장 방법

장점

  1. 빈 객체를 생성하는 시점에 모든 의존 객체가 주입된다
  2. 필수적으로 사용해야 하는 레퍼런스 없이는 인스턴스를 만들지 못하게 강제한다.
  3. 순환 참조를 방지 할 수 있다.
    순환 참조를 Compile 과정에서 발견 가능하다.
  4. 테스트 코드를 작성하기 쉽다.

단점

  1. 어떤 의존 객체가 주입되는지 보려면 생성자를 확인해야한다

ex )

//MemberRegisterService.java
public class MemberRegisterService {
    private MemberDao memberDao;

  //생성자를 통해 의존 객체를 주입 받음
    public MemberRegisterService(MemberDao memberDao) {
    //주입 받은 객체를 필드에 할당
        this.memberDao = memberDao;
    }

    public Long regist(RegisterRequest req) {
    //주입 받은 의존 객체의 메서드를 활용
        Member member = memberDao.selectByEmail(req.getEmail());
        ...
        memberDao.insert(newMember);
        return newMember.getId();
    }
}

//BeanConfig.java
@Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDao());
}

수정자 메서드를 통한 주입

Setter - Based Dependency Injection

장점

  1. 메서드 이름을 통해 어떤 의존 객체가 주입되는지 바로 알 수 있다.
  2. 의존성 주입이 선택적으로 필요한 경우에 사용 가능하다.

단점

  1. 필요한 의존 객체를 전달하지 않아도 빈 객체가 생성되기 때문에 NullPointerException 이 발생 가능하다.
  2. final 선언 불가능

ex)

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

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

//AppCtx.java
@Bean
    public MemberInfoPrinter infoPrinter() {
        MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
        infoPrinter.setMemberDao(memberDao());
        infoPrinter.setPrinter(printer);
        return infoPrinter;
    }

//main.java
private static void processInfoCommand(String[] arg) {
        if (arg.length != 2) {
            printHelp();
            return;
        }
        MemberInfoPrinter infoPrinter = 
                ctx.getBean("infoPrinter", MemberInfoPrinter.class);
        infoPrinter.printMemberInfo(arg[1]);
    }

@Bean

  • 객체를 Singleton 빈 객체로 생성해 놓고 어디서든 불러서 사용 가능하게 만든 것
  • 클래스를 재사용하기 편하게 함
  • IoC방식으로 관리하는 오브젝트. Managed object라고 불리기도 한다. 스프링은 Bean 객체의 생성 및 제어를 담당한다.
    1. IOC 방식(Inversion of Control) - 제어의 역전
      : 객체의 생성, 생명주기 관리를 컨테이너가 제어 하는 것
      1. 분류
        1. DL : 저장소에 저장되어 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 Lookup 하는 것
        2. DI : 각 클래스간의 의존관계를 빈 설정 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것
    2. Spring DI 컨테이너
      1. Spring DI 컨테이너가 관리하는 객체를 Bean이라고 하고 Bean을 관리하는 컨테이너를 BeanFactory라고 부른다.
        1. BeanFactory : Bean을 등록 생성, 조회, 반환 관리를 한다.
        2. ApplicationContext : BeanFactory에 확장판으로 각종 부가 서비스를 제공한다.

Spring BeanFactory

결론

생성자 주입을 사용한다.

  • 테스트 코드 작성하기 좋다.
    : Class들이 많아지고, 외부 의존성(Jpa 등)이 발생하다 보면, 생성자 주입을 통해 Test 코드 작성이 쉬워지는 것이 매우 큰 장점이라는 것을 경험 할 수 있다.
  • 순환 참조, NullPointException(없는 객체 주입) 예방 가능하다.
    : class들이 커지고 Layer들이 깊어지면 Human Error로 순환 참조, NullPointException 이 발생 할 수 있는데, 이 때 Compile 과정에서 감지할 수 있다.
  • final 선언 가능
    : 생성자를 통해 주입하기 때문에 final로 필드 선언 가능하고, immutable(불변) 하다는 것을 드러낼 수 있다.

#
출처: 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문 - 최범균 저'

리티의 성장 일기

성장 일지를 하루하루 써보려고 합니다.