욱'S 노트

Spring - Setter injection vs constructor injection and the use of @Required 본문

Programming/Spring

Spring - Setter injection vs constructor injection and the use of @Required

devsun 2016. 4. 5. 10:00

몇개월전 springframework.org에서는 어떠한 기능들을 사용하고 어떻게 해당 기능들을 사용하는지 설문조사를 하고 피드백을 받았다. 첫번째 질문은 required 디펜던시를 체크하는 것이고 어떠한 메커니즘을 활용하는 것이었다. 그런 다음 트랜잭션 관리 전략에 대해서도 물었다.


첫번째 설문에 대해선 @Required 어노테이션을 사용한다는 답변이 가장 많았고 두번째 설문에서는 @Transactional 어노테이션을 사용한다는 답변이 가장 많았다. 아래는 첫번째 설문에 대한 응답 결과이다. 두번째 설문의 경우 약 30% 응답자가 Transactional 어노테이션을 사용한다고 하였다. 


Required 디펜던시를 체크하는 방법


8% 비즈니스 로직에서 체크한다.

9%   init-method를 사용하고 assert 메커니즘을 활용한다.

9% XML dependency-check 어트리뷰트를 사용한다.

13% 생성자 인젝션을 사용한다.

15% InitializingBean을 사용하고 assert 메커니즘을 활용한다.

17% Required 어노테이션을 사용한다.

29% 아무런 체크를 하지 않는다.


가장 흥미로운 결과는 29 퍼센트의 사람들이 required 디펜던시 체크를 하지 않는다는 것이다. 이 흥미로운 결과는 포럼에 다양한 스레드가 생성되고 어떻게 해결할 것인가에 대해 토론이 벌어졌는데 몇가지 리뷰를 해보자.


Constructor injection


생성자 인젝션에 대해 리뷰를 해보자. 어떠한 오브젝트들은 인자를 가진 생성자를 가진다. 이럴 경우 명백하게 인자를 전달하지 않으면 객체를 생성할 수 없다. 자바에서는 개발자가 생성자를 추가하지 않으면 인자가 없는 디폴트 생성자가 암묵적으로 추가된다. 이것은 스프링에서도 지원한다.


다시 말하면 우리는 우리의 클래스의 클라이언트에게 인자를 전달하도록 강제할 수 있다는 것이다.


public class Service {

  public Collaborator collaborator;


  // constructor with arguments, you *have* to

  // satisfy the argument to instantiate this class

  public Service(Collaborator collaborator) {

    this.collaborator = collaborator;

  }

}


우리는 이러한 이점을 required 디펜던시 체크에 활용할 수 있다. 만약 위의 코드를 assertion을 추가해서 변경한다면  우리는 100% collaborator 없이 생성되는 것을 방지할 수 있다.

public Service(Collaborator collaborator) {

  if (collaborator == null) {

    throw new IllegalArgumentException("Collaborator cannot be null");

  }

  this.collaborator = collaborator;

}



다시 말해서 생성자 인젝션과 assertion을 활용하면 디펜던시 체크 메커니즘이 추가로 필요하지 않다.


Why do people not use constructor injection mostly


이제 왜 소수의 사람들만이 required 디펜던시를 강제하기 위해 constructor 인젝션을 사용할까? 여기는 두가지 이유가 있는데 하나는 역사적인 이유와 다른 하나는 스프링 자체 본질에 관한 것이다.


Historical reasons


2003년 초 스프링이 처음 발표되었을때, 주로 setter 인젝션에 초점을 맞추었다. 다른 프레임워크 PicoContainer는 생성자 인젝션에 강하게 초점을 맞췄다. 스프링은 그때 디폴트 인자와 인자명을 생성자의 인자로 전달하는 것이 명백하지 않다고 생각했다. 하지만 우리는 생성자 인젝션을 구현했고 개발자가 원하는 초기화 및 객체를 관리하는 방법을 제공했다.


이것이 바로 스프링 프레임워크 자신이 많은 setter 인젝션을 사용하는 것처럼 보이는 것이다. 사실 setter 인젝션은 스프링 자체에서 많이 쓰이기도 했지만, 많은 서드-파티 소프트웨어들도 setter 인젝션을 지원했다.


Frameworks need to be a lot more configurable


