BackEnd Developer, Love Crossfit, Welcome to Jay's blog

의존성 주입 (Dependency Injection, DI)

|

Index

  • Review
  • 스프링 DI
    • 의존이란
    • 의존 관계 주입 DI
    • 스프링 DI
  • 의존 자동 주입
    • Annotation을 이용한 주입, @Autowired
  • References

Review

스프링 컨테이너란
설정 코드를 참조하여 객체의 생성부터 의존 관계 설정 등 객체의 모든 것을 외부에서 대신 관리해주는 친구. 런타임때 필요한 객체를 주입시켜줌. 설정 코드는 객체간 생성 방법과 관계를 정의해 놓은 설계도(메타데이터).

스프링 DI

스프링은 DI 기반의 IoC 프레임워크이다.

다시 말해, 스프링은 IoC 기반의 프레임워크이며 이 IoC 기능의 핵심 동작원리는 DI(Dependency Injection)이다. 스프링의 DI 기능 제공은 다른 프레임워크와 차별화되는 기능이다.

DI를 설명하기에 앞서 먼저 의존에 대해 알아보자.

의존이란

DI에서 말하는 의존은 ‘객체 간 의존’을 말한다. 클래스 내부에서 사용할 객체를 직접 생성해서 다른 클래스의 메소드를 실행할 때 이를 ‘의존’한다고 표현한다.

그런데 만약 의존하는 객체에 변경이 일어난다면, 그 객체를 사용하고 있는 클래스 모두를 수정해야하는 일이 생겨난다. 규모가 커질수록 고쳐야 할 곳은 많아질 것이고 유지보수도 힘들어 질 것이다. 그러다 보니 자연스레 한 곳에서 일괄적으로 관리할 수 있는 방법을 찾게 되었고, 그 결과 의존 객체를 직접 생성하지 않고 외부로부터 전달받는 방식인 DI(의존 주입)을 사용한다.

의존 관계 주입 DI

  • Dependency Injection
  • 객체를 생성할 때 필요한 의존들을 외부로부터 전달 받는 것
    • 생성자 / 메소드 파라미터를 통해 의존 객체를 전달받는다
    • 의존 관계 주입을 위해 꼭 스프링이 필요한 건 아니다.

스프링 DI

  • 자신이 사용할 객체에 대한 선택과 생성의 제어권을 외부로 - 스프링 컨테이너 - 넘겨 사용할 객체를 외부로부터 전달 받는 것
  • 외부 환경 설정(xml, 어노테이션 이용한 java 클래스)에서 의존관계를 생성자나 세터 메소드를 통해 주입 받는다.
  • 설계 시점과 코드에서는 클래스와 인터페이스 사이의 느슨한 의존관계만 만들어 놓고, 런타임 시에 의존관계를 결정한다.
  • DI를 통해 모듈간 결합도를 낮춰 유연한 변경 가능하다.

여기서 인터페이스 타입을 사용하는 이유는, 동적으로 여러 서브 클래스들을 제공받아 의존 객체 변경을 쉽게 하기 위함이다.

의존 자동 주입

지금까지 우리는 객체간 의존 관계를 직접 설정 코드에서 - 생성자/세터메소드를 통해 - 지정하였다. 이번에는 코드로 작성하지 않아도 스프링이 알아서 연결 해주는 방법에 대해 알아보자

Annotation을 이용한 주입, @Autowired

프로퍼티(필드), 메소드, 생성자 위에 @Autowired를 붙이면 설정 클래스에 의존 객체를 명시하지 않더라도 스프링이 알아서 해당 타입의 빈을 찾아 설정해준다.

@Configuration
public class DaoFactory {
    @Bean
    public UserDao userDao() {
        // 자동 주입을 사용하지 않는다면 new UserDao(connectionMaker());처럼 직접 의존 객체를 넣어주어야 한다
       return new UserDao();
    }

    @Bean
    public ConnectionMaker connectionMaker() {
        return new DConnectionMaker();
    }
    ...
}

public class UserDao {
    // 필드 사용 방법
    @Autowired
    ConnectionMaker connectionMaker;

    // 또는 메소드 사용 방법
    @Autowired
    public void setConnectionMaker(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
}

@Autowired 애노테이션을 필드나 세터 메소드에 붙이면 스프링은 타입이 일치하는 빈 객체를 찾아서 주입해준다. UserDao의 필드 connectionMaker 타입은 ConnectionMaker이므로 일치하는 타입을 가진 connectionMaker 빈이 주입된다.

프로퍼티가 그룹형인 경우 - 배열, 컬렉션(List 등) - 스프링이 매칭되는 빈을 한 번에 모두 찾아 연결 해준다.

setConnectionMaker() 메소드의 connectionMaker 파라미터 타입이 ConnectionMaker이므로 일치하는 타입을 가진 connectionMaker 빈이 주입된다.

만약 일치하는 빈이 없다면? 일치하는 빈이 두개 이상이라면?

=> 스프링은 자동 주입에 실패하고 에러를 내뱉는다.
=> 이를 원하지 않을 경우 @Autowired의 required 속성값을 false로 지정하면 자동 주입을 수행하지 않고 그냥 지나치게 만들 수 있다.

설정 코드에서 의존 주입을 했는데 자동 주입 대상이기도 하면 어떻게 될까?

=> 설정 클래스에서 세터 메소드를 통해 의존을 주입해도 해당 세터 메소드에 @Autowired 애노테이션이 붙어 있으면 자동 주입을 통해 일치하는 빈을 주입한다. 따라서 자동 주입과 수동 주입 코드가 섞여 있다면 잘못된 주입으로 인해 에러 발생시 원인 찾기가 힘들 수 있다. 그래서 하나로 일관 되게 사용하는 것이 좋다.

같은 타입의 빈이 여러개라면 어떻게 될까?

=> 에러 발생
=> 어떤 빈 타입을 사용할지 지정해줌으로써 이를 피할 수 있다. 이는 @Primary로 우선권을 부여하거나 @Qualifier로 이름을 주어 해결한다.

@Configuration
public class AppCtx {
    @Bean
    @Qualifier("korean")
    public Member Member1() {
        return new Member();
    }

    @Bean
    public Member Member2() {
        return new Member();
    }
}

public class MemberList {
    private Member member;
    ..
    @Autowired
    @Qualifier("korean")
    public void setMember(Member member) {
        this.member = member
    }
}

setMember() 메소드에 @Autowired 애노테이션이 붙었으므로 Member 타입의 빈이 자동 주입된다. 이때 @Qualifier 값이 “korean”이므로 한정 값이 “korean”인 빈을 주입 후보로 사용한다.

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

애플리케이션 규모가 커질 수록 설정 클래스의 개수를 여러개로 나누어 관리를 하게 된다. 이때, 여러 설정 클래스에 흩어져 있는 빈들을 하나의 설정 클래스에서 참조해야 할 경우 후보들을 일일히 지정해주기 보다는 @Import로 사용할 설정 파일들을 불러와 사용하는 것이 더 좋다.

@Configuration
@Import({AppConf1.class, AppConf2.class})
@Import(AppConf3.class)
public class AppConfImport {
    ...
}

References

토비의 스프링 3.1
초보 웹 개발자를 위한 스프링5 프로그래밍 입문
다시 써보는 스프링 5 레시피