[Spring] Spring에서 의존성 주입하기

Junbeom Wi·2021년 5월 9일
0

Java Spring

목록 보기
2/2

저번 포스팅에서 일반적인 상황의 의존성 주입에 대해 공부했다. 이번에는 Spring Framework에서 사용하는 의존성 주입에 대해 집중하여 다룰 예정이다.





IoC (Inversion of Control)


저번 포스팅에서 간단히 다뤘었지만, Spring Framework에서 의존성 주입을 공부하기 전에 다시 IoC에 대해 언급하려고 한다.

IoC의 개념

Inversion of Control(제어의 역전), IoC는 객체의 흐름, 생명주기(Life-Cycle) 등을 관리하는 것을 위임하는 방식의 프로그래밍 설계를 의미한다. 결국, 개발 주체가 되는 개발자가 직접 위와 같은 것을 관리하는 것이 아닌 제 3자 Spring같은 경우엔 Spring Container에게 이 권한을 위임하여 IoC를 이루게 된다. 이러한 기능을 하는 Container를 IoC Container라고 일컫는다.

IoC == DI?

Spring 에서 이 두 개념이 같게 사용이 되고 있고 실제로 같다고 생각하는 사람도 많다. 하지만 이는 사실이 아니고, 따지고 보면 DI가 IoC의 분류 중 하나이다.

  • DL(Dependency Lookup) : 저장소에 저장되어 있는 Bean에 접근하기위해 컨테이너가 제공하는 API를 이용하여 Bean을 검색(Lookup)하는 것.
  • DI(Dependency Injection) : 컨테이너가 주체가 되어 애플리케이션 코드에 의존관계를 주입해 주는 것.
    • 각 클래스간의 의존관계를 빈설정(Bean Definition)정보를 바탕으로 컨테이너가 자동으로 연결함.

DL를 사용하게 된다면 종속성이 많이 생기게 되므로 DI를 많이 사용한다. 그래서 많은 개발자들이 IoC와 DI를 겸해 Spring에서 그 의미를 사용하고 있는 것 같다.



Spring 에서 의존성 주입하기


Spring에서 의존성을 주입하는 방식은 대표적으로 3가지를 들 수 있다.

  • Field Injection
  • Setter Injection
  • Constructor Injection

Field Injection


@Service
public class HelloService {

    
   @Autowired  // field injection
   private HelloRepository helloRepository;
   
   ... 
   }

가장 쉬운 의존성 주입 방식이다. 여기서,

@Autowired 어노테이션은 말 그대로 스프링 IoC 컨테이너 안에 있는 객체인 bean들이 자동으로 연결(wire)이 되게 하는 Spring Framework 의 기능이다.


이러한 Field Injection은 짧은 줄만 기입하면 되기 때문에 편리함을 주지만, Mock 을 주입하기가 어려운 구조이기 때문에 UnitTest가 어려워 사실상 DI의 장점을 살리지 못한다.

그래서 Field Injection은 지양하는 의존성 주입 방식이다.



Setter Injection


@Service
public class HelloService {

    
    // setter injection
    private HelloRepository helloRepository;

    @Autowired
    public void setHelloRepository(HelloRepository helloRepository) {
        this.helloRepository = helloRepository;
    }
   ... 
   }

Setter를 이용해 의존성을 주입하는 방식이고, Field Injection이 가지지 못하는 다형성을 가지게 되어 Mock을 제작하여 UnitTest를 진행할 수 있는 의존성 주입방식이다.

그러나 Spring 3.x까지만 권장된 방식이고, 그 다음 버전에서는 권장하지 않는 방식이다. 여러 이유가 있을 수 있지만 개발 중간에 어떠한 공격자가 위 코드의 helloRepository를 교체할 수도 있고, Setter로 인한 의도치 않은 변경이 이루어질 수도 있기 때문에 보안상 보기 좋지가 않다. 그렇기 때문에 Spring 4.x 버전을 사용하고 있는 지금 권장하는 방식은 아니라 할 수 있다.



Constructor Injection


@Service
public class HelloService {

    
    // Constructor injection
    private HelloRepository helloRepository;

  
    @Autowired // Spring 4.3 이후 없어도 spring이 알아서 injection을 해줌.
    public HelloService(HelloRepository helloRepository) {
        this.helloRepository = helloRepository;
    }
  
   ... 
   }

생성자를 이용한 DI는 현재 존재하는 의존성 주입 방법 중 가장 권장되어 있는 방식이다. 생성자가 존재함으로써 Field Injection이 가지는 문제점과 Setter Injection이 가지는 보안적인 문제점을 모두 해결하고 있다.

또한 Spring 4.3 이후 가장 권장되는 방법으로, @Autowired 어노테이션도 생략이 가능하다. 생략을 하더라도, IoC 컨테이너가 알아서 bean을 연결해준다.

여기서 lombok을 사용하면 더 간단한 코드로 정리할 수 있는데, lombokAllArgsConstructorRequiredArgsConstructor 가 생성자를 이용한 DI 조차 해주기 때문에 어노테이션만 클래스에 달아주면 코드가 더 간단해진다.


@Service
@AllArgsConstructor
public class HelloService {

    
    // Constructor injection
    private HelloRepository helloRepository;

  /* -> 이 과정을 lombok의 AllArgsConstructor가 대체해줌.
    @Autowired // Spring 4.3 이후 없어도 spring이 알아서 injection을 해줌.
    public HelloService(HelloRepository helloRepository) {
        this.helloRepository = helloRepository;
    }
  */
   ... 
   }

이렇게 기존 Constructor Injection 보다 훨씬 간편한 코드로 작성할 수 있다. 그러나 웬만하면 (상황에 따라 다를 수 있지만) Repository 같은 저장소는 변하지 않아야 하는 성격을 가지기 때문에 final로 사용하기도 한다.

그런 경우에는 AllArgsConstructor 대신, 초기화 되지않은 final 필드나, @NonNull 이 붙은 필드에 대해 생성자를 생성해 주는 RequiredArgsConstructor를 사용하는 것이 더 권장된다.


@Service
@RequiredArgsConstructor
public class HelloService {

    
    // Constructor injection
    private final HelloRepository helloRepository;

   ... 
   }

RequiredArgsConstructor를 사용한 Constructor Injection은 이러한 모습으로 코드를 단순화할 수 있다.



맺음

향후 @Autowired 어노테이션에 대해 자세히 다뤄볼 포스팅을 할 기회가 오면 좋겠다.

profile
No Experience Backend Developer

0개의 댓글