의존성 주입 : 필드 주입 대 생성자 주입?


61

나는 이것이 논쟁의 여지가 많다는 것을 알고 있으며 최선의 접근 방식에 대한 의견은 시간이 지남에 따라 변하는 경향이 있습니다.

나는 생성자 주입의 이점에 대해 다른 블로그 (예 : petrikainulainenschauderhaftfowler )를 읽을 때까지 클래스에 독점적으로 필드 주입을 사용했습니다 . 필자는 필요한 의존성에 대한 생성자 주입과 선택적 의존성에 대한 setter 주입을 사용하도록 방법을 전환했습니다.

그러나 나는 최근 조롱 프레임 워크 인 JMockit의 저자와 토론에 참여 했으며, 여기서 그는 생성자 및 세터 주입을 나쁜 습관으로보고 JEE 커뮤니티가 그와 동의한다는 것을 나타냅니다.

오늘날의 세상에는 주사를 선호하는 방법이 있습니까? 필드 주입이 선호됩니까?

지난 몇 년 동안 현장 주입에서 생성자 주입으로 전환 한 후 사용하는 것이 훨씬 명확하지만 내 관점을 다시 검토해야하는지 궁금합니다. JMockit (Rogério Liesenfeld) 의 저자는 DI에 정통한 사람이므로 생성자 / 설정자 주입에 대해 너무 강하게 느끼는 내 접근 방식을 검토 할 의무가 있습니다.



2
생성자 주입 또는 세터 주입 을 사용할 수 없다면 클래스에 종속성을 어떻게 전달해야합니까? Mockit 직원과의 대화가 비공개가 아닌 한 토론에 더 잘 연결될 수 있습니다.
Robert Harvey

2
@DavidArno : 불변 클래스에서 상태는 생성자
Robert Harvey

1
@Davkd는 빈혈 도메인 모델입니다. 확실히 지지자가 있지만 더 이상 객체 지향적이지는 않다. 데이터와 해당 데이터에 작용하는 기능이 함께 존재합니다.
Richard Tingle

3
@David 함수형 프로그래밍에는 아무런 문제가 없습니다. 그러나 자바는 기본적으로 객체 지향적으로 설계되었으며 매우 신중하지 않으면 끊임없이 싸우고 빈혈 도메인 모델로 끝날 것입니다 . 함수 지향 코드에는 아무런 문제가 없지만 적절한 도구를 사용해야합니다. 람다가 함수 기반 프로그래밍의 요소를 추가했지만 Java는 그렇지 않습니다.
Richard Tingle

답변:


48

현장 주입은 내 취향 에 따라 너무 "먼 거리에서 바보 같은 행동" 입니다.

Google 그룹스 게시물에서 제공 한 예를 고려하십시오.

public class VeracodeServiceImplTest {


    @Tested(fullyInitialized=true)
    VeracodeServiceImpl veracodeService;
    @Tested(fullyInitialized=true, availableDuringSetup=true)
    VeracodeRepositoryImpl veracodeRepository;
    @Injectable private ResultsAPIWrapper resultsApiWrapper;
    @Injectable private AdminAPIWrapper adminApiWrapper;
    @Injectable private UploadAPIWrapper uploadApiWrapper;
    @Injectable private MitigationAPIWrapper mitigationApiWrapper;

    static { VeracodeRepositoryImpl.class.getName(); }
...
}

그래서 기본적으로 당신이 말하는 것은 "이 클래스에는 개인 상태가 있습니다. @injectable주석 을 첨부했습니다 . 이는 내 상태가 모두 비공개 로 선언되었지만 외부에서 일부 에이전트가 상태를 자동으로 채울 수 있음을 의미합니다 . "

나는 이것에 대한 동기를 이해한다. 수업을 제대로 구성하는 데 따르는 많은 의식을 피하려는 시도입니다. 기본적으로 한 사람이 말하는 것은 "이 보일러 플레이트를 모두 작성하는 데 지 쳤기 때문에 모든 상태에 주석을 달고 DI 컨테이너가 설정하도록하겠습니다."

완벽하게 유효한 관점입니다. 그러나 해결해야 할 언어 기능에 대한 해결 방법이기도합니다. 또한 왜 거기서 멈춰? 전통적으로 DI는 컴패니언 인터페이스가있는 각 클래스에 의존했습니다. 어노테이션이있는 모든 인터페이스를 제거하지 않겠습니까?

대안을 고려하십시오 (이것은 더 잘 알기 때문에 C # 일 것입니다. 그러나 Java에는 정확히 동등한 것입니다).

public class VeracodeService
{        
    private readonly IResultsAPIWrapper _resultsApiWrapper;
    private readonly IAdminAPIWrapper _adminApiWrapper;
    private readonly IUploadAPIWrapper _uploadApiWrapper;
    private readonly IMitigationAPIWrapper _mitigationApiWrapper;

    // Constructor
    public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper,         IMitigationAPIWrapper mitigationApiWrapper)
    {
         _resultsAPIWrapper = resultsAPIWrapper;
         _adminAPIWrapper = adminAPIWrapper;
         _uploadAPIWrapper = uploadAPIWrapper;
         _mitigationAPIWrapper = mitigationAPIWrapper;
    }
}

