[Spring 기본] Spring의 객체 지향 IoC, DI, Bean, Container

2022. 10. 13. 15:35·Spring/Spring 기본
728x90
반응형

지난 포스트에서는 Spring의 도움을 받지 않고 순수한 Java 를 통해서 객체 지향을 위한 설계를 해봤습니다. 이번 포스트에서는 [Spring 기본] 포스트들의 목적에 맞게 Spring이 이런 설계를 도와주기 위해 어떤 지원을 해주는지 살펴보겠습니다. 스프링 짱짱맨..

 

 

 

 

 

IoC (Inversion Of Control) 

 

 

IoC 란 어떤 지원되는 기능, 기술을 말하는 것이 아니라, 설계적인 측면의 용어라고 생각하시는게 편합니다. Spring 한정으로 사용되는 용어가 아니라, 메소드 혹은 객체의 호출 작업을 개발자 코드에서 결정되는 것이 아닌, 외부에서 프레임워크 따위가 내 코드를 호출해 주는 구조를 말합니다. 이렇게 제어를 하는 권한이 뒤바뀐다고 해서 IoC, 제어의 역전이라고 부릅니다. 

 

지난 포스트에서 설계한 예제 클래스로 계속 살펴보겠습니다. 기존에는 다음과 같이 개발자가 직접 구현 객체를 호출하여 사용 시점을 제어할 수 있었습니다. 

 

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
  	...
}

 

하지만 App Configuration 클래스가 도입되어서 직접 구현 객체를 호출하지 않았음에도 외부에서 구현 객체가 주입된 것을 확인할 수 있었습니다. 또한, 호출하는 클라이언트는 구현 객체가 어떤 객체인지도 모르는 상태입니다. 이런 모습을 IoC, 제어의 역전이라고 합니다. 보통 프레임워크에서 나의 코드들을 가지고 앱을 동작시키기 때문에 IoC 형태를 자주 확인할 수 있습니다. 

 

 

 

DI (Dependency Injection)

 

 

지난 포스트에서 잠깐 등장하였든 DI, 의존성 주입이란 클라이언트에서 사용하는 역할 객체에 대한 구현 객체를 외부에서 설정해주는 방식을 말합니다. DI 를 사용하면 구현 객체에 의존하지 않고도 내부 로직을 수행할 수 있어, DIP와 OCP를 지킬 수 있게 됩니다. 

 

 

정적인 의존관계와 동적인 의존관계

 

정적 의존관계 : 코드만을 보고도 어떤 의존관계인지 알 수 있는 관계를 말합니다. 어떤 인터페이스에 의존하는지, 어떤 구현 객체에 의존하는지 바로 알 수 있는 관계를 말합니다. 
동적 의존관계 : 앱이 실행되면서 동적으로 바뀌는, 외부에서 구현 객체를 생성하고 전달하여 의존관계가 형성되는 관계를 말합니다. 

 

DI를 통해 설정을 해주면, 어떤 인터페이스를 사용할지는 정적 의존관계이고, 어떤 구현 객체를 담을지는 동적 의존관계로 형성 시켜줄 수 있습니다. 따라서 동적인 의존관계, 사용하고자 하는 구현 객체를 쉽게 바꿔줄 수 있다는 장점이 있습니다. 

 

 

 

IoC Container 와 Bean

 

 

IoC Containern (DI Container, Spring Container)

 

Spring 에서 IoC 를 해주는, DI를 해주는 공간을 Container 라고 부릅니다. Spring의 핵심 컴포넌트로, 개발자가 작성한 메타 데이터에 따라 내부 Bean 들을 관리해주는 공간입니다. 

 

 

Bean

 

Bean 이란 Spring Container 에서 Singleton 패턴으로 관리해주는 객체들을 말합니다. 즉, 개발자가 직접 생성하여 관리하는 것이 아닌, Container 를 통해서 개발자가 설정만 해주고, Spring에서 필요 시점에 생성해서 관리하는 객체를 Bean 이라고 합니다. Spring에서 자체적으로 사용되는 여러가지 Bean들도 관리하며, 개발자가 앱 구동을 위해 설정해준 Bean 들도 같이 관리됩니다. 

 

쉽게 말하자면, 사용하려는 인터페이스에 대한 구현 객체를 설정해 놓으면, 그 객체가 Bean 으로 Container에 등록이 되어서 Spring Container 에 보관된다고 보시면 됩니다. 이는 [Bean 파헤치기] 포스트에서 더 자세하게 확인하실 수 있습니다. 

 

 

 

 

 

... 그래서 뭐가 도대체 뭐라는거냐? 하실 수 있습니다. 이론 설명이 좀 길었는데, 이제부터 지난 포스트에서 설정해준 모델들을 Spring을 통해서 관리될 수 있도록 바꿔보겠습니다.

 

 

다음과 같이 AppConfig 같은 파일을 Configuration 어노테이션 (@Configuration) 을 통해서 등록할 수 있고, 그 내부적으로 Spring Container 에서 Singleton 으로 관리가 필요한 선언 설정들을 Bean 어노테이션(@Bean) 을 통해서 등록할 수 있습니다.

 

