Notice
Recent Posts
Recent Comments
Link
250x250
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

거인의 코딩일지

[Spring] 다양한 의존성 주입 방법 본문

코딩/JAVA

[Spring] 다양한 의존성 주입 방법

코딩거인 2023. 9. 27. 09:45
728x90
Spring 프레임 워크의 핵심 기술 중 하나는 DI(Dependency Injection, 의존성 주입)이다.
Spring 프레임워크와 같은 DI 프레임워크를 이용하면 다양한 의존성 주입을 하는 방법이 있다.
다양한 의존성 주입 방법
1. 생성자 주입 (Constructor Injection)
  • 생성자 주입은 생성자를 통해 의존 관계를 주입하는 방법이다.
@Service
public class UserService {

    private UserRepository userRepository;
    private MemberService memberService;
    
    @Autowired
    public UserService(UserRepository userRepository, MemberService memberService) {
        this.userRepository = userRepository;
        this.memberService = memberService;
    }
}
  • 생성자 주입은 생성자의 호출 시점에 1회 호출되는 것이 보장
  • 주입받은 객체가 변하지 않거나 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용할 수 있다.
  • 생성자가 1개만 있을 경우에 @Autowired 를 생략해도 주입이 가능하도록 편의성을 제공한다.
  • 따라서 아래 코드는 위에 코드와 동일한 코드가 된다.
@Service
public class UserService {

    private UserRepository userRepository;
    private MemberService memberService;
    
    public UserService(UserRepository userRepository, MemberService memberService) {
        this.userRepository = userRepository;
        this.memberService = memberService;
    }
}

 

2. 수정자 주입 (Setter 주입, Setter Injection)
  • 수정자 주입은 필드값을 변경하는 Setter를 통해서 의존 관계를 주입하는 방법이다.
  • Setter 주입은 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용 (실제로 변경이 필요한 경우는 드물다.)
  • @Autowired 로 주입할 대상이 없는 경우에는 오류가 발생한다.
  • 아래 코드에서는 XXX 빈이 존재하지 않을 경우에 오류가 발생한다. 주입할 대상이 없어도 동작하도록 하려면 @Autowired(required = false)를 통해 설정 가능하다
  • 스프링 초기에는 수정자 주입이 자주 사용되었는데, 그 이유는 바로 getX, setX 등 프로퍼티를 기반으로 하는 자바 기본 스펙 때문이였다. 하지만 시간이 지나면서 점차 수정자 주입이 아닌 다른 방식이 주목받게 되었다.
@Service
public class UserService {

    private UserRepository userRepository;
    private MemberService memberService;

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

    @Autowired
    public void setMemberService(MemberService memberService) {
        this.memberService = memberService;
    }
}

 

3. 필드주입 (Field Injection)
  • 필드 주입은 필드에 바로 의존관계를 주입하는 방법이다.
  • IntelliJ에서 필드 인젝션을 사용하면 Field injection is not recommended이라는 경고 문구가 발생한다.
  • 필드 주입을 이용하면 코드가 간결해져서 과거에 상당히 많이 이용되었던 주입 방법
  • 필드 주입은 외부에서 접근이 불가능하다는 단점이 존재하는데, 테스트 코드의 중요성이 부각됨에 따라 필드의 객체를 수정할 수 없는 필드 주입은 거의 사용되지 않게 되었다.
  • 필드 주입은 반드시 DI 프레임워크가 존재해야 하므로 반드시 사용을 지양해야 한다. 그렇기에 애플리케이션의 실제 코드와 무관한 테스트 코드나 설정을 위해 불가피한 경우에만 이용
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MemberService memberService;

}

 

4. 일반 메소드 주입 (Method Injection)
  • 일반 메소드를 통해 의존 관계를 주입하는 방법이다.
  • 수정자 주입과 동일하며 마찬가지로 거의 사용할 필요가 없는 주입 방법이다.
  • 수정자 주입을 사용하면 한 번에 여러 필드를 주입 받을 수 있도록 메소드를 작성할수도 있다.

생성자 주입을 사용해야 하는 이유
1. 객체의 불변성 확보
  • 실제로 개발을 하다 보면 의존 관계의 변경이 필요한 상황은 거의 없다. 하지만 수정자 주입이나 일반 메소드 주입을 이용하면 불필요하게 수정의 가능성을 열어 두기에 유지 보수성을 떨어뜨린다.
  • 따라서 생성자 주입을 통해 변경 가능성을 배제하고 불변성을 보장하는 것이 좋다
2. 테스트 코드의 작성
  • 테스트가 특정 프레임워크에 의존하는 것은 침투적이므로 좋지 못하다.
  • 그러므로 가능한 순수 자바로 테스트를 작성하는 것이 가장 좋은데, 생성자 주입이 아닌 다른 주입으로 작성된 코드는 순수한 자바 코드로 단위 테스트를 작성하는 것이 어렵다.
  • 생성자 주입을 사용하면 컴파일 시점에 객체를 주입받아 테스트 코드를 작성할 수 있으며, 주입하는 객체가 누락된 경우 컴파일 시점에 오류를 발견할 수 있다.
  • 또한 우리가 테스트를 위해 만든 Test객체를 생성자로 넣어 편리함을 얻을 수도 있다.