이미이 수업에 대해 알고 있습니다. 불변의 클래스입니다. 상태는 생성자 (이 경우 참조)에서만 설정할 수 있습니다. 그리고 모든 것이 인터페이스에서 파생되므로 생성자에서 구현을 바꿀 수 있습니다. 여기서 모의가 나오는 곳입니다.

이제 DI 컨테이너는 생성자를 통해 반영하여 새로운 객체가 필요한 객체를 결정합니다. 그러나 그 반성은 일류 방식으로 공개 회원 에게 이루어지고 있습니다 . 즉, 메타 데이터는 이미 클래스의 일부이며 생성자에서 선언되었으며 클래스의 목적은 클래스에 필요한 종속성을 제공하는 것입니다.

이것은 많은 상용구이지만, 언어가 설계된 방식입니다. 주석은 언어 자체에 내장되어 있어야 할 내용에 대한 더러운 해킹처럼 보입니다.


내가 당신의 게시물을 잘못 읽지 않으면, 당신은 실제로 예제를 올바르게보고 있지 않습니다. 내 구현은 VeracodeServiceJava와 C #에도 불구하고 작성한 것과 거의 동일합니다. 은 VeracodeServiceImplTest실제로 단위 테스트 클래스입니다. @Injectable필드 본질적 콘텍스트에 삽입되는 물체 조롱된다. 이 @Tested필드는 DI 생성자가 정의 된 객체 / 클래스입니다. 필드 주입보다 생성자 주입을 선호하는 관점에 동의합니다. 그러나 내가 언급했듯이, JMockit의 저자는 그 반대를 느끼고 이유를 이해하려고 노력하고 있습니다
Eric B.

이 토론 의 첫 번째 게시물 을 보면 Repo 수업에서 거의 똑같은 내용을 볼 수 있습니다.
Eric B.

테스트 수업에 대해서만 이야기하는 경우 중요하지 않을 수 있습니다. 수업을 테스트하기 때문입니다.
Robert Harvey

아니요-테스트 수업에 대해 이야기하고 있지 않습니다. 프로덕션 클래스에 대해 이야기하고 있습니다. 프레임 워크가 모의 / 테스트 프레임 워크이므로 토론 그룹의 예제는 단위 테스트입니다. (모의 프레임 워크가 생성자 주입을 지원해야하는지에 대한 전체 토론이 진행됩니다)
Eric B.

3
+1 : 예, C # 버전에서는 마법이 없습니다. 표준 클래스이고 종속성이 있으며 클래스가 사용되기 전에 한 시점에 모두 주입됩니다. 클래스는 DI 컨테이너와 함께 사용하거나 사용하지 않을 수 있습니다. 이 수업에서 IOC 또는 "자동 의존성 주입"프레임 워크는 없습니다.
이진 걱정

27

적은 테스트 초기화 boiletplate의 주장은 유효하지만 고려해야 할 다른 문제가 있습니다. 우선, 당신은 질문에 대답해야합니다 :

리플렉션만으로 수업을 인스턴스화 할 수 있습니까?

필드 주입을 사용하면 리플렉션을 사용하여 객체를 인스턴스화하고 이러한 특정 주입 주석을 지원하는 종속성 주입 환경에 대한 클래스의 호환성 이 좁혀 집니다. Java 언어를 기반으로하는 일부 플랫폼은 리플렉션 ( GWT ) 도 지원하지 않으므로 필드 삽입 클래스는 호환되지 않습니다.

두 번째 문제는 성능 입니다. 생성자 호출 (직접 또는 리플렉션)은 항상 여러 리플렉션 필드 할당보다 빠릅니다. 종속성 주입 프레임 워크는 반사 분석을 사용하여 종속성 트리를 작성하고 반사 생성자를 작성해야합니다. 이 프로세스로 인해 추가 성능 저하가 발생합니다.

성능은 유지 보수성에 영향을줍니다 . 각 테스트 스위트가 일종의 의존성 주입 컨테이너에서 실행되어야하는 경우 수천 단위 테스트는 수십 분 동안 지속될 수 있습니다. 코드베이스의 크기에 따라 문제가 될 수 있습니다.

이것은 모두 많은 새로운 질문을 제기합니다.

  1. 다른 플랫폼에서 코드의 일부를 사용할 확률은 얼마입니까?
  2. 몇 개의 단위 테스트가 작성됩니까?
  3. 각각의 새로운 릴리스를 얼마나 빨리 준비해야합니까?
  4. ...

