ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [01] Inversion of Control(IoC) 컨테이너와 Bean
    Spring/Spring 핵심 기술 2020. 4. 12. 11:18
    반응형

    Inversion of Control에 대한 개념을 확인하고자 wiki 검색 결과는 다음과 같습니다.


    제어 반전, 제어의 반전, 역제어는 프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴을 말한다. 줄여서 IoC(Inversion of Control)이라고 부른다. 전통적인 프로그래밍에서 흐름은 프로그래머가 작성한 프로그램이 외부 라이브러리의 코드를 호출해 이용한다. 하지만 제어 반전이 적용된 구조에서는 외부 라이브러리의 코드가 프로그래머가 작성한 코드를 호출한다. 설계 목적상 제어 반전의 목적은 다음과 같다:

    • 작업을 구현하는 방식과 작업 수행 자체를 분리한다.
    • 모듈을 제작할 때, 모듈과 외부 프로그램의 결합에 대해 고민할 필요 없이 모듈의 목적에 집중할 수 있다.
    • 다른 시스템이 어떻게 동작할지에 대해 고민할 필요 없이, 미리 정해진 협약대로만 동작하게 하면 된다.
    • 모듈을 바꾸어도 다른 시스템에 부작용을 일으키지 않는다.

    보통은 사용자가 시스템의 API 등을 호출하여 프로그램을 작성합니다만 이 IoC는 사용자의 코드를 시스템에서 호출한다고 합니다. studying 하면서 느낀 Spring에서의 Inversion of Control은 다음과 같았습니다.

     

    Spring Framework에서는 사용자가 Bean을 필요로 할 때 Framework 정확히는 Application Context가 그것을 만들어 제공해 줍니다. 이것을 Dependency Injection(의존성 주입)이라 하며, Bean과 Bean 사이에 의존관계가 있을 경우 다시 말해 하나의 Bean을 생성하는데 다른 Bean이 사용되는 경우 Bean을 만드는데 필요한 다른 Bean을 사용자 코드에서 직접 생성하지 않고 Framework에서 생성하여 사용자 코드에서 바로 사용할 수 있도록 하는 방법입니다.

     

    Spring Framework에서 Inversion of Control 기능을 제공하는 것은 Application Context입니다.

    • Application Context는 ApplicationContext interface 구현체를 구현하여 생성합니다.
    • ApplicationContext interface 중 Bean에 대한 관리와 컨테이너 생성 기능은 BeanFactory를 상속받아 사용합니다.
    • Bean 설정 file에 정의된 Bean의 설계를 따라 Bean을 구성하고 제공합니다.

     

    ApplicationContext는 BeanFactory의 기능 외에도 Environment profile과 property, 메시지 소스 처리, 이벤트 발생, 리소스 로딩 등 여러 기능을 제공하고 있습니다.

     

    Bean이란 Spring IoC 컨테이너가 관리하는 객체로 여러가지 scope로 생성할 수 있습니다.. default scope는 singleton scope입니다. 

        scope의 종류

            1. Singleton: 하나의 객체만 생성하여 계속 기능제공

            2. ProtoType: 기능 제공을 위해 매번 다른 객체를 생성

     

    Spring Framework에서는 Bean 생성 부터 소멸까지 진행되는 life cycle 추가적인 작업이 필요할 때를 대비해 life cycle interface를 제공하고 있습니다. 미리 정의된 어노테이션을 통해 특정 life cycle 구간에 원하는 작업을 있습니다. 예를 들어 method @PostConstruct 어노테이션을 붙여주면 Bean 생성 직후에 사용자가 원하는 작업을 있습니다. (자세한 내용은 BeanFactory 관한 레퍼런스를 확인)

     

    @PostConstruct 어노테이션 사용의 예는 다음과 같습니다.

    @Service
    public class BookService {
    	private BookRepository bookRepository;
        
        public BookService(BookRepository bookRepository) {
        	this.bookRepository = bookRepository;
    	}
        
        public Book save(Book book) {
        	book.setCreated(new Date());
            return bookRepository.save(book);
        }
        
        @PostConstruct
        public void postConstruct() {
        	System.out.println("==================================");
            System.out.println("BookService bean was created.");
        }
    }

     

     

    ApplicationTests.java에 테스트 코드를 작성하여 위의 코드를 확인합니다. (테스트 코드를 작성하는 것은 견고한 소프트 작성에 반드시 필요한 작업입니다. 완벽한 TDD를 하지 않더라도 단위 테스트 정도는 진행해 주는 것이 좋습니다.)

    테스트 코드는 다음과 같습니다.

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ApplicationTests {
    	@Test
        public void contextLoads() {
        
        }
    }

     

    위의 테스트 코드를 실행하면 다음과 같이 Bean Life Cycle Bean 생성 직후에 원하는 작업이 진행 것을 확인할 있습니다.

    테스트코드 실행 결과

     

     Bean 사용하는 이유

    1. 의존성 주입은 Bean간에만 가능하기 때문에 Bean 사용합니다.
    2. Singleton scope으로 객체를 만들어 사용하기 위해. (Bean 기본적으로 singleton scope)

     

    IoC 컨테이너를 사용하는 이유

    1. Life Cycle Interface Callback 통해 Famework에서 실행될 부가적인 기능들을 원하는 life cycle에 구현하기 위해 사용합니다.

     

     

    *객체(Bean)을 생성되어 있지 않으면 진행할 수 없는 Test가 있을 경우 가짜객체(Mock)를 통해 해결합니다..

     

    1. BookRepository.java의 기능이 아직 완성되지 않아 null return하도록 되어 있습니다.   

    @Repository
    public class BookRepository {
    	public Book save(Book book) {
        	return null;
        }
    }

     

    2. BookService는 BookRepository로 부터 Book을 받아 처리해야하는 상태이지만 위의 코드와 같이 아직 기능을 제공하고 있지 않습니다. 이런 경우 BookService 기능을 확인하기 위해 다음과 같이 가짜 객체를 생성하여 테스트를 진행할 있습니다.

    package me.dave.springapplicationcontext;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mock;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.mockito.Mockito.when;
    
    @RunWith(SpringRunner.class)
    public class BookServiceTest {
    
        @Mock
        MyBookRepository myBookRepository;  //@Mock 어노테이션으로 가짜 객채 생성
    
        @Test
        public void save() {
            Book book = new Book();
    
            when(myBookRepository.save(book)).thenReturn(book);   //가짜 객체를 mocking
    
            BookService bookService = new BookService(myBookRepository);
    
            Book result = bookService.save(book);
    
            assertThat(book.getCreated()).isNotNull();
            assertThat(book.getBookStatus()).isEqualTo(BookStatus.DRAFT);
            assertThat(result).isNotNull();
        }
    }

     

     

    댓글

Designed by Tistory.