[SpringBoot] 스프링부트 제어 역전(IoC), 의존성 주입(DI)

2025. 12. 12. 18:09·Spring,SpringBoot

 

스프링 프레임워크 핵심 가치는 아래와 같다.

애플리케이션 개발에 필요한 기반을 제공해서 개발자가 비즈니스 로직 구현에만 집중할 수 있도록 한다.

 

스프링 프레임워크는 개발자가 비즈니스 로직 개발에만 집중할 수 있게 한다.  

IoC, DI, AOP 3대 기술들을 이용해 스프링 프레임워크의 철학과 목적을 구현하고 제공하고 있다. 

 


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

IoC는 사용할 객체를 직접 생성하지 않고, 객체 생명주기를 외부(스프링 컨테이너)에 위임하는 것이다.

 

 

아래 코드처럼 일반적인 자바 프로그램에서는 객체의 생성과 관리의 주도권은 개발자에게 있다.

public class IoCTest1 {
    MyServiceImpl myService = new MyServiceImpl();

    public String getHello(){
        return myService.getHello();
    }
}


class MyServiceImpl{
    public String getHello(){
        return "Hello!!";
    }
}

 

코드에서 개발자가 직접 IoCTest1 클래스 필드에 MyServiceImpl 객체를 생성하여 관리하고 있다.

즉, 개발자가 필요한 객체를 직접 new로 만들어 사용중이다.

 

이렇게 new를 사용해 인스턴스를 계속 만들다 보면 나중에 코드를 고치기가 힘들어질 수 있다.

 

왜 고치기 힘든지 핵심적인 3가지 이유는 다음과 같다.

 

1. 결합도가 너무 높아짐

  new를 직접 사용한다는 것은, "이 구체적인 클래스가 아니면 안 돼"라고 못 박는 것과 같다. 이로 인해 코드에 결합도가 증가하게 된다.  (좋은 소프트웨어는 결합도가 낮고 응집도가 높아야 함)

 

2. 불필요한 객체 생성

  웹 서버를 배포했다고 가정하면 new 사용 시 사용자에 따라 객체를 끊임없이 생성할 것이다.

  하지만 IoC를 사용한다면 스프링은 기본적으로 객체를 싱글톤으로 관리하므로 만든 객체를 공유하여 훨씬 효율적이다.

 

3. SRP 위반이 될 수 있다.

  좋은 객체 지향 설계를 위해서 하나의 클래스는 하나의 책임만 져야 하는데  new를 사용한다면

     1. 자신의 업무

     2. 엄무를 수행하기 위한 객체 생성과 관리  

   이렇게 2가지 역할을 책임지게 되며 SRP를 위반한 코드가 된다.

 

 

 

그렇다면 개발자가 객체를 직접 생성(new) 하지 않고 객체를 어떻게 생성하고 관리할까? 

객체 생성과 관리의 책임을 개발자가 아닌 다른 누군가에게 넘겨버리면 된다.  (제어의 역전)

public class DITest {
    private final MyService myService;

    //생성자를 통해 외부(스프링)로부터 객체를 받아온다. - 주입
    public DITest(MyService myService) {
        this.myService = myService;
    }

    public String getHello() {
        return myService.getHello();
    }
}

class MyService {
    public String getHello() {
        return "Hello!!";
    }
}

 

현재 코드는 외부 스프링 컨테이너에서 객체를 직접 주입받는 코드는 아니다. 보통 주입받을 때 저런 로직으로 주입받는데 DI부분에서 해당 코드를 스프링 컨테이너에서 주입받는 코드로 점진적 발전을 시키겠습니다.

 

 

⭐ IoC의 결론

IoC(제어의 역전)는 특정 코드나 스프링부트에서 지원하는 코드가 아니고, 설계 원칙(Design Principle)입니다.
다시 말해 IoC라는 철학을 구현하기 위해 스프링이라는 도구를 사용합니다. 

더 정확하게는 IoC 철학을 구현하기 위해 스프링 컨테이너라는 도구를 이용해 DI(방법)으로 객체를 직접 생성이 아닌 외부에서 주입해 주는 방식입니다. 

 

 

 

 

제어의 역전을 구현하기 위해 스프링프레임워크는 스프링 컨테이너와 DI를 이용합니다.

 

 


DI(Dependency Injection): 의존성 주입

 

의존성이란? 

  클래스가 자신의 기능을 수행하기 위해 꼭 필요한 다른 객체를 말한다.

 

class Order{
	private final OrderRepository repo;	//Order의 의존성이다.
}

 

Order 클래스의 주문 기능을 수행하기 위해서는 OrderRepository 객체가 필요하다. 이때 OrderRepository는 Order의 의존성이 된다.

 

 

전통적인 방식 (IoC 방식이 아님)

