의존성 주입과 의존성 주입을 하는 방법

Glen·2023년 4월 15일
0

배운것

목록 보기
8/37

서론

"의존성을 주입한다"

여기서 의존성이란 무엇이고 의존성을 주입한다는 것은 무엇인가?

그리고 의존성을 주입하는 여러 방법과 장단점에 대해 알아보자.


본문

의존성 주입

우선 의존성이란 무엇일까?

바로 코드로 알아보자

class A {
    private B b;

    public void logic() {
        b.logic();
    }
}

class B {
    public void logic() {
        // 어떤 로직을 수행한다.
    }
}

A와 B라는 클래스가 있고, A의 로직이 B의 로직을 사용한다.

B의 로직이 변경되면 A의 로직이 변하게 되므로 A는 B를 의존한다고 볼 수 있다.

의존성은 객체가 행위를 하는 데 있어, 다른 객체가 필요하다는 것을 의미한다.

그러려면 객체를 사용하는 데 있어 자신의 생성보다, 의존하는 객체가 먼저 생성이 되어 있어야 한다.

B b = new B(); // A가 의존하는 B를 먼저 생성한다.
A a = new A();
a.logic() // NullPointerException이 발생한다.

하지만 단순히 의존하는 객체를 먼저 생성한다고 동작하지는 않는다.

A가 의존하는 B가 아직 초기화가 되어있지 않기 때문이다.

따라서 의존성을 관리하고 사용하게 하는 작업이 필요하다.

B b = new B(); 
A a = new A(b); // 의존하는 객체를 사용하게 한다.
a.logic();

즉, 의존성을 관리하고 사용하게 만드는 것을 의존성 주입이라고 한다.

의존성 주입의 방법

의존성을 주입한다는 것은 DI(Dependency Injection)라고 한다.

의존성 주입의 의도는 위키피디아에 다음과 같이 나와 있다.

어떻게 애플리케이션이나 클래스가 객체의 생성 방식과 독립적일 수 있는가?
어떻게 객체의 생성 방식을 분리된 구성 파일에서 지정할 수 있는가?
어떻게 애플리케이션이 다른 구성을 지원할 수 있는가?

그리고 스프링에서 의존성 주입은 다음과 같이 설명한다.

의존성 주입(DI)은 객체가 필요로 하는 다른 객체를 직접 생성하는 대신, 생성자 인수, 팩토리 메서드 인수 또는 속성으로 정의하고, 컨테이너가 객체를 생성할 때 해당 의존성을 주입하는 프로세스이다.

의존성 주입이란 객체를 직접 생성하고 설정하는 게 아닌, 어떠한 주체가 의존성을 주입해주는 것이다.

여기서 어떠한 주체란 사용자가 직접 코드를 설정하는 게 아닌, 의존성의 주입을 담당하는 객체라고 칭한다. (스프링에서는 Container, 정확히는 IoC Container라고 한다)

의존성 주입은 주로 프레임워크에서 제공하는 방법을 사용하므로 프레임워크를 기반으로 의존성을 주입하는 방법을 알아보자.

책 "리팩터링"의 저자인 마틴 파울러는 다음과 같이 3가지의 의존성 주입 패턴을 제시했다.

  1. 생성자 주입
  2. 세터 주입
  3. 인터페이스 주입

하지만 링크의 내용을 읽어보면 PicoConatiner라고 하는 프레임워크를 통해 예시 코드로 설명하고, 스프링 기반의 설명도 지금은 잘 사용하지 않는 XML을 사용하여 설명한다.

최근 스프링 프레임워크를 사용하며 의존성 주입을 할 때는, 주로 에너테이션으로 의존성을 주입한다.

따라서 스프링의 에너테이션 기반 의존성 주입을 기반으로 설명한다.

그렇다고 XML을 아예 사용하지 않는 것은 아니다. 스프링에서도 에너테이션과 XML 중 적절하게 선택하라고 권장하고 있다. (XML을 사용하면 소스코드를 수정하지 않아도 되므로)

의존성 주입은 주로 3가지 방법으로 나뉜다.

  1. 필드 주입
  2. 세터(Setter) 주입
  3. 생성자 주입

필드 주입

필드를 통해 의존성을 주입한다.

@Component
class A {
    @Autowired
    private B b;
}