@Configuration
public class AppSpringConfig {

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        return new FixDiscountPolicy();
    }

    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(),discountPolicy());
    }

}

 

이전에 봤던 AppConfig Container 를 Spring 화하여 AppSpringConfig.class 로 만들었습니다. 이를 @Configuration 을 달고 내부 관리 설정들에 @Bean 을 달아준다면, 이 모든 객체들, Bean 들을 Spring Container 에서 관리할 것이라는 뜻입니다.

 

 

사용 예시로, memberService를 사용할 공간에서 이 앱 Spring Container 에 (실행시점에) 등록된 Service 를 사용하는 모습을 확인해봅시다. ApplicationContext 란 Spring Container 를 말합니다.

 

public static void main(String [] args){
	
    // 중앙 Container 를 불러오고, 사용할 Configuration 파일을 등록
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppSpringConfing.class);
    
    // 사용할 MemberService 를 가져옴 (AppSpringConfig 에 등록된 모습으로)
    MemberService memberService = ac.getBean("memberService", MemberService.class);
    
    // Config에 설정된 Service 의 내부 로직들을 사용할 수 있음
    memberService.saveMember(memberA);
}

 

순서대로 설명을 붙이자면, 실행되면서 Spring Container (위 ApplicationContext 공간) 에 넣을 Bean 들을 주입해주는데, 이를 AppSpringConfig.class 에서 가져옵니다. 그 다음으로 등록된 Bean 중 method 명이 "memberService" 인 Bean 이며, 반환 객체는 MemberService.class 인 Bean 을 가져오라고 요청하는 모습입니다. 

 

이 시점에서 클라이언트는 바로 main method 이고, 서버는 MemberService 역할 Interface 라는 점을 생각해본다면, 사용되는 MemberServiceImpl.class 구현 객체는 AppSpringConfig.class 에 설정이 되어 있습니다. 만약 사용하는 service 를 바꾸고 싶다면, 해당 main method 에서는 바꿀 것이 아무것도 없는데, 이는 DIP, OCP 를 잘 지켜줬기 때문입니다. 

 

 

다른 예제도 살펴보겠습니다. 

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppSpringConfig.class);

@Test
@DisplayName("이 빈이 저 빈이냐?")
void test0(){

    MemberRepository memberRepository = ac.getBean(MemberRepository.class);
    Assertions.assertThat(memberRepository).isInstanceOf(MemoryMemberRepository.class);
}

 

다음과 같이 테스트를 돌려보면, 분명히 MemberRepository.class 의 Bean 객체를 반환해달라고 요청하는 모습을 볼 수 있습니다. 하지만 Spring Container에는 구현 객체가 등록되어 관리되고 있기 때문에, Configuration에서 구현 객체로 등록된 MemoryMemberRepository 객체가 반환되는 모습을 확인할 수 있습니다. 

 

 

 

어떤 Bean 들이 등록되고, 관리되는지 한번 확인하려면 다음과 같이 찍어볼 수 있습니다. 

 

@Test
@DisplayName("등록되는 모든 Bean을 살펴보자")
void test1(){
    String [] beanNames = ac.getBeanDefinitionNames();

    for(String beanName : beanNames){
        Object bean = ac.getBean(beanName);
        System.out.println("beanName = " + beanName + ":: beanObj = " + bean);
    }
}

Result

beanName = org.springframework.context.annotation.internalConfigurationAnnotationProcessor:: beanObj = org.springframework.context.annotation.ConfigurationClassPostProcessor@78365cfa
beanName = org.springframework.context.annotation.internalAutowiredAnnotationProcessor:: beanObj = org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@64a8c844
beanName = org.springframework.context.annotation.internalCommonAnnotationProcessor:: beanObj = org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@3f6db3fb
beanName = org.springframework.context.event.internalEventListenerProcessor:: beanObj = org.springframework.context.event.EventListenerMethodProcessor@52de51b6
beanName = org.springframework.context.event.internalEventListenerFactory:: beanObj = org.springframework.context.event.DefaultEventListenerFactory@18c5069b
beanName = appSpringConfig:: beanObj = com.example.basicprinciple.config.AppSpringConfig$$EnhancerBySpringCGLIB$$5d3669f8@3a0d172f
beanName = memberRepository:: beanObj = com.example.basicprinciple.member.MemoryMemberRepository@68ad99fe
beanName = memberService:: beanObj = com.example.basicprinciple.member.MemberServiceImpl@485e36bc
beanName = discountPolicy:: beanObj = com.example.basicprinciple.discount.RateDiscountPolicy@781f10f2
beanName = orderService:: beanObj = com.example.basicprinciple.order.OrderServiceImpl@2a79d4b1

 

실행시 Spring Container 에 관리중인 Bean 들이 Log에 찍히는 모습을 확인할 수 있는데, Spring 에서 자체적으로 생성하는 bean 들과, 내가 @Bean 으로 등록한 객체들이 생성되는 것을 확인할 수 있습니다.

 

