스프링 생성자 주입 권장 이유

merci·2023년 8월 16일
0

Spring

목록 보기
21/21

스프링에서 의존성을 주입하는 몇가지 방법이 있습니다.

생성자 주입 (Constructor Injection)

생성자 주입은 의존성이 필수적이거나 변경될 수 없을 때 권장됩니다.

@Autowired는 생략할 수 있습니다.
스프링 4.3부터는 단일 생성자를 가진 클래스에는 자동으로 @Autowired가 적용됩니다.

private final SomeBean someBean;

@Autowired
public YourClass(SomeBean someBean) {
    this.someBean = someBean;
}

@RequiredArgsConstructor를 사용하면
필드 레벨에서 final로 선언된 의존성들을 모두 포함하는 생성자를 자동으로 생성해줍니다.

@RequiredArgsConstructor
public class YourClass {
    private final SomeBean someBean;
    // ...
}


Setter 주입 (Setter Injection)

Setter 주입은 선택적으로 의존성을 주입받거나 의존성을 변경할 수 있는 경우에 사용됩니다.

private SomeBean someBean;

@Autowired
public void setSomeBean(SomeBean someBean) {
    this.someBean = someBean;
}


일반적으로는 다음과 같은 장점으로 생성자 주입방식이 권장됩니다.

  • 불변성 (Immutability)
    생성자 주입을 사용하면 주입받는 필드를 final로 선언할 수 있습니다.
    이는 한 번 초기화된 후에 변경할 수 없게 되므로, 객체의 불변성을 보장합니다.

  • 순환 참조
    생성자 주입을 사용하면 런타임에 순환 참조 문제를 감지할 수 있습니다.
    필드 주입이나 Setter 주입을 사용하면 이를 감지하기 어렵습니다.

  • 테스트 용이성
    생성자 주입을 사용하면, 테스트 시에 의존성을 쉽게 목(mock) 객체 등으로 교체할 수 있습니다.


@RequiredArgsConstructor(생성자 주입)

@RequiredArgsConstructorfinal 필드나 @NonNull 어노테이션이 붙은 필드를 포함하는 생성자를 자동으로 생성해 줍니다.

  • 장점

    • 코드 간결성
      생성자 코드를 직접 작성하지 않아도 되므로, 코드가 간결해집니다.
  • 단점

    • 가독성 문제
      @RequiredArgsConstructor의 동작을 모르면 생성자가 어떻게 동작하는지 파악하기 어렵습니다.

    • 불필요한 생성자 생성 위험
      실수로 필요 없는 필드에 final을 붙이게 되면 의도하지 않은 생성자가 생성될 수 있습니다.


@Autowired(필드 주입)

@Autowired를 생성자에 붙이지 않고 필드에 붙이게 되면 필드 주입이 됩니다.

@Service
public class MyService {

    @Autowired
    private SomeService someService;
}
  • 장점

    • 편하다
      의존성을 주입하고 싶은 필드에 @Autowired만 추가하면 쉽게 의존성을 추가할 수 있습니다.
  • 단점

    • 명시성 부족
      생성자 코드가 명시적으로 작성되지 않아서, 코드를 처음 보는 개발자에게는 클래스의 의존성이 명확히 드러나지 않을 수 있습니다.

    • 테스트 용이성
      필드 주입을 사용하면, 테스트에서 의존성을 교체하기가 더 어렵습니다.


@Autowired 를 지양하는 이유

@Autowired 어노테이션을 사용하여 필드 주입을 할 경우, 해당 필드는 final로 선언될 수 없습니다.
따라서 그 필드의 불변성을 보장할 수 없게 됩니다.

불변성이 중요한 이유

  • 스레드 안전 (Thread Safety)
    멀티 스레드 환경에서는 여러 스레드가 동시에 같은 객체에 접근할 수 있습니다.
    객체의 상태가 변경 가능하면, 예기치 않은 동시 수정으로 인한 버그가 발생할 수 있습니다.
    불변 객체는 이런 문제에서 안전합니다.

  • 신뢰성
    객체의 상태가 변경되지 않는다면, 해당 객체를 사용하는 코드가 예측 가능하고 안정적입니다.

  • 부작용 방지
    다른 코드에서 객체의 상태를 변경할 수 있다면, 그 객체를 사용하는 모든 곳에서 예기치 않은 부작용이 발생할 수 있습니다.
    불변성은 이러한 부작용을 방지합니다.

불변성이 없다면 다음과 같은 상황이 발생합니다.

@Service
public class MyService {

    @Autowired
    private SomeService someService;
}

여기서 someService 필드는final이 아니기 때문에 런타임 중에 다른 객체로 변경될 수 있습니다.
예를들어 아래의 메소드로 필드를 변경할 수 있습니다.

public void replaceService(MyService myService, SomeService newService) {
    myService.someService = newService;
}

이런 식으로 직접 필드를 변경하는 것은 일반적으로 좋지 않습니다.
이는 객체의 상태와 행동이 예기치 않게 변경될 수 있다는 위험성을 내포하게 됩니다.

따라서 생성자 주입(또는 @RequiredArgsConstructor)을 사용하면 필드를 final로 선언할 수 있어서 해당 필드에 다른 객체를 할당하는 것이 불가능하게 됩니다.
이는 클래스의 불변성을 보장하고, 객체의 상태가 예기치 않게 변경되는 것을 방지합니다.


생성자 주입을 이용하면 테스트시에도 장점이 있습니다.

생성자 주입을 사용하면 테스트에서 원하는 Mock 객체나 Stub 객체를 직접 생성자에 전달할 수 있습니다.

@Service
public class MyService {
    private final SomeService someService;

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

테스트에서는 다음과 같이 Mock 객체를 주입할 수 있습니다:

SomeService mockService = mock(SomeService.class);
MyService myService = new MyService(mockService);

또한 스프링 테스트에서는 @MockBean 어노테이션을 사용하여 해당 빈의 인스턴스를 Mock 객체로 자동 교체할 수 있습니다.
이 방법은 Spring Boot Test에서 제공됩니다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTest {

    @MockBean
    private SomeService mockService;

    @Autowired
    private MyService myService;

}


반면에 @Autowired로 필드 주입을 사용할 경우,
해당 필드는 주로 private으로 선언되기 때문에 외부에서 직접 접근하거나 변경하는 것이 어렵습니다.
테스트에서 해당 필드에 직접 Mock 객체를 주입하려면 리플렉션과 같은 추가적인 방법을 사용해야 합니다.

profile
작은것부터

0개의 댓글