노션으로 다시 돌아갔습니다 😅

[Spring 특징] IoC (Inversion of Control) 와 DI (Dependency Injection)

by mignon25

IoC (Inversion of Control) 란?

애플리케이션 흐름의 주도권을 Spring 이 갖는다.

 

일반적인 애플리케이션 제어 흐름과 IoC가 적용된 제어 흐름의 차이

1. 일반적인 애플리케이션 제어 흐름

EX) Java 콘솔 애플리케이션 

// 순수 Java 코드만으로 간단한 메세지를 콘솔에 출력하는 프로그램

public class Example {
	public static void main(String[] args) {
		System.out.println("Hello IoC!");
    }
}
  • 일반적으로 위와 같은 Java 콘솔 애플리케이션을 실행하려며 main() 메서드가 있어야 한다.
  • main() 메서드 호출
  • System 클래스를 통해 static 멤버 변수인 outprintln() 을 호출 

이렇게 개발자가 작성한 코드를 순차적으로 실행하는 것이 애플리케이션의 일반적인 제어 흐름이다. 

 

2. Java 웹 애플리케이션에서의 IoC 적용

EX) 서블릿 컨테이너의 서블릿 호출

서블릿 기반의 애플리케이션을 웹에서 실행하기 위한 서블릿 컨테이너의 모습

 

Java 콘솔 애플리케이션의 경우 main() 메서드가 종료되면 애플리케이션의 실행이 종료된다. 

그러나 웹에서 동작하는 애플리케이션의 경우 실행 상태가 계속 유지되어야 한다. (클라이언트가 언제 접속할 지 모르므로)

그런데 서블릿 컨테이너에는 서블릿 사양(Specification)에 맞게 작성된 서블릿 클래스만 존재하고, 별도의 main() 메서드가 존재하지 않는다. 

 

그렇다면 엔트리 포인트인 main() 메서드 없이 애플리케이션이 어떻게 실행되는 것일까?

 

=> 서블릿 컨테이너의 경우, 클라이언트의 요청이 들어올 때마다 서블릿 컨테이너 내의 컨테이너 로직(service() 메서드)이 서블릿을 직접 실행시켜 주므로 main() 메서드가 필요하지 않다. 

 

이런 경우, 서블릿 컨테이너가 서블릿을 제어하고 있기 때문에 애플리케이션의 주도권은 서블릿 컨테이너에 있는 것이다.

이것이 바로 서블릿과 웹 애플리케이션 간에 IoC(제어의 역전) 개념이 적용되어 있는 것!

 

 

그렇다면 Spring 에는 IoC 개념이 어떻게 적용되어 있을까?

=> DI (Dependency Injection)


DI(Dependency Injection) 란?

IoC(제어의 역전)는 서버 컨테이너 기술, 디자인 패턴, 객체 지향 설계 등에 적용하게 되는 일반적인 개념인 반면, DI(Dependency Injection)는 IoC 개념을 조금 구체화 시킨 것이라고 볼 수 있다. 

 

 

의존성 주입이 무엇일까?

1. 의존성

  • 객체지향 프로그래밍에서의 의존성 => 객체 간의 의존성
class A {
	B b;
    
    A(B b) {
    this.b = b;
    }
    
   	methodA() {
    	b.methodB();
    }
}

class B {
	methodB() {
    //methodB 로직
	}
}

위와 같은 클래스 A 와 클래스 B 가 있다고 가정해보자.

클래스 A 의 methodA() 의 동작을 보면 클래스 B의 methodB() 를 실행하고 있다. 

즉, 클래스 A의 메서드는 클래스 B 의 메서드 없이는 동작할 수 없다. 

=> 이렇게 A 클래스가 B 클래스의 기능을 사용할 때, 클래스 A 는 클래스 B 에 의존한다. 라고 표현한다. 

 

2. 그렇다면 "의존성 주입" 이란?
 위의 예시에서 A 클래스에서 B 클래스의 기능을 사용하기 위해서는 일단 A 클래스 내부에서 B의 인스턴스가 정의되어야 한다. 

A 클래스에서 B 인스턴스를 정의하기 위해서는 

  • A 클래스 내부에서 직접 B 클래스의 인스턴스를 생성
  • A 클래스에서는 B 클래스 타입의 멤버변수만 선언하고, A 클래스의 생성자를 통해 B 타입의 멤버변수 값을 초기화

이렇게 두 가지 경우로 정의할 수 있을 것이다. 

// A 클래스 내부에서 직접 B 클래스의 인스턴스를 생성

class A {
	B b = new B();

	methodA() {
    	b.methodB();
    }
}


// 의존성 주입
// A 클래스에서는 B 클래스 타입의 멤버변수만 선언하고, A 클래스의 생성자를 통해 B 타입의 멤버변수 값을 초기화
class A {
	B b;
    
    A(B b) {
    this.b = b;
    }
    