class Order{
	private final OrderRepository repo = new OrderRepository();
}

 

  Order 클래스가 스스로 OrderRepositroy 객체를 생성한다.

  위 코드는 Order와 OrderRepository가 강하게 결합되어 있어 테스트와 구현 변경에 어려움이 존재한다.   (IoC 철학 도입 시급)

 

 

위 코드에서 new를 이용해 직접 객체를 생성하고 생명주기를 관리하였다. 하지만 IoC에서는 컨테이너가 대신 만들어서 관리하는데 컨테이너가 생성한 객체를 다른 객체에 의존성이 필요하면 의존성 주입을 사용해야 한다.

 

 

 

의존성 주입(DI)의 정의

의존성(필요한 객체)을 스스로 만들지 않고, 외부(컨테이너)가 대신 생성하여 객체 안으로 주입하는 것이다.

 

 

스프링에서 의존성 주입 세 가지 방식

  1. 생성자를 통한 의존성 주입  (생성자 주입)  👍

  2. 필드 객체 선언을 통한 의존성 주입  (필드 주입)  👎

  3. setter 메서드를 통한 의존성 주입  (세터 주입)

 

스프링에서는 @Autowired라는 어노테이션을 통해 의존성을 주입할 수도 있다.

 

 

 

1. 생성자를 통한 의존성 주입 코드

@RestController
public class Order {
    OrderRepository orderRepository;

    @Autowired
    Order(OrderRepository orderRepository){
        this.orderRepository= orderRepository;
    }

}

 

생성자 주입은 Autowried를 생략해도 된다. 스프링에서 알아서 어떤 객체를 주입받으려는지 판단하여 의존성을 주입해 준다.

