공부/Spring

스프링 IoC

Lai_Khan 2020. 5. 29. 23:38
[인프런] 백기선님의 강의 「예제로 배우는 스프링 입문 (개정판)」 을 듣고 정리

 

스프링 IoC

IoC = Inversion of Control

IoC란  제어권이 역전되었다. 는 의미다.

 

일반적인 경우, 자기가 만든 의존성을 자기가 사용한다.

이 의존성에 대한 제어권이 역전되었다는 것은 이 제어권을 원래는 자기자신이 가지고 있었는데 다른 누군가에게 넘어갔다는 의미이다.

 

다음 코드를 봐보자.

class OwnerController {
    private OwnerRepository repository = new OwnerRepository();
}

위 코드에서 OwnerController 클래스는 내부에서 OwnerRepository 객체를 직접 만들어 쓴다. 이 경우, 의존성에 대한 제어권이 자기자신에게 있다고 할 수 있다.

 

class OwnerController {
    private OwnerRepository repo;
    
    public OwnerController(OwnerRepository repo) {
    	this.repo = repo;
    }
}

그런데 이 코드에서는 OwnerController가 OwnerRepository를 사용은 하지만 OwnerRepository 객체를 내부에서 만들지는 않는다. 누군가가 밖에서 줄 수 있게끔 생성자를 통해 받아온다.

 

의존성을 만드는 일을 더이상 OwnerController가 하지 않고 누군가가 OwnerController밖에서 해준다. 그렇기 때문에 제어권이 역전되었다고 볼 수 있다.

 

이렇게 의존성을 주입해주는 것을 Inversion of Control이라고 한다.

그래서 Dependency Injection(의존성 주입)도 일종의 IoC라고 볼 수 있다.

 

스프링 IoC 컨테이너

주로 하는 일 : 빈(Bean)을 만들고, 빈들 사이의 의존성을 엮어주며, 빈들을 제공해준다.

빈 설정

  • 이름 또는 ID
  • 타입
  • 스코프

 

IoC 컨테이너는 BeanFactory 또는 ApplicationContext 중 하나를 사용한다. 이 중 ApplicationContext는 BeanFactory를 상속받으며, 그 외에도 더 다양한 기능을 가지고 있다.

 

알아둬야 할 점은 「의존성 주입은 빈끼리만 가능하다.」 는 것이다.

빈(Bean)이란 스프링이 관리하는 객체를 의미한다.

모든 객체가 다 빈으로 등록되어 있진 않다. 빈은 상속, Annotation, 직접 등록 등 다양한 방법으로 등록될 수 있는데, 이때 서로 간의 의존성 주입을 IoC 컨테이너가 해준다.

즉, 스프링 IoC 컨테이너 안에 들어있는 객체끼리만 의존성 주입을 해줄 수 있다. 밖에 있는 객체들은 X

 

스프링 빈(Bean)

빈(Bean)이란 스프링 IoC 컨테이너가 관리하는 객체를 의미한다.

 

그냥 객체를 만들었다고 해서 다 Bean이 아니다. Bean과 일반적인 객체의 차이는 스프링 IoC 컨테이너, 즉 ApplicationContext가 알고있는 객체냐 아니냐, ApplicationContext가 만들어서 그 안에 담고 있는 객체냐 아니냐다.

그냥 new로 객체를 만들면 Bean이 아니다.

 

그렇다면 어떻게 스프링 컨테이너 안에 Bean을 만들까?

크게 2가지 방법이 있다.

  • @Component
    • @Repository
    • @Service
    • @Controller
    • @Configuration 등등....
  • 또는 직접 일일히 XML이나 자바 설정 파일에 등록하는 방법

 

이 중 @Component Annotation을 이용하는 방법에 대해 설명해 보자면, Annotation Processor(어노테이션 처리기) 중에 스프링 IoC 컨테이너가 사용하는, 스프링 IoC 컨테이너를 만들고 그 안에 Bean을 등록할 때 사용하는 인터페이스들이 있는데, 그런 인터페이스들을 Lifecycle Callback이라 한다.

여러 Lifecycle Callback 중에는 이런 @Component가 붙어있는 Model Class를 찾아서 그 클래스의 인스턴스를 만들어서 Bean으로 등록하는, 그런 일을 처리하는 Annotation Processor(어노테이션 처리기)가 등록되어 있다.

 

