CS 전공지식

24.02.22 Spring IoC 컨테이너 (Inversion of Control)

김용글 2024. 2. 22. 15:23

1. IoC (제어반전)

    - 객체의 생성, 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미

    - 컴포넌트 의존관계 설정, 설정 및 생명주기를 해결하기 위한 디자인 패턴

 

2. IoC 컨테이너

    - 스프링 프레임워크도 객체를 생성하고 관리하고 책임지고 의존성을 관리해주는 컨테이너가 있는데

      이것이 바로 IoC 컨테이너(=스프링 컨테이너) 이다

    * 컨테이너 : 보통 객체의 생명주기를 관리, 생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 것

    - 인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 컨테이너가 대신 해줌

    - 객체 관리 주체가 프레임워크(Container)가 되기 때문에 개발자는 오직 로직에 집중할 수 있는 장점

  1) IoC 컨테이너는 객체의 생성을 책임지고 의존성을 관리

  2) POJO 의 생성, 초기화, 서비스, 소멸에 대한 권한을 가짐

  3) 개발자들이 직접 POJO 를 생성할 수 있지만 컨테이너에게 맡김

  4) 개발자는 비지니스 로직에 집중할 수 있따

  5) 객체 생성 코드가 없으므로 TDD 용이

  * POJO (Plain Old Java Object) : 주로 특정 자바 모델이나 기능, 프레임워크를 따르지 않는 Java Object

                                                       Java Bean 이 대표적이고 간단하게 Getter / Setter 를 생각하면 됨

 

3. IoC 의 분류

  1) DL (Dependency Lookup)

       - 저장소에 저장되어 있는 Bean 에 접근하기위해 컨테이너가 제공하는 API 를 이용해 Bean 을 Lockup 하는 것

  2) DI (Dependency Indjection)

       - 각 클래스 간의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것

  3) DL 사용시 컨테이너 종속이 증가하기 때문에 주로 DI 를 사용 함

     (1) Bean

           - Spring 에서는 스프링컨테이너에 객체들을 생성하면 객체끼리 의존성을 주입(DI) 하는 역할을 해주고

             스프링 컨테이너에 등록한 객체들을 Bean 이라고 함

 

             A) 컴포넌트 스캔과 자동 의존관계 설정

                  - 스프링 부트에서 사용자 클래스를 스프링 빈으로 등록하는 가장 쉬운 방법은

                    클래스 선언부 위에 @Component 어노테이션을 사용하는 것

                  - @Controller, @Service, @Repository 는 모두 @compoenet 를 포함하고 있고,

                    해당 어노테이션으로 등록된 클래스들은 스프링 컨테이너에 의해 자동으로 생성되어 스프링 빈으로 등록됨    

 

             B) 자바 코드로 직접 스프링 빈 등록 

                  - 수동으로 스프링 빈을 등록하려면 자바 설정 클래스를 만들어 사용해야 함

                  - 설정 클래스를 만들고 @Configuration 어노테이션을 클래스 선언부에 추가

                  - 특정 타입을 리턴하는 메소드를 만들고 @Bean 어노테이션을 붙여주면 자동으로 해당 타입의

                    빈 객체가 생성됨

                  - MemberRepository 는 인터페이스고 MemoryMemberRepository 가 구현체이기 때문에

                    MemoryMemberRepository 를 new 해준다

                  - MemberRepository 를 변경해야할 경우 @Configuration 에 등록된 @Bean 만 수정해주면 되므로 수정이 용이

                    A) 의 방법은 일일이 변경해줘야 함

 

             C) @Bean 어노테이션의 주요 내용은 아래와 같다

                  - @Configuration 설정된 클래스의 메소드에서 사용가능

                  - 메소드의 리턴 객체가 스프링 빈 객체임을 선언

                  - 빈의 이름은 기본적으로 메소드의 이름

                  - @Bean(name="name") 으로 이름 변경 가능

                  - @Scope 를 통해 객체 생성을 조정할 수 있다

                  - @Component 어노테이션을 통해 @Configration 없이도 빈 객체를 생성할 수 있다

                  - 빈 객체에 init( ), destory( ) 등 라이프사이클 메소드를 추가한 다음 @Bean 에서 지정할 수 있다

 

             D) 등록된 빈을 @Autowired 로 사용하기

                  - 스프링부터의 경우 @Component, @Service, @Controller, @Repository, @Bean, @Configuration 등으로

                    빈들을 등록 후 필요한 곳에서 @Autowire 를 통해 의존성 주입을 받아 사용하는 것이 일반적

 

     (2) 의존성 주입 방법

           - 스프링은 @Autowired 어노테이션을 이요한 다양한 의존성 주입(DI) 방법을 제공

           - 의존성 주입은 필요한 객체를 직접 생성하는 것이 아닌 외부로부터 객체를 받아 사용하는 것

           - 객체간의 결합도를 줄이고 코드의 재활용성을 높일 수 있다

           - @Autowired 는 스프링에게 의존성을 주입하는 지시자 역할로 쓰임

 

             A) 의존성을 주입해야 하는 이유

                  - Test 가 용이해짐

                  - 코드의 재사용성을 높여줌

                  - 객체간의 의존성(종속성)을 줄이거나 없앨 수 있다

                  - 객체간의 결합도를 낮추면서 유연한 코드를 작성할 수 있다

 

             B) 생성자 주입 (Constructor Injection)

                  - 클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체가 빈으로 등록되어 있다면

                    @Autowired 를 생략할 수 있다

