24.02.22 Spring IoC 컨테이너 (Inversion of Control)
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 와 동일
- 스프링의 각종 부가 기능을 추가로 제공
- 국제화가 지원되는 텍스트 메세지 관리
- 이미지 같은 파일 자원을 로드할 수 있는 포괄적 방법 제공
- 리스너로 등록된 빈에게 이벤트 발생을 알려줌