@SpringBootApplication 안에 @ComponentScan이란 Annotation이 있는데 이 Annotation이 어느 지점부터 컴포넌트를 찾아봐야 할지를 알려준다. 해당 위치부터 모든 하위 패키지에 있는 모든 클래스를 다 찾아본뒤 @Component 등을 사용하는 Class를 찾아 Bean으로 등록하는 것이 ComponentScan의 기능이다.

 

Bean으로 등록하는 코드의 예시를 살펴보자. 첫번째로 @Component Annotation을 사용해서 Bean으로 등록하는 방법이다.

@Controller
public class SampleController {

}

클래스 위에 @Component를 붙여주기만 하면 된다. ComponentScan이 알아서 찾아서 Bean으로 등록한다.

 

두번째로, 직접 Bean으로 등록하는 방법이 있다.

@Configuration
public class SampleConfig {
    @Bean
    public SampleController sampleController() {
    	return new SampleController;
    }
}

@Bean을 사용해 클래스 내에서 직접 Bean으로 등록해 준다.

 

이 밖에도 XML 파일에서 직접 빈으로 등록할 수 있다.

 

그렇다면 꺼내쓸 때는 어떻게 할까?

이것도 크게 2가지 방법이 있다.

  • @Autowired 또는 @inject
  • 또는 ApplicationContext에서 getBean()으로 직접 꺼내쓴다.

 

@Autowired를 사용해 IoC 컨테이너 안에 들어있는 Bean을 주입받아서 사용할 수 있다.

@Autowired
private OwnerRepository owners;

 

의존성 주입 (Dependency Injection)

스프링이 제공하는 다양한 의존성 주입 방법이 있다. 하나씩 살펴보자.

 

@Autowired / @inject를 사용할 수 있는 지점이 많다.

  • 생성자
  • 필드
  • Setter

 

스프링 버전 4.3부터 어떠 클래스에 생성자가 하나뿐이고, 그 생성자로 주입받는 reference 변수들이 Bean으로 등록되어 있다면 그 Bean을 자동으로 주입해주도록 하는 기능이 스프링 프레임워크에 추가되었다. (= @Autowired 생략이 가능하다.)

@Controller
public class OwnerController {
    private OwnerRepository owners;
    
    // @Autowired -> 생략 가능
    public OwnerController(OwnerRepository repo) {
    	this.owners = repo;
    }
}

 

생성자가 아닌 필드로 바로 주입받는 방법이 있다.

위 코드를 이렇게 고치면 된다.

@Controller
public class OwnerController {
    @Autowired
    private OwnerRepository owners;
}

 

Setter에 @Autowired를 붙이는 방법

@Controller
public class OwnerController {
    private OwnerRepository owners;
    
    @Autowired
    public void setOwners(OwnerRepository owners) {
    	this.owners = owners;
    }
}

스프링 IoC 컨테이너가 OwnerController 인스턴스를 만들고나서 Setter를 통해서 IoC 컨테이너에 들어있는 Bean 중에 OwnerRepository 타입을 찾아서 여기에 넣어준다.

 

이렇듯 의존성을 주입받는 방법은 여러가지가 있지만 이 중에서 생성자를 사용하는 방법을 권장한다.

생성자를 사용하는 방법이 좋은 이유는 필수적으로 사용해야하는 reference 옵션에 이 인스턴스를 만들지 못하도록 강제할 수 있기 때문이다. 이게 무슨 소리냐면 위 코드에서 OwnerController 클래스는 OwnerRepository가 없으면 제대로 동작하지 않는 클래스라고 하자. OwnerRepository는 OwnerController 클래스에게 반드시 있어야 할 객체다. 따라서 OwnerRepository 객체가 제대로 주입되지 않으면 아예 OwnerController 클래스인스턴스가 만들어지지 않는다. 이걸 강제하기 위한 가장 좋은 수단이 생성자를 사용하는 것이다.

 

필드 Injection이나 Setter Injection은 일단 이 클래스의 인스턴스를 만들 수가 있다.

하지만 그 자체가 장점이 될 수 있는 경우가 있는데 바로 순환 참조(Circular Dependency)가 발생하는 경우다. 경우에 따라 클래스 A와 B가 서로를 참조할 때 둘다 생성자 Injection을 사용하고 있다면 둘다 못 만든다. 그런 경우에는 필드 또는 Setter Injection을 사용해서 일단 인스턴스를 만든 다음 서로의 인스턴스를 주입하면 된다. 이렇게 상호 참조하는 의존성 문제를 해결할 수 있다.