3. final 키워드 작성 및 Lombok과의 결합
  • 생성자 주입을 사용하면 필드 객체에 final 키워드를 사용할 수 있으며, 컴파일 시점에 누락된 의존성을 확인할 수 있다. 반면에 다른 주입 방법들은 객체의 생성(생성자 호출) 이후에 호출되므로 final 키워드를 사용할 수 없다.
  • 또한 final 키워드를 붙이면 Lombok과 결합되어 코드를 간결하게 작성할 수 있다. Spring과 같은 DI 프레임워크는 Lombok과 환상적인 궁합을 보여주는데, 위에서 작성했던 생성자 주입 코드를 Lombok과 결합시키면 다음과 같이 간편하게 작성할 수 있다.
4. 스프링에 비침투적인 코드 작성
  • 필드 주입을 사용하려면 @Autowired를 이용해야 하는데, 이것은 스프링이 제공하는 어노테이션이다. 그러므로 @Autowired를 사용하면 다음과 같이 UserService에 스프링 의존성이 침투하게 된다. 
import org.springframework.beans.factory.annotation.Autowired;
// 스프링 의존성이 UserService에 import되어 코드로 박혀버림

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MemberService memberService;

}
  • 우리가 사용하는 프레임워크는 언제 바뀔지도 모를 뿐만 아니라, 사용자와 관련된 책임을 지는 UserService에 스프링 코드가 박혀버리는 것은 바람직하지 않다.
  • 프레임워크는 비즈니스 로직을 작성하는 서비스 계층에서 알아야 할 대상이 아니다. 물론 이는 필요한 자바 파일을 임포트해야 하는 정적 언어인 자바의 한계이기도 하다. 그래도 가능하다면 스프링이 없이 코드가 작성되면 더욱 유연한 코드를 확보하게 된다.
  • 프레임워크가 자주 바뀌는 것도 아니므로 비록 스프링 코드가 침투하는게 치명적인 문제는 아니긴하다. 하지만 그래도 더 좋은 방법(생성자 주입)이 있는데, 굳이 사용할 필요는 없다.
5. 순환 참조 에러 방지
  • 생성자 주입을 사용하면 애플리케이션 구동 시점(객체의 생성 시점)에 순환 참조 에러를 예방할 수 있다.
  • 예를 들어 다음과 같이 필드을 사용해 서로 호출하는 코드가 있다고 하자.
@Service
public class UserService {

    @Autowired
    private MemberService memberService;
    
    @Override
    public void register(String name) {
        memberService.add(name);
    }

}

 

  • UserSerivce가 이미 MemberService에 의존하고 있는데, MemberService 역시 UserService에 의존하는 것이다.
@Service
public class MemberService {

    @Autowired
    private UserService userService;

    public void add(String name){
        userService.register(name);
    }

}
  • 위의 두 메소드는 서로를 계속 호출할 것이고,
    메모리에 함수의 CallStack이 계속 쌓여 StackOverflow 에러가 발생하게 된다.

  • 만약 이러한 문제를 발견하지 못하고 서버가 운영된다면 어떻게 되겠는가?
    해당 메소드의 호출 시에 StackOverflow 에러에 의해 서버가 죽게 될 것이다. 하지만 생성자 주입을 이용하면 이러한 순환 참조 문제를 방지할 수 있다.
Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  memberService defined in file [C:\Users\Mang\IdeaProjects\build\classes\java\main\com\mang\example\user\MemberService.class]
↑     ↓
|  userService defined in file [C:\Users\Mang\IdeaProjects\build\classes\java\main\com\mang\example\user\UserService.class]
└─────┘
  • 애플리케이션 구동 시점(객체의 생성 시점)에 에러가 발생하기 때문이다.
  • 그 이유는 Bean에 등록하기 위해 객체를 생성하는 과정에서 다음과 같이 순환 참조가 발생하기 때문이다.

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

 

[ 생성자 주입 요약 맟 정리 ]

  1. 객체의 불변성을 확보할 수 있다.
  2. 테스트 코드의 작성이 용이해진다.
  3. final 키워드를 사용할 수 있고, Lombok과의 결합을 통해 코드를 간결하게 작성할 수 있다.
  4. 스프링에 침투적이지 않은 코드를 작성할 수 있다.
  5. 순환 참조 에러를 애플리케이션 구동(객체의 생성) 시점에 파악하여 방지할 수 있다.

 

 

참고문헌

 

[Spring] 다양한 의존성 주입 방법과 생성자 주입을 사용해야 하는 이유 - (2/2)

Spring 프레임워크의 핵심 기술 중 하나가 바로 DI(Dependency Injection, 의존성 주입)이다. Spring 프레임워크와 같은 DI 프레임워크를 이용하면 다양한 의존성 주입을 이용하는 방법이 있는데, 각각의 방

mangkyu.tistory.com

 

728x90