	methodA() {
    	b.methodB();
    }
}

2번 방식처럼 외부에서 생성자를 통해 의존관계에 있는 클래스의 인스턴스를 생성 및 참조하는 것을 의존성 주입이라고 한다. 

 

3. 의존성 주입이 필요한 이유

1번 방식에서 B를 생성하는 방식이 바뀌었다고 생각해보면,

B 클래스의 내용만 변경하면 끝나는 것이 아니라 A 내부의 B 인스턴스 생성 로직도 수정이 필요하다. 

B 클래스를 한 군데가 아니라 수십, 수백 군데에서 사용하고 있다면?

수십,  수백 군데의 사용하는 모든 곳에서 수정이 필요해진다. 

 

그러나 2번 방식(의존성 주입)의 경우 A 클래스를 사용하는 곳(즉, 외부)에서 A 클래스의 인스턴스를 생성할 때 그 생성자에 B 클래스를 넣어서 생성하는 것이므로, B 가 변경되더라도 A 클래스 내부에는 B 의 생성관련 로직이 없으므로 A 클래스를 수정할 필요가 없다. 

 

 

느슨한 의존성 주입

의존성 주입을 하더라도, 구체적인 클래스를 대상으로 다른 클래스에 의존성을 주입한다면?

위의 경우처럼 B 클래스를 그대로 생성자의 인자로 사용하는 경우라고 할 수 있다. 

예를 들어 B 클래스가 한 가지의 할인 정책을 다루고 있는 클래스였다고 가정해보자.

할인 정책은 시기에 따라 변경될 수 있다. 

그렇다면 정책이 변경되면?  C 라는 할인 정책을 사용해야 한다면?

B 클래스를 사용하던 모든 곳에서 B -> C 로 변경하는 작업이 필요해질 것이다. 

 

이렇듯 의존 대상이 구체적인 클래스 자체가 될 경우 또다시 객체 간 결합도가 높아지게 된다. 

결론적으로, 의존성 주입을 하더라도 의존성 주입의 혜택을 보기 위해서 느슨한 결합이 필수적이다. 

 

느슨한 의존성 주입은 어떻게 하면 될까? 

=> 대표적인 방법은 인터페이스(interface)를 사용하는 것이다. 

 

B 클래스와 C 클래스가 모두 하나의 인터페이스를 구현하고,

의존성을 주입하는 곳에는 그 인터페이스 타입의 변수를 사용하도록 하는 것이다. 

 

사용하는 곳 , 위의 그림에서는 MenuController 가 될 것이다. 

MenuController 는 MenuService 라는 인터페이스 타입을 의존하고 있다.

그러나 실제로 MenuService 에 어떤 클래스가 오는지는 신경쓰지 않아도 된다. 

MenuService라는 인터페이스를 구현한 클래스라면 어떤 클래스가 와도 된다. 

 

이후 사용하는 클래스 변경이 필요할 경우, 설정 클래스에서 단 1번의 변경만으로 모든 변경이 끝난다. 

 

// CafeClient
public class CafeClient { 
	public static void main(String[] args) {
    	// spring 지원 API => 객체 생성을 Spring 이 대신해준다. 
		GenericApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        MenuController controller = context.getbean(MenuController.class);
        
        List<Menu> menuList = controller.getMenus();
    }
}


// MenuController
public class MenuController {
	private MenuService menuService;
    
    @Autowired 
    public MenuController(MenuService menuService) {
    	this.menuService = menuService;
    }
    public List<Menu> getMenus() {
    	return menuService.getMenuList();
    }
}


// Config => 설정 클래스
@Configuration
@ComponentScan(basePackageClasses = CafeClient.class)
public class Config {
	// MenuService 에서 실제 사용할 객체 정의
    @Bean
    public MenuService getMenuService() {
		return new MenuServiceStub();
    }
    
    // MenuController 객체 생성 정의
    @Bean
    public MenuController getMenuController(MenuService menuService) {
    	return new MenuController(menuService);
    }
}
  1. Config 클래스에서 MenuController 객체 생성을 정의해두고, 
  2. CafeClient 에서 Spring API 가 객체를 생성하여 애플리케이션 코드에서 사용할 수 있도록 해준다. 

 

변경이 필요할 경우 Config 클래스에서 실제 사용할 객체를 리턴하는 곳의 코드만 다른 클래스로 변경하면 된다.

=> 다른 곳에서의 수정이 전혀 필요하지 않다는 것! 

 

'Spring' 카테고리의 다른 글

스프링 컨테이너  (0) 2023.04.05
[Spring 특징] PSA (Portable Service Abstraction)  (0) 2023.04.04
[Spring 특징] AOP (Aspect Oriented Programming)  (0) 2023.04.04
[Spring 특징] POJO  (0) 2023.04.03
Spring Framework란?  (0) 2023.03.31

블로그의 정보

Mignon'S Dev Log

mignon25

활동하기