의존이 필요한 필드에 @Autowired 에너테이션을 붙여 의존성을 주입할 수 있다.

사용이 제일 간단하고 직관적이다.

하지만 필드 주입은 스프링에서 권장되지 않는 방법이고, 스프링을 사용하지 않더라도 생성자가 없으면 의존하는 객체를 초기화할 없다.

객체를 초기화하려면 접근제어자가 public이 되어야 하는데, 필드를 public으로 설정하는 것은 캡슐화를 포기하겠다는 것과 마찬가지이다.

따라서, 필드 주입은 테스트 코드 작성을 하거나, 디버깅할 때 같은 특수한 상황을 제외하고 프로덕션 코드에서 사용하지 않는 것이 바람직하다.

@Autowired
MyService myService;

@Test
void myServiceTest() {
    myService.logic()
    ...
}

세터(Setter) 주입

세터를 통해 의존성을 주입한다.

@Component
class A {
    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

세터에 @Autowired 에너테이션을 붙여 의존성을 주입할 수 있다.

세터를 사용하면 필드 주입과 다르게 접근제어자를 private로 유지하면서, 캡슐화를 어느 정도 지킬 수 있다.

여기서 어느 정도 지킬 수 있다는 말은, 결국 세터의 접근제어자를 public으로 설정을 해줘야 한다.

또한 세터를 만들어 두는 것은 객체지향 생활 체조 원칙을 위배한다.

그렇다고 세터 주입을 사용하지 말라는 것은 아니다.

세터 주입을 사용하면 되면 객체가 생성된 후 지연 로딩이 가능하거나, 상황에 따라 다른 객체로 바꿀 수 있기 때문이다.

하지만 스프링을 사용하지 않을 때, 세터를 호출하지 않으면 NPE가 발생한다.

MyService myService = new MyService();
myService.logic(); // NPE 발생

따라서 적절한 상황을 고려하고 세터 주입을 사용하자

생성자 주입

생성자를 통해 의존성을 주입한다.

@Component
class A {
    private B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }
}

생성자에 @Autowired 에너테이션을 붙여 의존성을 주입시킬 수 있다.

생성자로 의존성을 주입하면 우선 NPE를 방지할 수 있다.

MyService myService = new MyService(); // 컴파일 에러
myService.logic();

또한 필드를 final로 지정할 수 있어 불변으로 설계할 수 있다.

게다가 필드가 많아질수록 생성자의 파라미터도 많아지므로, 객체가 너무 많은 책임을 지고 있는지 눈으로 바로 확인할 수 있다.

@Component
class A {
    private final B b;
    private final C c;
    private final D d;

    @Autowired
    public A(B b, C c, D d) { // 파라미터가 3개 이상이면 눈에 띈다.
        this.b = b;
        this.c = c;
        this.d = d;
    }
}

게다가 Spring 4.3 부터 생성자가 하나만 있을 경우 @Autowired 에너테이션을 생략 가능하다.

@Component
class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

이렇게 생성자 주입을 사용하면 이점이 많아 다른 방법을 사용하지 않아야 할 것 같다.

하지만 생성자 주입은 무조건 객체가 생성될 때만 의존성을 주입 시킬 수 있으므로 유연성이 떨어진다.

그래서 스프링 공식 문서에서는 필수 종속성에는 생성자를 사용하고 선택적 종속성에는 세터를 사용하도록 권장한다.


결론

의존성이란 객체가 행위를 하는 데 있어, 다른 객체가 필요하다는 것을 의미한다.

의존성 주입은 의존성을 관리하고 사용하게 만드는 것이다.

의존성 주입의 의도를 지키려면 객체의 생성과 사용을 분리해야 한다.

따라서 직접 의도를 지키려면 구현이 복잡해지기 때문에 의존성 주입 프레임워크를 주로 이용한다.

유명한 의존성 주입 프레임워크는 스프링이 있다.

스프링을 사용하면 필드 주입, 세터 주입, 생성자 주입을 통한 의존성 주입이 가능하다.

각 방법마다 장단점이 있고, 필드 주입은 되도록 사용을 안 하는 것이 바람직하다.

세터 주입과 생성자 주입 중 생성자 주입을 먼저 선택하고, 선택적으로 의존이 필요할 경우 세터 주입을 사용하자.

끝.

profile
꾸준히 성장하고 싶은 사람

0개의 댓글