[Spring] Spring IoC / DI의 기초 개념

Gogh·2023년 1월 2일
0

Spring

목록 보기
1/23
post-thumbnail

🎯 목표 :  IoC와 DI의 기초 개념 학습

📒 IoC(Inversion of Control)

📌 제어의 역전

  • 스프링 애플리케이션에서 객체(Bean)의 생성과 의존 관계 설정, 사용, 제거 등 전반적인 작업을 개발자가 작성한 소스코드가 아닌 스프링 컨테이너가 제어한다.
  • 객체에 대한 제어권을 스프링 컨테이너가 갖고 있다고 하여 IoC라 칭하며, 스프링 컨테이너를 IoC 컨테이너라고도 한다.

📌 Spring IoC Container

vua

  • ApplicationContext 인터페이스를 통해 스프링 컨테이너는 DI에 관련된 Bean 객체의 생성 및 Bean 간의 상호 의존성 관리를 한다.
  • 스프링 컨테이너는 실제로 단순한 DI 관련 작업보다 더 많은 기능을 ApplicationContext 인터페이스 구현을 통해 수행 한다. 예를 들면, 환경변수 구분처리, 애플리케이션 이벤트 관리, 편리한 리소스 조회, 메세지 소스 국제화 기능 등.
  • Spring Container의 최 상위 인터페이스는 BeanFactory이며, ApplicationContext는 빈 팩토리를 상속받은 인터페이스, AnnotationConvigApplicationContext는 ApplicationContext를 구현한 클래스이다.
  • 지금 수준에서는 IoC Container를 Bean을 위한 여러가지 기능을 하는 하나의 박스로 이해하는게 좋을 것 같다.

📒 DI(Dependency Injection)

  • 의존 관계란 의존 대상 B가 변하면 A에 영향을 미치는 것을 A는 B에 의존하고 있다라고 표현할수 있겠다.

📌 의존 관계 주입

public class MenuController {
    private MenuService menuService;

    @Autowired
    public MenuController(MenuService menuService) {
        this.menuService = menuService;
    }

    public List<Menu> getMenus() {
        return menuService.getMenuList();
    }
}

public interface MenuService {
    List<Menu> getMenuList();
}
  • 위 예제에서 MenuController 클래스에 생성자를 통해 인터페이스 MenuService에 의존하도록 주입하였으며, 추상화 된 MenuService를 DI 함으로서 MenuService를 구현한 다양한 클래스와 의존 관계를 맺을수 있고, 실제 구현한 다양한 클래스 와의 의존 관계는 느슨한 관계로서 유지할수 있다.
  • 만약, Test를 위해 테스트용 Stub 객체가 필요하다고 가정하면, 아래 와 같이, MenuService를 구현한 새로운 Stub객체를 정의해주기만 하면 될 뿐, 위 코드에서 변경점은 없고, MenuController 클래스를 가져다 사용할때 Stub 객체를 주입하기만 하면 될 것이다.
public class MenuServiceStub implements MenuService {

    @Override
    public List<Menu> getMenuList() {
        return List.of(
                new Menu(1,"아메리카노",2500),
                new Menu(2,"카라멜 마끼아또",4500),
                new Menu(3,"바닐라 라떼",4500)
        );
    }
}

📌 Why DI ?

  • 위 예제에서와 같이 생성자 파라미터로 객체를 전달하여 외부에서 객체를 주입하기 위한 준비는 끝났다.
  • 외부에서 객체를 주입 해주기 위해 new 키워드를 사용해야될까?
  • 애플리케이션 코드 내부에서 직접적으로 new 키워드를 사용할 경우 객체 지향 설계의 관점에서 문제가 발생할수 있기 때문에 조금이라도 new 키워드 사용을 줄이기 위해 의존 관계 주입의 예제 처럼 인터페이스를 구현한 클래스를 파라미터로 받을수 있도록 하였다.
  • 하지만, MenuController와 MenuService 인터페이스를 구현한 클래스 간의 느슨한 결합으로 유지되고 있을때, MenuController를 사용하기 위해서는 여전히 new 키워드를 사용해야 할것이다.
public class CafeClient {
    public static void main(String[] args) {
        MenuController menuController = new MenuController(new MenuServiceStub());

        List<Menu> menuList = menuController.getMenus();
    }
}
  • POJO 프로그래밍에 반하는 코드이지만, 개념 이해를 하기위한 코드며, 실제 개발에 사용되는 코드는 아니다.
  • 위 코드와 같이 main 메소드는 프로그램 실행 메소드라 가정하고, MenuController을 사용하기 위해서는 인스턴스를 생성해줘야 되며, 외부(CafeClient)에서 객체를 주입 해 주기 위해 new 키워드를 사용해야 한다.
  • new 키워드를 사용하지 않고 어떻게 의존성 주입을 해줄수 있을까?
  • 이때, 주입하고자 하는 객체를 Spring Bean으로 등록을 하고 스프링 컨테이너에서 Bean들을 관리하도록 만들어 주면된다.
// CafeClient.java
public class CafeClient {
    @Test
    public static void main(String[] args) {
        GenericApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

        MenuController menuController = context.getBean(MenuController.class);

        List<Menu> menuList = menuController.getMenus();

        for(Menu a : menuList)
            System.out.println(a);
    }
}

// MenuController.java
public class MenuController {
    private MenuService menuService;

    @Autowired
    public MenuController(MenuService menuService) {
        this.menuService = menuService;
    }

    public List<Menu> getMenus() {
        return menuService.getMenuList();
    }
}

// Config.java
@Configuration
@ComponentScan(basePackageClasses = CafeClient.class)
public class Config {

    @Bean
    public MenuService getMenuService(){
        return new MenuServiceStub();
    }

    @Bean
    public MenuController getMenuController(MenuService menuService){

        return new MenuController(menuService);
    }

}

// MenuServiceStub.java
public class MenuServiceStub implements MenuService {

    @Override
    public List<Menu> getMenuList() {
        return List.of(
                new Menu(1,"아메리카노",2500),
                new Menu(2,"카라멜 마끼아또",4500),
                new Menu(3,"바닐라 라떼",4500)
        );
    }
}

// Menu.java
@Getter
@AllArgsConstructor
@ToString
public class Menu {

    private long id;
    private String name;
    private Integer price;

}
  • Spring Framework의 영역인 Config 클래스에서 MenuController 객체 생성을 정의 해 두었고 이 객체를 Spring의 도움을 받아 CafeClient 에 사용할수 있게 제공하게 된다.

  • 즉 의존성 주입을 Spring에서 대신 해주는 것이다.

  • 스프링 컨테이너는 @Configuration클래스를 자동으로 빈 등록을 하고 클래스를 파싱해서 @Bean이 있는 메소드를 찾아 빈을 생성한다.

  • @Configuration은 수동으로 등록한 Bean이 생성시 싱글톤을 보장해준다.

  • 그렇게 생성된 빈은 CafeClient 클래스에서 getBean 메소드에 의해 호출된다.

profile
컴퓨터가 할일은 컴퓨터가

0개의 댓글