⚠️주의: 생성자가 2개 이상일 때  생성자가 여러 개라면 스프링이 어떤 생성자를 써야 할지 모릅니다. 이때는 주입받을 생성자에 반드시 @Autowired를 붙여야 한다.

 

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Order {
    OrderRepository orderRepository;    //OrderRepo는 Order의 의존성
    //💡`@Autowired 생략  스프링이 알아서 의존성 주입
    Order(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @PostMapping("/save")
    public String save() {
        orderRepository.save();
        return "The action executed correctly.";
    }
}

interface OrderRepository {
    void save();
}

 

 

 

2. 필드 객체 선언을 통한 의존성 주입 코드

@RestController
public class Order {
    @Autowired
    OrderRepository orderRepository;
}

 

 

 

3. setter 메서드를 통한 의존성 주입 코드

@RestController
public class Order {
    OrderRepository orderRepository;

    @Autowired
    public void setOrderRepository(OrderRepository orderRepository){
        this.orderRepository = orderRepository;
    }
}

 

 

정리

제어의 역전(IoC) = 객체 생성, 연결을 내가 아닌 컨테이너가 한다는 원리  (일종의 철학)

의존성 주입(DI) = IoC를 실현하기 위한 구체적인 방법 중 하나.

 

✅ 기본적으로 컨테이너 시작 시 대부분 빈을 다 만들어 놓는다.(컨테이너에 객체 생성)

✅ 의존성 주입은 이미 생성된 객체(컨테이너가 관리하는 객체)를 가져와 서로 연결하는 것이다.

 

참고로 스프링부트의 컨테이너에 존재하는 객체들은 싱글톤 디자인패턴으로 객체들을 관리한다.

 

 

DI의 대전제

  의존성 주입(@Autowired)을 받으려면, 반드시 의존성을 주는 놈과 받는 놈 둘 다 스프링 빈이어야 한다.

  그래야 스프링을 알 수 있기 때문. @Component가 없다면 스프링은 절대 관여하지 않습니다. 

더보기

  OrderReository가 @Repository 어노테이션을 이용해 @Component가 존재해도 

  받는 쪽 Order 클래스는 순수 자바 클래스이므로 스프링에서는 Order 클래스 관리는 안 합니다.

  Order에 @Component가 없기 때문.

public class Order {
    OrderRepository orderRepository;   
    @Autowired
    Order(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @PostMapping("/save")
    public String save() {
        orderRepository.save();
        return "The action executed correctly.";
    }
}

                    

 

 

 

🔎 @Autowried에 대해 자세히 까보기

더보기
package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	boolean required() default true;

}

 

@Autowried 어노테이션을 어디에 붙일 수 있을지는 @Target을 통해 확인 가능하다. (생성자, 메서드, 파라미터, 필드, 어노테이션 위에도 붙일 수 있다.)

 

@Rentention(RetentionPolicy.RUNTIME)  = 런타임까지 유지

 

boolean required() default true;

 

필드에 해당 메서드가 핵심이라 볼 수 있는데   required = true의 의미는 Bean이 반드시 존재해야 함 아니면 에러 발생

required = false는 없으면 null 또는 Optional.empty()로 처리이다.

 

애초에 의존성 주입하는데 requried=false는 보통 안 쓴다.

 

 

 

 

 


 

스프링 컨테이너와 스프링 빈  (나중에 따로 더 공부할 예정)

 

스프링 컨테이너는 org.springframework.context.ApplicationContext 인터페이스를 구현한 객체이다.

 

핵심 역할은 다음과 같다.

단순한 객체 저장소가 아닌 내부적으로 설정 메타데이터(Configuration Metadata)를 읽어 들여, 빈 정의(BeanDefinition)를 생성하고, 이를 바탕으로 객체의 인스턴스화, 의존성 주입, 초기화, 소멸에 제어하는 런타임 엔진이다.

 

 

 

스프링 빈

스프링 컨테이너가 BeanDefinition이라는 메타데이터를 기반으로 생성하고 관리하는 자바 인스턴스이다.

 

 

 

 

@Configuration으로 직접 스프링 빈 등록 후 의존성 주입

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//순수한 자바 클래스들
interface OrderRepo { void save();}
class OrderRepoImpl implements OrderRepo{public void save(){}}
class OrderService {public OrderService(OrderRepo orderRepo){}}
class OrderController {public OrderController(OrderService orderService){}}

@Configuration 
public class Config {

    @Bean
    public OrderRepo orderRepo() {
        return new OrderRepoImpl();
    }
    
    @Bean
    public OrderService orderService() {
        return new OrderService(orderRepo());
    }
    
    @Bean 
    public OrderController orderController() {
        return new OrderController(orderService());
    }
}

 

동작 해석

  • 스프링은 @Configuration이 붙은  Config 클래스를 파싱 한다.
  • @Bean이 붙은 메서드를 실행하여 반환값을 컨테이너에 빈으로 등록한다.
  • orderService() 메서드 안에서 orderRepository()를 호출할 때, 일반적인 자바 코드라면 매번 new가 실행되어야 하지만, 스프링은 CGLIB 바이트코드 조작을 통해 이미 생성된 빈이 있으면 그것을 반환하도록 가로챕니다(Interception).

 

 

 

 

스프링 부트의 기술적 프로세스 요약

SpringApplication.run() 실행되면 다음과 같은 절차가 일어난다.

 

1. 스캔(Scan):  @ComponentScan 설정에 따라 @Component가 붙은 클래스들을 찾는다.

2. 등록(Registraction): 찾은 클래스 정보를 바탕으로 BeanDefinition을 생성하여 컨테이너에 등록 (아직 객체 생성 아님)

3. 인스턴스화(Instantiation): BeanDefinition을 참조하여 실제 객체 인스턴스를 생성한다. (싱글톤 생성규칙에 맞게 생성)

4. 의존성 주입(Dependancy Injection): 생성된 객체의 생성자나 필드에 @Autowired가 존재하면 컨테이너가 다른 빈 객체의 참조를 주입

5. 초기화(Initialization): @PostConstruct 같은 콜백 메서드가 있다면 실행

6. 사용 준비 완료:  이제 ApplicationContext에 getBean()을 통해 언제든 꺼내 쓸 수 있는 상태가 된다.

'Spring,SpringBoot' 카테고리의 다른 글

[Spring] @ComponentScan, @Configuration  (0) 2026.01.02
[SpringBoot] @Component  (1) 2025.12.19
[ETC] API, REST, REST API  (0) 2025.09.04
[SpringBoot] Lombok 설치  (0) 2025.08.03
[Spring Boot] Hello API 만들어 보기 (@RestController, @GetMapping)  (0) 2025.04.08
'Spring,SpringBoot' 카테고리의 다른 글
  • [Spring] @ComponentScan, @Configuration
  • [SpringBoot] @Component
  • [ETC] API, REST, REST API
  • [SpringBoot] Lombok 설치
oneH
oneH
  • oneH
    Hello WeonHyeok!
    oneH
  • 전체
    오늘
    어제
    • 분류 전체보기 (66)
      • CSS (1)
      • Javascript (5)
        • JS자료구조,알고리즘 (1)
      • Java (26)
        • OOP (10)
      • JSP (1)
      • Computer Network (2)
      • 이론 컴퓨터 (2)
      • Project (0)
      • Algorithm&Data Structure (12)
      • 데이터베이스 (4)
      • Spring,SpringBoot (6)
      • Git & GitHub (1)
      • Android (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Java
    이진검색
    덱
    Selector
    티스토리챌린지
    object
    Git
    컴퓨터구조
    JS
    뷰클래스
    combinators
    SQL
    Stack
    컴퓨터 네트워크
    오블완
    Android
    선택자
    OSI7계층
    스택
    View
    MySQL
    JavaScript
    큐
    폰노이만 아키텍쳐
    OOP
    링크드 리스트
    선형 큐
    컴퓨터네트워크
    math
    Algorithm
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
oneH
[SpringBoot] 스프링부트 제어 역전(IoC), 의존성 주입(DI)
상단으로

티스토리툴바