IoC
IoC(Inversion of Control)은 객체의 생성 ~ 생명주기 관리를 컨테이너가 도맡아, 제어권이 컨테이너로 넘어가게 되어서 제어권의 흐름이 바껴 이것을 IoC라고 한다.
IoC 컨테이너는 스프링에서 쓰이는 여러 객체들을 생성, 관리하는 객체 (IoC 컨테이너 관리하는 객체 : Bean이라는 인스턴스 형태로 관리)
IoC가 없다면 Autowired를 통한 의존성 주입을 할 수 없을 것public class ExampleService { private ExampleRepository exampleRepository = new ExampleRepository(); }
👆 ExampleService 에서 new를 통해 의존 객체를 직접 생성
@RequiredArgsConstructor @Service // @Service라는 어노테이션을 이용해 Spring이 Component Scan을 통해 Bean에 등록해줌, IoC // ExampleService가 직접 의존성을 관리하는 것이 아닌, 스프링이 관리 public class ExampleService { // DI private final ExampleRepository exampleRepository; }
👆 의존 객체를 외부에서 주입
Bean으로 등록하는 방법
1. Component Scan (Annotation)
2. XML 또는 Java 설정 파일Component Scan (Annotation)
Spring Boot Project에서 @SpringBootApplication 어노테이션이 @ComponentScan 어노테이션을 포함하고 있다.
@SprinBootConfiguration + @ComponentScan + @EnableAutoConfiguration
SpringBootApplication은 Bean을 2번 등록
처음에 ComponentScan으로 등록 후, EnableAutoConfiguration으로 추가적인 Bean들을 읽어서 등록
@ComponentScan : 해당 패키지에서 @Component 어노테이션을 가진 Bean들을 스캔해서 등록
@EnableAutoConfiguration : Bean을 등록하는 자바 설정 파일. spring.factories 내부에 여러 Configuration들이 있고, 조건에 따라 Bean 등록
@Component, @Repository, @Service, @Controller, @Configuration
: @Component 라는 meta Annotation을 사용한 Annotation으로, Component Annotation
@Repository는 Spring Data JPA가 제공하는 기능으로 @Repository 어노테이션이 없더라도 Bean으로 등록됨
: Interface를 상속받은 객체를 찾아서 그 객체의 구현체를 IoC Container 내부적으로 만들어서 Bean으로 등록해주는 과정 처리
DI
의존 관계 주입이라 하고, 어떤 객체가 사용하는 의존 객체를 직접 생성하는 것이 아니라 주입하는 방법(객체간의 의존성을 자신이 아닌 외부에서 주입)
의존성 주입 방법(생성자 주입 선호!)
@Autowired 라는 어노테이션을 통해 (자동으로 의존성 주입)
- Field, Setter, Constructor 등 다양한 곳에 사용할 수 있지만, Bean으로 등록되어있지 않으면 Error가 발생하게 됨(No qualifying bean), 또한 순환참조 방지가 불가능하다
- 필드 주입(Field Injection)
@Service public class ExampleService { // 순환참조 @Autowired private PracticeService practiceService; }
@Service public class PracticeService { // 순환 참조 @Autowired private ExampleService exampleService; }
cf) 수정자 주입 방법도 존재
- 수정자 주입(Setter Injection)
@Service public class PracticeService { private ExampleService exampleService; // setter에 @Autowired @Autowired public void setExampleService(ExampleService exampleService){ this.exampleService = exampleService; } }
→ @Autowired를 사용하면, 아무런 오류 없이 정상 구동이 되지만, 순환 참조되는 메서드 실행 시, 오류와 함께 종료가 된다
- 생성자 주입(Constructor Injection)
@RequiredArgsConstructor @Service public class ExampleService { private final ExampleService exampleService; }
@RequiredArgsConstructor @Service public class ExampleService { private final PracticeService practiceService; }
→ 생성자 주입 방법(RequiredArgsConstructor를 이용)
BeanCurrentlyInCreationException
예외가 발생해 어플리케이션이 구동도 되지 않는다 → 사전에 오류 처리 가능
- 실행결과에 차이가 발생하는 이유로는 생성자 주입 방법은 필드와 수정자 주입과 Bean을 주입하는 순서가 다르다
- 생성자 주입 방법은 둘과는 달리, 먼저 빈을 생성하지 않고, 생성 자의 인자에 사용되는 빈을 찾거나 빈 팩터리에서 만든다. 그 후 찾은 인자 빈으로 주입하려는 빈의 생성자를 호출한다. → 객체 생성 시점에 빈을 주입하기 때문에, 서로 참조하는 객체가 생성되지 않은 상태에서 그 빈을 참조하기 때문에 오류가 발생한다.
테스트 코드 작성이 편리
- DI의 핵심은 관리되는 클래스가 DI 컨테이너에 의존성이 없어야 한다는 것. 독립적으로 인스턴스화 가능한 POJO(Plain Old Java Object) 이어야 한다. DI 컨테이너를 사용하지 않고서도 단위 테스트에서 인스턴스화 할 수 있어야 한다.
불변성 : final
- 한번 주입받은 Reference가 다른 Reference로 바뀌지 않도록 보장하기 위함