주입 실패

의존성 주입(DI)이란?

 

스프링에서의 의존성 주입이란, 객체 간에 의존성(객체 간의 연결관계)을 객체 내부에서 직접 호출하는 대신,

외부(스프링 컨테이너를 이용함)에서 객체를 생성해서 넣어주는 방식이다.

 

이전에 있던 객체 간의 의존성 설정은
A 클래스 내에서 B 객체를 new 키워드를 사용하여 생성하거나, 혹은 B 클래스에서 싱글톤 패턴을 이용해 자신의 객체를 생성해둔 것을 A 클래스 안에서 getInstance() 등의 메서드 등을 통해 생성하였다.

 

class A {
	BService b;
    
    public A() {
    	this.b = BService.getInstance();
    }
}

 

위와 같은 코드가 존재할 때, 만약 A클래스에서 멤버 변수로 갖는 BService 객체를 BService2 클래스의 인스턴스로 바꾸려면,

 

class A {
	BService2 b;
    
    public A() {
    	this.b = BService2.getInstance();
    }
}

 

이렇게 코드 자체를 수정해야 한다. 이러한 구조는 객체 간의 높은 결합도를 갖게 되며 이후 변동사항이 생기면 많은 부분들을 수정해야 하기 때문에, 프로젝트의 규모가 비대해질수록 수정하기가 어려워진다.

 

그래서 Spring에서는 객체 간의 관계 설정을 클래스가 직접 하는 방식 대신, Spring Container를 이용하여 외부에서 객체를 생성하고, 객체를 주입하는 방식, 즉 의존성 주입(DI) 방식을 사용하고 있다.

 

위와 같은 방식은, 객체끼리 서로 생성에 관여하지 않고 Spring Container가 대신 함으로써 객체 간의 결합도를 최대한 

낮추는 게 스프링 IoC(Inversion of Control)제어의 역전 스프링의 핵심 개념이다.

 

의존성 주입(DI)은 어떻게 하는 걸까?

 

의존성 주입 어노테이션은 @Autowired, @Resource, @Inject 가 있다.
하지만 @Resource, @Inject는 프레임 워크에 종속적이지 않으며 , Java에서 지원하는 어노테이션이기 때문에

해당 글에선 Spring에서 지원하는 @Autowired 만 설명하겠습니다.

@Autowired는 주입하려고 하는 객체의 타입으로 주입한다, 만약의 타입이 존재하지 않다면,

@Autowired에 위치한 속성명이 일치하는 bean을 컨테이너에서 찾고,

@Qualifire 어노테이션의 유무를 찾아 해당 어노테이션이 붙은 속성에 의존성을 주입합니다.

이 과정을 걸치고 찾지 못한다면 실패합니다.

 

@Autowired를 사용하는 3가지 방식은 아래와 같습니다.

 

의존성 주입 3가지 방식은 무엇인가?

 

1. Field Injection (필드 주입)

 

//1. 필드주입
    @Autowired
    private BService Bservice;

 

2. Setter based Injection (수정자 주입, 세터 주입)

 

//2. 세터주입
    private BService bService;

    @Autowired
    public void setBService(BService bService) {
        this.bService = bService;
    }

 

3. Constructor based Injection (생성자 주입)

 

@Controller
public class BController {

	//3. 생성자주입
      private final BService bService;

      @Autowired
      public BController(BService bService) {
          this.bService = bService;
      }
}

 

무엇이 다른가?

 

3가지 방식에는 빈을 주입하는 순서가 다르다.

 

Field Injection

1. 주입받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩토리에 등록

2. 생성자 인자에 사용하는 빈을 찾거나 생성

3. 필드에 주입

 

Setter based Injection은 

1. 주입받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩토리에 등록

2. 생성자 인자에 사용하는 빈을 찾거나 생성

3. 주입하려는 빈 객체의 수정자를 호출하여 주입

 

위에 2가지 방식은 런타임에서 의존성을 주입하기 때문에 의존성을 주입하지 않아도 객체가 생성될 수 있다.

 

Constructor based Injection
1. 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 생성

2. 찾은 인자 빈으로 주입하려는 생성자를 호출

 

무엇을 사용하는 것이 좋을까?

 

스프링에서는 현재 생성자 주입 방식을 권고하고 있다. (인텔리제이에서 필드 주입 사용 시 경고창이 나옴)

아래와 같다.

 

1. 필드에 final 키워드 사용이 가능하다.

Field Injection시 final 키워드를 사용할 수 없지만,

Constructor based Injection 은 사용할 때 final 키워드를 사용할 수 있다. 

그러므로 불변(immutable)하게 사용할 수 있다.

 

2. 순환 참조를 방지할 수 있다.

어떤 클래스 A가 B를 참조하고, B가 A를 참조하는 경우를 순환 참조라고 말한다.

Field Injection, Setter based Injection은 빈이 생성된 후 참조를 하기에 애플리케이션은 아무런 오류나 경고 없이 구동된다. 실제 코드가 호출되기 전까지는 문제를 알 수 없는 것이다.

 

반면, Constructor based Injection으로 통해 실행해보면 BeanCurrentlyInCreationException이 발생한다.

순환 참조뿐만 아니라 의존 관계에 내용을 외부로 노출시킴으로써 애플리케이션을 실행하는 시점에서 오류를 체크할 수 있다.

 

3. 테스트 작성의 편의성

테스트를 하려는 클래스가 Field Injection을 사용할 때에는 외부에서 빈을 주입해줄 수가 없다. 그렇기에 해당 필드는 null 이 된다. 따라서 스프링 빈 및 모든 설정을 가져와서 실행해야 테스트를 할 수 있다. Constructor based Injection일 때는 테스트 코드 자체에서 필요한 의존관계만 만들어서 테스트가 가능하다.