참고로 Spring 내부에서 사용하는 Bean 들은 Role_Infrastructure Bean, 직접 Configuration을 통해 개발자가 등록한 Bean들은 Role_Application Bean으로 분류합니다. 내가 생성한 Bean 만 보고 싶으면 다음과 같이 호출해볼 수 있습니다. 

 

@Test
@DisplayName("내 Bean 들만 살펴보자")
void myBeanTest(){

    String[] beanNames = ac.getBeanDefinitionNames();

    for (String name : beanNames) {

        BeanDefinition beanDef = ac.getBeanDefinition(name);
        if (beanDef.getRole() == BeanDefinition.ROLE_APPLICATION) {
            Object myBean = ac.getBean(name);
            System.out.println("myBean = " + myBean);
        }
    }
}

Result

myBean = com.example.basicprinciple.config.AppSpringConfig$$EnhancerBySpringCGLIB$$5d3669f8@5e8ac0e1
myBean = com.example.basicprinciple.member.MemoryMemberRepository@aafcffa
myBean = com.example.basicprinciple.member.MemberServiceImpl@6955cb39
myBean = com.example.basicprinciple.discount.RateDiscountPolicy@235a0c16
myBean = com.example.basicprinciple.order.OrderServiceImpl@2b5f4d54

 

내가 직접 등록한 Bean 들만 호출되는 모습을 확인할 수 있습니다. 이번 포스트에선 조금 전체적으로 한번 훑어보았고, 앞으로 더 자세하게 Bean 의 관리에 대해서 살펴볼 것입니다. 감사합니다!

 

 

 

포스트 요약

 

  • IoC란 프레임워크에서 많이 보이는 설계 구조로, 호출에 대한 제어권을 시스템이 가지고 있는 모습이다.
  • DI란 외부에서 사용할 구현 객체를 등록해주는 모습을 말하며, 의존성 주입이라 한다.
  • Container 란 Spring 에서 DI 대상인 Bean 들을 관리하는 공간을 말한다

 

 

출처

 

 

[스프링 기본]으로 엮인 모든 포스트들은 교육 사이트 인프런의 지식공유자이신 김영한님의 [스프링 핵심 원리] 강의를 기반으로 작성되었습니다. 열심히 정리하고 스스로 공부하기 위해 만든 포스트이지만, 제대로 공부하고 싶으시면 해당 강의를 꼭 들으시는 것을 추천드립니다. 

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

728x90
반응형

'Spring > Spring 기본' 카테고리의 다른 글

[Spring 기본] Component Scan을 통한 Bean 자동화 관리  (0) 2022.10.17
[Spring 기본] Spring Container의 Singleton 전략  (0) 2022.10.14
[Spring 기본] Container와 Bean과 조금 더 친해져보자  (0) 2022.10.13
[Spring 기본] 앱 기본 설계, Config 클래스 및 주입의 시작  (0) 2022.10.13
[Spring 기본] Spring을 시작하며  (0) 2022.10.12
'Spring/Spring 기본' 카테고리의 다른 글
  • [Spring 기본] Spring Container의 Singleton 전략
  • [Spring 기본] Container와 Bean과 조금 더 친해져보자
  • [Spring 기본] 앱 기본 설계, Config 클래스 및 주입의 시작
  • [Spring 기본] Spring을 시작하며
문케이크
문케이크
    반응형
  • 문케이크
    누구나 개발할 수 있다
    문케이크
  • 전체
    오늘
    어제
    • 전체 보기 (122)
      • CS 이론 (13)
        • 운영체제 (8)
        • 네트워크 (2)
        • 알고리즘 (0)
        • Storage (3)
      • Spring (26)
        • Spring 기본 (12)
        • Spring 심화 (0)
        • JPA (11)
        • Spring Security (3)
      • 리액티브 (0)
        • RxJava (0)
      • SW 설계 (14)
        • OOP (0)
        • UML (3)
        • OOAD (0)
        • Design Pattern (11)
      • Java (8)
      • 웹 운영 (17)
        • AWS (15)
        • 운영 구축 (2)
      • Testing (3)
        • Unit (3)
      • Extra (3)
        • API 적용 (1)
      • 인프라 기술 (5)
        • Kubernetes (2)
        • Elasticsearch (3)
      • Logging (7)
        • Spring (5)
        • 인프라 (2)
      • 일상 (2)
        • 음식점 리뷰 (2)
        • Extra (0)
  • 블로그 메뉴

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

  • 공지사항

    • 문케이크의 블로그
  • 인기 글

  • 태그

    decorator
    composition
    elasticsearch
    JPA
    runtime exception
    Design Pattern
    김영한
    Configuration
    GoF
    analyzer
    spring boot
    Spring
    di
    lombok
    OOAD
    n+1
    단위테스트
    객체지향
    mockito
    Composite
    junit
    BEAN
    spring container
    lazy loading
    Java
    디자인 패턴
    Setter
    OOP
    k8s
    SRP
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
문케이크
[Spring 기본] Spring의 객체 지향 IoC, DI, Bean, Container
상단으로

티스토리툴바