일반적으로 프로젝트가 더 크고 중요할수록 이러한 요소가 더 중요합니다. 또한 품질 측면에서는 일반적으로 호환성, 테스트 가능성 및 유지 관리 가능성을 높이기 위해 코드를 유지하고 싶습니다. 철학적 인 관점에서, 필드 주입은 캡슐화를 깨뜨립니다. 캡슐화 는 Java의 주요 패러다임 인 객체 지향 프로그래밍 의 네 가지 기본 요소 중 하나입니다 .

필드 주입에 대한 많은 주장.


3
+1 신경을 친다. 클래스를 인스턴스화하기 위해 리플렉션이나 DI / IoC 프레임 워크가 필요하지 않습니다. 암스테르담에서 파리로가는 로마로 운전하는 것과 같습니다. DI를 좋아한다는 점을 명심하십시오. 프레임 워크에 대해 확실하지 않습니다.
Marjan Venema

1
큰 논쟁. 그것은 내가 가진 많은 생각을 말로 표현하지만 다른 사람들이 같은 방식으로 느끼는 것을 보게되어 기쁩니다. 필드 주입을 찾는 데 사용했던 것만 큼 놀랍습니다. 나는 그것이 "더 쉬워"했기 때문에 나는 그것을 아주 좋아했다고 생각합니다. 생성자 주입이 훨씬 명확 해졌습니다.
Eric B.

19

현장 주입은 나로부터 확실한 "아니오"투표를받습니다.

Robert Harvey와 마찬가지로, 그것은 내 취향에 비해 너무 자동적입니다. 나는 암시 적 코드보다 명시 적 코드를 선호하며 코드를 이해하고 추론하기가 어려워 명백한 이점을 제공 할 때만 간접적 허용을 허용합니다.

Maciej Chałapuk와 마찬가지로 클래스를 인스턴스화하기 위해 반영 또는 DI / IoC 프레임 워크가 필요하지 않습니다. 암스테르담에서 파리로가는 로마로 운전하는 것과 같습니다.

DI와 그로 인한 모든 이점을 사랑합니다. 클래스를 인스턴스화하기 위해 프레임 워크가 필요한지 확실하지 않습니다. 특히 IoC 컨테이너 또는 다른 DI 프레임 워크가 테스트 코드에 미칠 수있는 영향은 없습니다.

나는 테스트가 매우 간단하다는 것을 좋아한다. 나는 IoC 컨테이너 또는 다른 DI 프레임 워크를 설정하여 테스트중인 클래스를 설정하도록하는 간접적 인 절차를 좋아하지 않습니다. 어색한 느낌입니다.

그리고 그것은 내 테스트를 프레임 워크의 글로벌 상태에 의존하게 만듭니다. 다시 말해서 클래스 X의 인스턴스를 다른 종속성으로 설정 해야하는 두 가지 테스트를 더 이상 병렬로 실행할 수 없다는 것입니다.

최소한 생성자 및 세터 주입에서는 테스트에서 프레임 워크 및 / 또는 리플렉션을 사용하지 않는 옵션이 있습니다.


예를 들어 Mockito mockito.org를 사용하는 경우 필드 주입을 사용하는 경우에도 DI / IoC 프레임 워크가 필요하지 않으며 병렬로 테스트를 실행할 수 있습니다.
Hans-Peter Störr

1
@hstoerr 니스. 그러나,이 ... Mockito가 반사를 사용 (또는 그 자바 상당이라고 무엇이든)을 의미한다
마르 얀 Venema의

5

글쎄, 이것은 논쟁의 여지가있는 것 같습니다. 해결해야 할 첫 번째 사항은 필드 주입이 Setter 주입과 다르다는 것 입니다. 나는 이미 Field와 Setter 주입이 같은 것이라고 생각하는 사람들과 함께 일하고 있습니다.

따라서 명확하게 :

현장 주입 :

@Autowired
private SomeService someService;

세터 주입 :

@Autowired
public void setSomeService(SomeService someService) {
    this.someService = someService;
}

그러나 저에게는 동일한 관심사를 공유합니다. 그리고 내가 가장 좋아하는 생성자 주입 :

@AutoWired
public MyService(SomeService someService) {
    this.someService = someService;
}