@Controller
public calss CocoController {
    //final 을 붙일 수 있음
    	private final CocoService cocoService;
    //----------------------------------------------
    //@Autowired
    	public CocoController(CocoService cocoservice) {
        	this.cocoService = cocoservice;
        }
    }

 

             C) 필드 주입 (Method  Injection)

                  - 필드에 @Autowired 어노테이션만 붙여주면 자동으로 의존성이 주입됨

                  - 사용법이 매우 간단하므로 가장 많이 접할 수 있는 방법

                  - 코드가 간걸하지만 외부에서 변경하기 힘들고, 프레임워크에 의존적이고 객체지향적으로 좋지않은 단점

@Controller
public class CocoController {
	
    @Autowired
    private CocoService cocoService;
}

  

             D) 수정자 주입 (Setter Injection)

                  - Setter 메소드에 @Autowired 어노테이션을 붙이는 방법

                  - setXXX 메서드를 public 으로 열어두어야 하기 때문에 언제 어디서든 변경이 가능

@Controller
public class CocoController {
	private CocoService cocoService;
    
    @Autowired
    public void setSocoService(CocoService cocoService){
    	this.cocoSErvice = cocoService;
    }
}

 

             E) 생성자 주입 (Constructor Injection) 을 권장하는 이유

                 (A) 순환 참조 방지 가능

                       - 개발을 하다보면 여러 컴포넌트 간의 의존성이 생김

                       - 예) A 가 B 를 참조하고 B 가 다시 A 를 참조하는 순환 참조되는 코드가 있다고 가정

@Service
public class AService {

	// 순환참조
    @Autowired
    private BService bservice;
    
    public void HelloA() {
    	bService.HelloB();
    }
}



@Service
public class BService {

	// 순환참조
    @Autowired
    private AService aservice;
    
    public void HelloB() {
    	bService.HelloA();
    }
}

                       - 필드 주입과 수정자 주입은 빈이 생성된 후에 참조를 하기 때문에 아무런 오류 및 경고 없이

                         구동되는데 그것은 실제 코드가 호출될 때 까지 문제를 알수 없다는 이야기

                       - 생성자를 통해 주입하고 실행하면 BeanCurrentlyInCreationException 이 발생

                       - 순환참조 뿐만 아니라 더 나아가서 의존 관계에 내용을 외부로 노출 시킴으로 어플리케이션을

                         실행하는 시점에서 오류를 체크할 수 있다

 

                 (B) 불변성

                       - 생성자로 의존성을 주입할 때 final 로 선얼 할 수 있고, 이로 인해 의존성을 주입받는

                         객체가 변할 일이 없어지게 됨

                       - 수정자 주입이나 일반 메소드 주입을 사용하게 되면 불필요하게 수정의 가능성을 열어두게 되고 이는                                 OOP 5가지 원칙중 OCP(Open-Closed Principal) 개방 폐쇄의 원칙을 위반하게 됨         

@Controller
public calss CocoController {

    	private final CocoService cocoService;

    	public CocoController(CocoService cocoservice) {
        	this.cocoService = cocoservice;
        }
    }

 

                 (C) 테스트에 용이

                       - 생성자 주입을 사용하게 되면 테스트코드를 좀 더 편리하게 작성 가능

                       - 독립적으로 인스턴스화가 가능한 POJO(Plain Old Java Object) 를 사용하면 DI 컨터이너 없이도

                         의존성을 주입하여 사용 가능

                       - 코드 가독성이 높아지고 유지보수가 용이하고 테스트의 격리성과 예측 가능성을 높일 수 있다는

                         장점이 생김

public class CocoService {
	private final CocoRepository cocoRepository;
    
    public CocoService(CocoRepository cocoRepository) {
    	this.cocoRepository = cocoRepository;
    }
    
    public void doSomething() {
    	// do Something with cocoRepositroy
    }
}

public class CocoServiceTest {
	CocoRepository cocoRepository = new CocoRepository();
    CocoService cocoService = new CocoService(cocoRepository);
    
    @Test
    public voic testDoSomething() {
    	cocoService.doSomething();
    }
}

 

4. 스프링 컨테이너 (=IoC 컨터이너)의 종류

    - 스프링 컨테이너가 관리하는 객체를 빈(Bean) 이라하고 이 빈들을 관리한다는 의미로 컨테이너를

      빈 팩토리(BeanFactory) 라고 부름

 

     (1) BeanFactory

           - BeanFactory 계열의 인터페이스만 구현한 클래스는 단순히 컨테이너에서 객체를 생성하고 DI 를

             처리하는 기능만 제공

           - Bean 을 등록, 생성, 조회, 반환 관리를 함

           - 팩토리 디자인 패턴을 구현한 것으로 BeanFactory 는 빈을 생성하고 분배하는 책임을 지는 클래스

           - Bean 을 조회할 수 있는 getBean( ) 메소드가 정이됨

           - 보통은 BeanFactory 를 바로 사용하지 않고 이를 확장한 ApllicationContext 를 사용

 

     (2) ApllicationContext 

           - Bean 을 등록, 생성, 조회, 반환 하는 기능은 BeanFactory 와 동일

           - 스프링의 각종 부가 기능을 추가로 제공

           - 국제화가 지원되는 텍스트 메세지 관리

           - 이미지 같은 파일 자원을 로드할 수 있는 포괄적 방법 제공

           - 리스너로 등록된 빈에게 이벤트 발생을 알려줌