[Spring] DI(Dependency Injection) 세 가지 방법

gillog·2020년 11월 8일
2

Spring

목록 보기
5/22

앞서 DI(Dependency Injection)에 대해서 알아보았는데, Spring에서 의존성을 주입하는 세 가지 방법에 대해서 다루어 보려고한다.

DI는 Spring에서만 사용되는 용어가 아니라 객체지향 프로그래밍에서는 어디에서나 통용되는 개념이다.

강한 결합

객체 내부에서 다른 객체를 생성하는 것은 강한 결합도를 가지는 구조이다.
A 클래스 내부에서 B 라는 객체를 직접 생성하고 있다면, B 객체를 C 객체로 바꾸고 싶은 경우에 A 클래스도 수정해야 하는 방식이기 때문에 강한 결합이다.

느슨한 결합

객체를 주입 받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해서 넘겨받는 것이다.
이렇게 하면 결합도를 낮출 수 있고, 런타임시에 의존관계가 결정되기 때문에 유연한 구조를 가진다.

SOLID 원칙에서 O 에 해당하는 Open Closed Principle 을 지키기 위해서 디자인 패턴 중 전략 패턴을 사용하게 되는데, 생성자 주입을 사용하게 되면 전략 패턴을 사용하게 된다.

의존성 주입의 종류로는 Field Injection, Setter Injection, Constructor Injection 방법이 있다.


Field Injection(필드 주입)

변수 선언부에 @Autowired Annotation을 붙인다.

@Component
public class SampleController {
    @Autowired
    private SampleService sampleService;
}

Field Injection을 사용하면 안되는 이유

  • 단일 책임(SRP)의 원칙 위반

의존성을 주입하기가 쉽다.
@Autowired 선언 아래 개수 제한 없이 무한정 추가할 수 있으니 말이다.

여기서 Constructor Injection을 사용하면 다른 Injection 타입에 비해 위기감을 느끼게 해준다.

Constructor의 parameter가 많아짐과 동시에 하나의 Class가 많은 책임을 떠안는다는 걸 알게된다.

이때 이러한 징조들이 Refactoring을 해야한다는 신호가 될 수 있다.


  • 의존성이 숨는다.

DI Container를 사용한다는 것Class가 자신의 의존성만 책임진다는게 아니라 제공된 의존성 또한 책임진다.

그래서 Class가 어떤 의존성을 책임지지 않을 때, 메서드나 생성자를 통해(Setter나 Constructor) 확실히 커뮤니케이션이 되어야한다.

하지만 Field Injection은 숨은 의존성만 제공해준다.


  • DI Container의 결합성과 테스트 용이성

DI Framework의 핵심 아이디어는 관리되는 Class가 DI Container에 의존성이 없어야 한다.

즉, 필요한 의존성을 전달하면 독립적으로 Instance화 할 수 있는 단순 POJO여야한다.

DI Container 없이도 Unit Test에서 Instance화 시킬 수 있고, 각각 나누어서 테스트도 할 수 있다.

Container의 결합성이 없다면 관리하거나 관리하지 않는 Class를 사용할 수 있고, 심지어 다른 DI Container로 전환할 수 있다.

하지만, Field Injection을 사용하면 필요한 의존성을 가진 Class를 곧바로 Instance화 시킬 수 없다.


  • 불변성(Immutability)

Constructor Injection과 다르게 Field Injection은 final을 선언할 수 없다.
그래서 객체가 변할 수 있다.


  • 순환 의존성

Constructor Injection에서 순환 의존성을 가질 경우 BeanCurrentlyCreationExeption을 발생시킴으로써 순환 의존성을 알 수 있다.

순환 의존성?

A Class가 B Class를 참조하는데 B Class가 다시 A Class를 참조할 경우,
A Class가 B Class를 참조하고, B Class가 C Class를 참조하고 C Class가 A Class를 참조하는 경우 이를 순환 의존성(Circular Dependency)이라고 부른다.


Field Injection은 스프링 컨테이너 말고는 외부에서 주입할 수 있는 방법이 없다.

Field Injection은 읽기 쉽고, 사용하기 편하다는 것 말고는 장점이 없다.


Setter Injection(수정자 주입)

Setter Injection선택적인 의존성을 사용할 때 유용하다.
상황에 따라 의존성 주입이 가능하다.
스프링 3.x Documents에서는 Setter Injection을 추천했었다.

Setter Injection은 set Method를 정의해서 사용한다.

@Component
public class SampleController {
    private SampleService sampleService;
 
    @Autowired
    public void setSampleService(SampleService sampleService) {
        this.sampleService = sampleService;
    }
}