생성자 주입이 setter / field injection보다 낫다고 믿는 다음과 같은 이유가 있습니다 (저는 스프링 링크를 인용하여 요점을 보여줍니다).

  1. 순환 의존성 방지 : @Tomasz가 설명했듯이 Spring은 Bean 간의 순환 의존성을 감지 할 수 있습니다. 또한 참조 말 봄 팀 이 있습니다.
  2. 클래스 의 의존성 은 명백하다 : 클래스 의 의존성은 생성자 에서는 명백 하지만 필드 / 세터 주입으로 숨겨진 다 . 필드 / 세터 주입이있는 "블랙 박스"와 같은 수업을 느낍니다. 그리고 내 경험에 따르면 개발자가 많은 종속성을 가진 클래스에 대해 신경 쓰지 않는 경향이 있습니다. 생성자 주입을 사용하여 클래스를 조롱하려고 할 때 분명합니다 (다음 항목 참조). 개발자를 귀찮게하는 문제가 해결 될 가능성이 높습니다. 또한 종속성이 너무 많은 클래스는 SRP (Single Responsibility Principle)에 위배 될 수 있습니다.
  3. 모의하기 쉽고 신뢰성 : 필드 주입에 비해 , 그것은 당신이 클래스 내부에 선언 된 개인 콩에 도달하기 위해 어떤 상황에 맞는 모의 또는 반사 기술을 필요로하지 않기 때문에, 단위 테스트에서 모의 쉽게 당신은되지 않습니다 그것은 속지 . 클래스를 인스턴스화하고 조롱 된 콩을 전달하면됩니다. 이해하기 쉽고 쉽습니다.
  4. 코드 품질 도구를 사용하면 도움이 될 수 있습니다 . Sonar와 같은 도구는 매개 변수가 많은 클래스가 너무 복잡한 클래스이므로 리팩터링이 필요하기 때문에 클래스가 너무 복잡 해지는 것에 대해 경고 할 수 있습니다. 이 도구는 클래스가 필드 / 세터 주입을 사용할 때 동일한 문제를 식별 할 수 없습니다.
  5. 더 좋고 독립적 인 코드 : 코드@AutoWired 사이의 확산이 적어 코드가 프레임 워크에 덜 의존적입니다. 프로젝트에서 DI 프레임 워크를 간단하게 변경 할 수 있다는 가능성이 그것을 정당화하지는 않는다는 것을 알고 있습니다 (누가 그럴까요?). 그러나 이것은 나에게 더 나은 코드를 보여줍니다 ( EJB와 조회 를 통해 어려운 방법 으로 배웠습니다 ). 스프링을 @AutoWired사용하면 생성자 주입을 사용 하여 주석을 제거 할 수도 있습니다 .
  6. 더 많은 주석이 항상 정답 아니다 : 들어 어떤 이유로 , 난 정말 그들이 정말 유용한 경우에만 주석을 사용하려고합니다.
  7. 주사를 불변으로 만들 수 있습니다 . 불변성은 매우 환영받는 기능입니다.

더 많은 정보를 찾고 있다면 Spring Blog 의이 오래된 (그러나 여전히 관련이있는) 기사 가 왜 그렇게 많은 setter 주입을 사용하고 생성자 주입을 사용하도록 권장하는지 알려줍니다.

setter injection은 예상보다 훨씬 자주 사용됩니다. 일반적으로 Spring과 같은 프레임 워크는 생성자 주입보다 setter 주입으로 구성하는 것이 훨씬 적합하다는 사실입니다

그리고 왜 생성자가 응용 프로그램 코드에 더 적합하다고 믿는지에 대한 참고 사항 :

생성자 주입은 프레임 워크 코드보다 응용 프로그램 코드에 훨씬 유용합니다. 응용 프로그램 코드에서는 본질적으로 구성해야하는 선택적 값이 덜 필요합니다.

마지막 생각들

생성자의 이점에 대해 확실하지 않은 경우 Spring 팀에서 설명한대로 필드가 아닌 setter를 생성자 주입과 혼합하는 것이 좋습니다 .

생성자 기반 및 Setter 기반 DI를 모두 혼합 할 수 있으므로 필수 종속성에 대해 생성자 인수를 사용하고 선택적 종속성에 대해 설정자를 사용하는 것이 좋습니다.

그러나 필드 주입은 아마도 세 가지 중에서 피해야 할 것임을 기억하십시오 .


-5

수업이 진행되는 동안 종속성이 변경 될 가능성이 있습니까? 그렇다면 현장 / 재산 주입을 사용하십시오. 그렇지 않으면 생성자 주입을 사용하십시오.


2
이것은 실제로 아무것도 설명하지 않습니다. 대신 적절한 방법으로 개인 필드를 삽입해야하는 이유는 무엇입니까?
Robert Harvey

개인 필드에 대해서는 어디에 있습니까? 저는 수업의 공개 인터페이스에 대해 이야기하고 있습니다
Darren Young

질문에 메소드 주입과 생성자 주입에 대한 논쟁은 없습니다. JMockit의 저자는 둘 다 나쁜 것으로 본다. 질문의 제목을보십시오.
Robert Harvey

질문에 필드 주입 대 생성자 주입이 있습니까?
Darren Young

1
현장 주입은 완전히 다른 동물입니다. 개인 회원에게 가치를 파고 있습니다 .
Robert Harvey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.