Framework/Spring

[Spring] IoC와 DI란 무엇인가?

Joonfluence 2024. 9. 20.

제어의 역전 (IoC, Inversion of Control)

개발자가 작성한 객체나 메서드의 제어를 개발자가 아니라 외부(프레임워크)에 위임하는 설계 원칙제어의 역전이라고 한다.

관련 용어

빈 (Spring Bean)

  • 스프링이 IoC 방식으로 관리하는 오브젝트를 뜻한다. 자바 빈과 구분 짓기 위해, 스프링 빈이라고도 부른다.
  • 참고로 스프링 내 모든 오브젝트가 전부 빈이 아니다. 그 중에서 스프링이 직접 그 생성과 제어를 담당하는 오브젝트만을 빈이라고 부른다.

빈 팩토리 (Bean Factory)

  • 스프링의 IoC를 담당하는 핵심 컨테이너를 말한다.
  • 스프링 컨테이너의 최상위 인터페이스로, 스프링 빈을 관리하고 조회하는 역할을 담당한다. 대표적으로 getBean() 메소드를 제공한다.

애플리케이션 컨텍스트 (ApplicationContext)

  • 빈 팩토리를 확장한 IoC 컨테이너를 말한다. 스프링 컨테이너라고도 부른다.
  • 빈을 등록하고 관리하는 기본적인 기능은 빈 팩토리와 동일하나, 스프링이 제공하는 각종 부가 서비스를 포함해서 이야기할 때 애플리케이션 컨텍스트라고 이야기한다.
  • 대표적으로 메제시 소스를 활용한 국제화 기능, 환경변수, 애플리케이션 이벤트, 리소스 조회 기능을 제공한다.
    public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, 
    	HierarchicalBeanFactory, MessageSource, 
    	ApplicationEventPublisher, 
    	ResourcePatternResolver {}

설정정보/설정 메타 정보

  • 스프링의 설정정보란 애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보를 말한다.
  • 자바 코드, XML, Groovy 등 다양한 형식으로 설정 정보를 기술할 수 있다.

컨테이너 또는 IoC 컨테이너

  • IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC 컨테이너라고 부른다.
  • 애플리케이션 컨텍스트는 그 자체로 AppicationContext 인터페이스를 구현한 오브젝트를 가리키기도 하는데, 애플리케이션 컨텍스트 오브젝트는 하나의 애플리케이션에서 보통 여러 개가 만들어져 사용된다. 이를 통틀어서 스프링 컨테이너라고 부를 수 있다.

스프링 프레임워크

  • IoC 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할 때 주로 사용한다.

스프링 컨테이너 생성 과정

스프링 컨테이너 생성

  • 아래와 같이 명시적으로 생성할 수 있다.
    // 스프링 컨테이너 생성
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
    	HelloConfig.class
    );

스프링 빈 등록

  • 스프링 빈 등록 방식에는 수동 등록과 자동 등록이 있다. 아래는 수동 등록
  • 이 때 빈 이름은 항상 다른 이름으로 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.
  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.
@Configuration
public class HelloConfig {

    @Bean
    public HelloController helloController() {
        return new HelloController(helloService());
    }

    @Bean
    public HelloService helloService() {
        return new HelloService();
    }
}

스프링 빈 의존관계 설정

  • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다. 빈을 생성하고 의존관계를 주입하는 단계가 나뉘는데, 이 때 의존관계를 주입하는 방법에 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계도 함께 처리된다. 
@Configuration
public class HelloConfig {

    @Bean
    public HelloController helloController() {
        return new HelloController(helloService());
    }

    @Bean
    public HelloService helloService() {
        return new HelloService();
    }
}

스프링 빈 등록 방식

스프링 빈을 등록하는 방법에는 크게 2가지 방법이 있다.

수동 등록

  • 앞서 스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있도록 설계되어 있다고 했다. 수동 등록하는 방법에도 3가지가 있다. 자바 코드로 하는 방법, XML 코드로 하는 방법, Groovy로 등록하는 방법. 이 중 가장 일반적인 방법은 어노테이션 기반의 자바 코드로 설정하는 것이다.