Setter Injection으로 의존관계 주입은 런타임시에 할 수 있도록 낮은 결합도를 가지게 구현되었다.

하지만 Setter Injection을 통해서 Service의 구현체를 주입해주지 않아도 Controller 객체는 생성이 가능하다.

Controller 객체가 생성가능하다는 것은 내부에 있는 Service의 method 호출이 가능하다는 것인데,
set을 통해 Service의 구현체를 주입해주지 않았으므로, NullPointerException 이 발생한다.

주입이 필요한 객체가 주입이 되지 않아도 얼마든지 객체를 생성할 수 있다는 것이 문제다.

이 문제를 해결 할 수 있는 방법이 Constructor Injection이다.

Constructor Injection(생성자 주입)

아래 처럼 Constructor에 @Autowired Annotation을 붙여 의존성을 주입받을 수 있다.

@Component
public class SampleService {
    private SampleDAO sampleDAO;
 
    @Autowired
    public SampleService(SampleDAO sampleDAO) {
        this.sampleDAO = sampleDAO;
    }
}

@Component
public class SampleController {

	private final SampleService sampleService = new SampleService(new SampleDAO());
    
	...
}

Construtor Injection을 사용해야 하는 이유

Spring Framework Reference에서 권장하는 방법은 생성자를 통한 주입이다.

생성자를 사용하는 방법이 좋은 이유는 필수적으로 사용해야하는 의존성 없이는 Instance를 만들지 못하도록 강제할 수 있기 때문이다.

Spring 4.3버전부터는 Class를 완벽하게 DI Framework로부터 분리할 수 있다.

단일 생성자에 한해 @Autowired를 붙이지 않아도 된다.
Spring 4.3부터는 클래스의 생성자가 하나이고 그 생성자로 주입받을 객체가 Bean으로 등록되어 있다면 @Autowired를 생략할 수 있다.

또한 앞서 살펴본 Field Injection의 단점들을 장점으로 가져갈 수 있다.


  • null을 주입하지 않는 한 NullPointerException 은 발생하지 않는다.

의존관계 주입을 하지 않은 경우에는 Controller 객체를 생성할 수 없다.
즉, 의존관계에 대한 내용을 외부로 노출시킴으로써 컴파일 타임에 오류를 잡아낼 수 있다.

  • final 을 사용할 수 있다.

final로 선언된 레퍼런스타입 변수는 반드시 선언과 함께 초기화가 되어야 하므로 setter 주입시에는 의존관계 주입을 받을 필드에 final 을 선언할 수 없다.

final의 장점은 객체가 불변하도록 할 수 있는 점으로, 누군가가 Controller 내부에서 Service 객체를 바꿔치기 할 수 없다는 점이다.

  • 순환 의존성을 알 수 있다.

앞서 살펴 본 Field Injection에서는 컴파일 단계에서 순환 의존성을 검출할 방법이 없지만, Construtor Injection에서는 컴파일 단계에서 순환 의존성을 잡아 낼 수 있다.

  • 의존성을 주입하기가 번거로워 위기감을 느낄 수 있다.

Construtor Injection의 경우 생성자의 인자가 많아지면 코드가 길어지며 개발자로 하여금 위기감을 느끼게 해준다.

이를 바탕으로 SRP 원칙을 생각하게 되고, Refactoring을 하게 된다.

이러한 장점들 때문에 스프링 4.x Documents에서는 Constructor Injection을 권장한다.


굳이 Setter Injection을 사용하려면, 합리적인 default를 부여할 수 있고 선택적인(optional) 의존성을 사용할 때만 사용해야한다고 말한다.
그렇지 않으면 not-null 체크를 의존성을 사용하는 모든 코드에 구현해야한다.

결국 더 좋은 디자인 패턴과 코드 품질을 위해서는 Constructor Injection을 사용해야 한다.


🙆‍♂️ 참고사이트 🙇‍♂️

스프링 - 생성자 주입을 사용해야 하는 이유, 필드인젝션이 좋지 않은 이유[BY YABOONG]

[Spring] 의존성 주입(DI, Dependency Injection)의 세가지 방법[by 버터필드]

[Spring/Core] DI(의존성 주입)은 생성자 주입을 사용해라[효기미나]

[Spring]필드 주입(Field Injection) 대신 생성자 주입(Constructor Injection)을 사용해야 하는 이유[Carry On Progamming]

profile
🚀 기록보단 길록을 ⭐

1개의 댓글

comment-user-thumbnail
2022년 6월 9일

덕분에 좋은 내용 잘 보고 갑니다
감사합니다.

답글 달기