1. 제어의 연전(Inversion of Control, IoC)

제어의 역전은 프로그램의 제어 흐름을 개발자가 아닌 프레임워크가 가져가는 것이다.

전통적인 프로그래밍에서는 개발자가 프로그램의 흐름을 제어했는데, 스프링과 같은 환경에서는 프레임워크가 전체적인 흐름을 주도해서 개발자는 프레임워크가 정한 규칙에 따라 필요한 부분만 개발한다.

쉬운 비유로 설명하자면, IoC는 "식당에서 주문한 음식이 알아서 만들어져 나오는 것"과 비슷하다. 손님(개발자)은 주문(코드 작성)만 하고, 실제 요리 과정(프로그램 흐름)은 주방(프레임워크)이 알아서 처리한다.

 

 

2. 의존성 주입(Dependency Injection, DI)

의존성 주입은 한 객체가 다른 객체를 사용할 때, 직접 생성하거나 찾지 않고 외부에서 주입받는 방식을 의미한다.

이는 IoC(제어의 역전) 원칙을 실현하는 핵심 디자인 패턴 중 하나이다. 즉, DI 패턴을 사용해서 IoC 설계 원칙을 구현하고 있다고 할 수 있다.

DI를 통해 객체 간의 결합도를 낮추고, 코드의 재사용성과 유지보수성을 높일 수 있다. 

간단한 예로, 자동차(Car) 객체가 엔진(Engine) 객체를 필요로 한다고 가정해보자.

  • DI를 사용하지 않은 경우
public class Car {
    private Engine engine = new Engine();  // Car가 직접 Engine을 생성
}
  • DI를 사용한 경우
public class Car {
    private Engine engine;
    
    public Car(Engine engine) {  // 외부에서 Engine을 주입받음
        this.engine = engine;
    }
}

 

DI를 사용함으로써 객체 간의 의존성을 외부에서 관리할 수 있게 되며, DI를 사용하면 다음과 같은 장점을 얻을 수 있다.

  • 코드의 재사용성 증가
  • 객체 간 결합도 감소
  • 유지보수성 향상
  • 테스트 용이성 증가

 

 

 

3. Bean과 IoC컨테이너

  • Bean : 스프링이 관리하고 있는 객체
  • IoC 컨테이너 : 빈이 모여있는 컨테이너

 

 

 

4. 의존성 주입의 구현생성자 주입

1. 생성자를 이용한 의존성 주입

public class MemoController {
	private final MemoService memoService;

    public MemoController(MemoService memoService) {
        this.memoService = memoService;
    }
}


@Service
public class MemoService {
	// 서비스계층 내용
}
  • 현업에서 가장 많이 사용되는 방식이다.
  • 생성자 주입 방식에서는 주입되는 객체가 스프링 컨테이너에 의해 관리되는 빈(Bean)이어야 하기 때문에, 주입할 클래스에 @Component, @Service, @Repository, 또는 @Controller 같은 어노테이션을 붙여줘야 한다.
  • 메서드 주입도 가능한데, 이때는 메서드에 @Autowired를 붙여줘야 한다.
  • 불변성을 보장한다. (private final)

 

2. 필드 주입

@Controller
public class MemoController {
    @Autowired
    private MemoService memoService;

    // 기본 생성자가 자동으로 제공됨
}

@Service
public class MemoService {
    // 서비스 계층 내용
}
  • 필드에 직접 @Autowired 어노테이션을 붙여서 의존성을 주입한다.
  • 간단하게 사용할 수 있어서 편리하지만, 권장하지 않는 방법이다.

 

3. 롬복을 이용한 주입

@Controller
@RequiredArgsConstructor
public class MemoController {
    private final MemoService memoService;
    
    // @RequiredArgsConstructor가 생성자를 자동으로 생성해줌
}

@Service
public class MemoService {
    // 서비스 계층 내용
}
  • @RequiredArgsConstructor 어노테이션을 사용하면 final이 붙은 필드에 대해 자동으로 주입해준다.
  • 코드가 더 깔끔해지고, 의존성 주입이 명확해진다.

 

4. 수동으로 IoC 컨테이너에 접근해서 주입

public MemoController(ApplicationContext context) {
    // 1. Bean 이름으로 가져오기
    this.memoService = (MemoService) context.getBean("memoService");

    // 2. Bean 클래스로 가져오기
    this.memoService = context.getBean(MemoService.class);
}
  • 스프링의 IoC 컨테이너에서 직접 Bean을 가져와서 주입하는 방법이다.
  • 이 방법은 특수한 상황에서만 사용한다. 대부분의 경우는 위의 자동 주입 방법을 사용한다.