자동 등록

  • @SpringBootApplication 을 통해, 사용할 수 있다.

IoC와 연관된 어노테이션 정리

@Component과 @CoponentScan

  • 스프링 부트로 개발할 때, 사용되는 @SpringBootApplication에는 @ComponentScan이 포함되어 있다.
  • 스프링 부트 실행 시, 컴포넌트 스캔을 시작하며 @Component이 추가된 클래스를 찾아 스프링 빈으로 등록합니다.
  • WebMvc에서 사용하는 @Controller, @Service, @Repository에도 @Component이 포함되어 있습니다.

@Configuration

  • Bean 메타정보들을 담고 있는 클래스입니다. 
  • 클래스에 @Configuration을 추가하면 관리해야될 빈들이 있다고 SpringContainer에게 알려주는 어노테이션입니다. 

@Bean

  • 메소드 레벨에서 선언하며, 반환되는 객체(인스턴스)를 개발자가 수동으로 빈으로 등록하는 애노테이션입니다. 

의존성 주입 (DI, Dependency Injection)

스프링에서 의존성을 주입하려면 먼저 스프링 컨테이너가 생성되고 스프링 빈이 등록되어야 한다.

관련 용어

의존관계

  • 여기서 의존관계란 A가 B의 메서드를 사용하는 경우, A에서 B에 대한 의존관계가 있다고 말할 수 있다. 의존관계에는 방향성이 있으며, A가 B에 의존하고 있지만 반대로 B는 A에 의존하지 않는다. 이는 곧, B는 A의 변화에 영향을 받지 않는다는 의미이다.

DI(의존성 주입) 구현 방법

생성자 주입

생성자 주입은 생성자를 통해서 의존 관계를 주입받는 방법이다.

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    private final HelloService helloService;

    @Autowired
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    @GetMapping("/hello-spring")
    public String hello(){
        return helloService.hello();
    }
}
  • 장점
    • 의존성을 불변으로 만들어 우발적인 변경을 방지할 수 있다.
  • 단점
    • 한 번 의존 관계가 주입되면, 변경할 수 없다.
    • 순환 종속성이 발생할 수 있다.

Setter 주입

필드의 값을 변경하는 수정자 메서드(Setter)를 통해서 의존 관계를 주입하는 방법이다.

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    private HelloService helloService;

    @GetMapping("/hello-spring")
    public String hello(){
        return helloService.hello();
    }

    @Autowired
    public void setHelloService(HelloService helloService) {
        this.helloService = helloService;
    }
}
  • 장점
    • 선택, 변경 가능성이 있는 의존 관계에서 사용하기 적합하다.
    • 순환 참조 문제를 예방할 수 있다.
  • 단점
    • 의존 관계를 외부에서 변경 가능하므로, 예측하기 어려운 사이드 이펙트가 발생할 가능성이 있다.

필드 주입

필드에 @Autowired를 붙여서 의존 관계를 주입하는 방법이다.

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    HelloService helloService;

    @GetMapping("/hello-spring")
    public String hello(){
        return helloService.hello();
    }
}
  • 장점
    • 사용하기 편리하다.
    • 테스트 환경에서 주로 사용한다.
  • 단점
    • 스프링에 종속적이다. 스프링 컨테이너 없이는 사용할 수 없다.
    • 변경에 대응하기 어렵다. 유닛 테스트를 하려면 리플렉션과 mock 프레임워크를 사용해야 한다.
    • 필드 주입은 순환 참조 문제를 막을 수 없다.

일반 메서드 주입

일반 메서드를 통해서 의존 관계를 주입하는 방법이다.

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    private HelloService helloService;

    @GetMapping("/hello-spring")
    public String hello(){
        return helloService.hello();
    }

    @Autowired
    public void init(HelloService helloService) {
        this.helloService = helloService;
    }
}
  • 특징
    • 한 번에 여러 의존관계를 한 번에 주입 받을 수 있다.
    • 이 방법은 잘 사용하지 않는다.

참고한 사이트

토비의 스프링 3.1
https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html

반응형

댓글