모래블로그

[스프링] Spring 의존성 주입 방법 및 생성자 주입 방법의 장점 본문

Framework/Spring

[스프링] Spring 의존성 주입 방법 및 생성자 주입 방법의 장점

별모래 2023. 11. 22. 17:46
728x90

@Autowired와 private final 의 차이점에 대해 알아보다가 의존성 주입 방법과 생성자 주입 방법의 장점에 대해 정리하였다.

의존성 주입 방법

1. 생성자 주입(Constructor Injection)

생성자를 통해 의존 관계를 주입하는 방법

생성자 주입은 생성자의 호출 시점에 1회 호출되는 것이 보장된다. 그렇기 때문에 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용할 수 있다. 클래스의 생성자가 1개일 경우, 그 생성자로 주입받을 객체가 빈으로 등록되어 있다면 @Autowired를 생략할 수 있다.

@Service
pulbic class UserService {
	// final 붙일 수 있음
	private final UserRepository userRepository;

	// @Autowired (생략 가능)
	public UserService(UserRepository userRepository) {
		this.userRepository= userRepository;
		}
}

2. 필드 주입(Field 

필드에 의존관계를 주입하는 방법

Intellij에서 필드 인젝션을 사용하면 Field injection is not recommended 라는 경고 문구 발생

코드가 간결해지지만 외부에서 접근이 불가능하다는 단점이 있다. 테스트 코드의 중요성이 부각됨에 따라 필드의 객체를 수정할 수 없는 필드 주입은 거의 사용하지 않게 되었다. 또한 필드 주입은 반드시 DI 프레임워크가 존재해야하므로 사용을 지양하는 것이 좋다.

@Service
public class UserService {
	@Autowired
	private UserRepository userRepository;
}

3. 수정자 주입(Setter Injection)

필드 값을 변경하는 setter를 통해 의존 관계를 주입하는 방법

생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다.

private class UserService {
	private UserRepository userRepository;
	@Autowired
	public void setUserRepository(UserRepository userRepository) {
		this.userRepository = userRepository;
	}
}

 

참고로 @Autowired와 setter는 필드를 final로 선언할 수 없다.

가장 권장하는 방식은 생성자 주입 방식

@RequiredArgsConstructor final이 선언된 모든 필드를 인자값으로 하는 생성자를 생성해준다.

생성자를 직접 안쓰고 @RequiredArgsConstructor를 사용하는 이유는 해당 클래스의 의존성 관계가 변경될 때마다 생성자 코드를 계속해서 수정하는 번거로움을 해결하기 위함이다.

 

생성자 주입 방식의 장점

1) 순환 참조 방지

순환 참조란, A 클래스가 B 클래스의 Bean 을 주입받고, B 클래스가 A 클래스의 Bean 을 주입받는 상황처럼 서로 순환되어 참조할 경우 발생하는 문제를 의미한다.

 

생성자 주입 방식의 동작 원리

  • A클래스가 B클래스 의존, B클래스가 C클래스 의존하는 경우,
    A클래스에 대한 Bean을 만들기 위해 B클래스의 Bean을 주입해야하는데 아직 없으니까 B클래스의 Bean을 만든다. 그런데 이 과정에서 C클래스의 Bean을 주입해야하는데 이마저도 없으니까 C클래스의 Bean을 가장 먼저 만든다.
    결과적으로 C→ B → A 순서로 Bean을 생성한다.
  • A ↔ B의 경우
    A클래스의 Bean을 만드는 과정에서 B클래스의 Bean이 필요한데 없으니까 B클래스의 Bean을 먼저 생성한다. 근데 B클래스는 A클래스를 의존하고 있으므로 A클래스의 Bean을 먼저 만든다.
    위 상황이 반복되면서 무한 반복에 빠져버리게 되는데, 이렇게 순환되는 과정에서 결과적으로 어떠한 Bean도 생성하지 못하는 문제를 순환참조 문제라고 한다.
@Service
public class AService {
	// 순환 참조
	@Autowired
	private BService bService;
	
	public void helloA() {
		bService.helloB();
	}
}
public class BService {
	// 순환 참조
	@Autowired
	private AService aService;
	
	public void helloB() {
		aService.helloA();
	}
}

 