두번째 이유는 스프링 프레임워크와 같이 일반적인 프레임워크들은 생성자 인젝션보다 setter 인젝션이 설정하기에 더 적당하다. 대부분은 프레임워크는 설정할 수 있는 대부분은 옵션 값들이 많이 존재하기 때문이다. 만약 옵션값들을 생성자 인젝션으로 설정할 수 있게 지정한다면 필요없는 생성자들을 증식시켜 클래스를 너무 복잡하게 만들 것이다.


두가지 이유에서 나는 생성자 인젝션이 프레임워크 코드보단 어플리케이션 코드에서 훨씽 유용하다고 생각한다. 어플리케이션 코드에서는 우리는 옵션 값을 가질 필요가 많지 않다. 두번째는 어플리케이션 코드는 프레임워크 코드보다 상속을 사용하는 빈도가 훨씬 적다.


So what should you use?


우리는 항상 모든 필수 collaborator를 위해 생성자 인젝션을 사용하고 다른 프로퍼티들을 위해 setter 인젝션을 사용하라고 권고한다.. 또한 생성자 인젝션을 반드시 모든 mandantory 프로퍼티를 만족시킬수 있도록 사용하라고 권고한다. 그래야 불완전한 상태에서 객체가 생성되는 것을 방지할 수 있기 때문이다. 다시 말해서 생성자 인젝션을 사용하면 추가적은 검사 메커니즘을 명시하지 않아도 된다.


Using the alternative mechanisms


2006년부터 스프링은 @Required 어노테이션을 소개했다. @Required 어노테이션은 required 디펜던시를 체크한다. 어노테이션은 생성자 또는 setter에 위치할 수 있다.


public class Service {

  private Collaborator collaborator;


  @Required

  public void setCollaborator(Collaborator c) {

    this.collaborator = c;

  }

}


<bean class="org.sfw.beans.factory.annotation.RequiredAnnotationBeanFactoryPostProcessor"/>


Other mechanisms to check required dependencies


몇가지 requred 디펜던시를 체크를 강제하는 다른 방법이 존재한다. 대부분은 스프링은 콜백 기능을 활용하는데 InitailizingBean 또는 init-method를 이용하는 방법이다. 


public class Service implements InitializingBean {


  private Collaborator collaborator;


  public void setCollaborator(Collaborator c) {

    this.collaborator = c;

  }


  // from the InitializingBean interface

  public void afterPropertiesSet() {

    if (collaborator == null) {

      throw new IllegalStateException("Collaborator must be set in order for service to work");

    }

  }

}


So why NOT check required dependencies


많은 사람들이 디펜던시가 정확히 세팅되었는지 체크하지 않고 있다. 가장 큰 이유는 ApplicationContext를 구동하는 것만으로도 어떻게 클래스들이 디펜던시를 가지고 있는지 충분히 알 수 있기 때문이다. 만약 스프링의 인티그레이션 테스트 예제를 보면 당신을 위한 어플리케이션 컨텍스트를 로딩할 수 있다. 만약 디펜던시가 제대로 설정되지 않았다면 조금 있다가 바로 버그를 인지할 수 있을 것이다. 만약 테스트 케이스를 확신하고 충분히 테스트 커버리지를 확보했다면 디펜던시에 대한 테스트가 불필요할 수도 있다. 물론 이러한 방식은 런타임시에 잘못된 디펜던시를 발견할 수도 있기 때문에 나는 선호하지 않는다.


Conclusion


생성자 인젝션과 setter 인젝션에 대해 얘기를 나눠봤고 많은 사람들이 setter 인젝션을 더 선호한다는 것을 알게 되었다. 나는 생성자 인젝션과 디펜던시 체크 콤비네이션이 더 좋은 방법이라고 생각한다. 이러한 방식이 멀티 스레드 환경에 안정성을 증대시킨다. 


그러나 많은 콜라보레이터 클래스들이 존재한다면 좋은 방법이 아닐 수도 있다. 하지만 20개의 클래스가 협력을 한다고 생각해보자. 그렇다면 해당 클래스가 너무 많은 책임을 가지고 있는것이 아닐까라고 의심해본다.

'Programming > Spring' 카테고리의 다른 글

Spring - Java-based container configuration  (0) 2015.05.19
Comments