위의 두 메소드는 서로를 계속 호출하여, StackOverflow 에러가 발생하게 된다.
그러나 생성자 주입을 이용하면 어플리케이션 구동 시점(객체의 생성 시점)에 에러가 발생하게 된다.
⇒ 서버 자체가 구동되지 않으므로 바로 순환참조를 알 수 있고 방지할 수 있다.

Autowired를 이용한 필드 주입에서 어프리케이션 구동 시점에 에러가 발생하지 않는 이유는 빈의 생성과 조립(@Autowired) 시점이 분리되어 있기 때문이다. 생성자 주입은 객체의 생성과 조립(의존관계 주입)이 동시에 실행되기 때문에 에러를 사전에 잡을 수 있다. 하지만, Autowired는 모든 객체의 생성이 완료된 후에 조립(의존관계 주입)이 처리된다. 그래서 호출이 된 후에 순환 이슈를 확인할 수 있는 것이다.

 

스프링 2.6부터는 순환 참조가 기본적으로 허용되지 않도록 변경되어, 필드 주입을 받아도 순환 참조가 발생한다면 애플리케이션 로딩 시점에 에러가 발생되므로 스프링부트 2.6 이하 버전을 사용하는 경우 발생한다.

  • 생성자 주입 방식은 필드/수정자 주입 방식과 빈을 주입하는 순서가 다르다.
    • 필드주입/수정자 주입 ⇒ 먼저 빈을 생성 후 주입하려는 빈을 찾아 주입
    • 생성자 주입 ⇒ 먼저 빈을 생성하지 않고 주입하려는 빈을 먼저 찾음(먼저 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 만든다. 그 후에 찾은 인자 빈으로 주입하려는 빈의 생성자를 호출한다)

2) 테스트 용이

테스트하고자 하는 클래스에 필드 주입이나 수정자 주입으로 빈이 주입되어 있으면, Mockito를 통해 목킹한 후 테스트를 진행하여야 한다.
하지만, 생성자 주입의 경우 단순히 원하는 객체를 생성한 후 생성자에 넣어주면 된다.
생성자 주입을 사용할 경우, 주입의 대상이 되는 객체를 테스트 코드 안에서 직접 new 연산을 통해 주입될 객체를 생성할 수 있다는 장점이 있다.

 

필드주입을 사용할 경우

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public void signUp(String name) {
        userRepository.add(name);
    }
}
public class UserTest {

    @Test
    public void test() {
        UserService userService = new UserService();
        userService.signUp("morae");
    }
}

 

위의 테스트 코드는 Spring 위에서 작동하지 않으므로 의존관계 주입이 되지 않아, userRepository가 null이 되어 호출 시 NullPointerException이 발생한다. ⇒ Mockito를 이용해 목킹해야함

생성자 주입을 사용하면 Mockito를 이용하지 않고도 순수 자바 코드 만으로 정상적으로 테스트를 수행할 수 있다.

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
 
    public void register(String name) {
        userRepository.add(name);
    }

}

3) final 선언 가능 (불변성 보장)

런타임에 객체 불변성을 보장한다
다른 주입 방법들은 객체의 생성(생성자 호출) 이후에 호출되므로 final 키워드를 사용할 수 없다.
수정자 주입이나 일반 메소드 주입을 이용하면, 불필요하게 수정의 가능성을 열어두어 유지보수성을 떨어뜨린다. 그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋다.

4) 스프링에 비침투적인 코드 작성

필드 주입을 사용하려면 @Autowired를 이용해야 하는데, 이것은 스프링이 제공하는 어노테이션이다. 그러므로 @Autowired 를 사용하면 UserService에 스프링 의존성이 침투하게 된다.

 @Service
public class UserService {
	@Autowired
	private UserRepository userRepository;
	}
}

 

REFERENCE🙇‍♀️

https://dev-coco.tistory.com/70
https://mangkyu.tistory.com/125
https://ch4njun.tistory.com/269
https://jackjeong.tistory.com/41

 

728x90

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

[스프링] Spring WebClient  (0) 2023.11.22
[스프링] Spring json 파싱하기  (1) 2023.11.22
[스프링] Spring UriComponents  (0) 2023.11.22
[스프링] Spring RestTemplate  (0) 2023.11.22
REST API란  (0) 2023.11.22