모든 종류의 작업을 사용하는 쪽에서 제어하는 구조를 거꾸로 뒤집는 것입니다.
제어권을 상위 템플릿 메소드에 넘기고 자신은 필요할 때 호출 되어 한다는 개념입니다.
IOC가 적용된 대표 기술이 프레임워크이며, 스프링 프레임워크로 예로 들면 Controller,Service 같은 객체들의 동작을 우리가 직접 구현 하지만, 해당 객체들이 어느 시점에 호출될지는 개발자가 제어하지 않는다. 프레임 워크가 해당 객체들을 갖고 생성,호출,소멸 시킨다.
이러한 과정을 개발자의 제어권이 역전됐다 라고 볼수 있습니다.
IOC를 적용하면 객체를 클래스 내부에서 직접 생성하여 사용하지 않고, 미리 생성해놓은 객체를 주입 받아 사용하면된다.
스프링 쓰기 전엔 개발자가 프로그램의 흐름을 제어해서 일일이 다 설정해줘야 했지만,
스프링에서는 프로그램의 흐름을 프레임워크가 주도한다. 객체(빈)의 생성부터 생명 주기 관리를 컨테이너가 다 해준다.
즉 제어권이 컨테이너로 넘어가게 되기에 이것을 제어권의 흐름이 바뀌었다고 해서 IOC이라고 합니다.
제어권이 컨테이너로 넘어오면서 DI,AOP등이 가능해진것입니다.
그래서 스프링이 실행될 때 모든 의존성 객체를 다 만들어주고 필요한 곳에 주입시킨다.
💡 Bean들은 싱글톤 패턴의 특징을 가지며, 제어의 흐름을 스프링에게 맡겨 작업을 처리하게 된다.의존하는 대상 B가 변하면, 이게 A에 영향이 간다는 뜻이다.
필드에 직접 어노테이션을 붙혀 주입하는 방식
public class Main{
public static void main(String[] args){
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
}
}
Constructor Injection
순수 자바
public class Controller{
private Service service;
public Controller(Service service){
this.service = service;
}
}
public class Service {}
public class Main{
public static void main(String[] args){
Controller controller = new Controller(new Service());
Controller controller2 = new Controller(null);
}
}
프레임워크 사용
public class Main{
public static void main(String[] args) {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public Main(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
}
순수 자바
public class Controller{
private Service service;
public void setService(Service service){
this.service = service;
}
public void servicePrint(){
service.print();
}
}
public class Service{
public void print(){
System.out.println("todo");
}
}
public class Main{
public static void main(String[] args){
Controller controller = new Controller();
controller.setService(new Service());
controller.servicePrint();
Controller controller2 = new Controller();
controller2.servicePrint(); // NullPointerException
}
}
프레임워크 사용
public class Main{
public static void main(String[] args) {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy= discountPolicy;
}
}
}
프로젝트 규모가 커지게 되면 의존성 주입을 서로 하게 되면서 순환 참조가 일어나게 된다.
예제 코드
@Service
@AllArgsConstructor
public class TestService1{
private TestService2 testService2;
}
@Service
@AllArgsConstructor
public class TestService2{
private TestService1 testService1;
}
이렇게 생성자 주입으로 서로 순환 참조를 하도록 설정하고 스프링 실행하면 이런 오류가 발생한다.
Error creating bean with name 'testService1' : Requested bean is currently in creation: Is there an unresolvable circular reference?
sertter 주입은 문제 없이 되어서 정상적으로 동작되는 것처럼 보인다.
그래서 나중에 버그가 발생해도 찾기 힘든 문제로 나타날수도 있다.
스프링 공식 사이트에서 생성자 기반 주입을 옹호한다고 합니다.
의존성과 설정 값을 생성자 주입으로 해야하는 이유는
항상 생성자 주입으로 하다가 필수 값이 아닌 경우에만 setter 주입 방식을 옵션으로 부여하면 된다.
주석 해석하면 실제 타깃에 Autowired가 붙은 빈을 주입하는 것은 BeanPostProcessor라는 내용을 찾을 수 있고, 그것의 구현체는 AutowiredAnnotationBeanPostProcessor인 것을 확인할 수 있다. 라고 합니다.
@Target : 생성자와 필드, 메소드에 적용 가능하다.
@Retention : 컴파일 이후 JVM에 의해 참조가 가능하다. 런타임 시 이 어노테이션의 정보를 리플렉션으로 얻을 수 있다.
리플렉션 : 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드,타입,변수들에 접근할 수 있도록 해주는 자바 API.
실제 타깃에 빈을 주입하는 역할 한다.
이 클래스에 processInjection() 메소드를 보면
@Autowired로 된 필드나 메소드에 객체를 주입하는 메소드다. 이 메소드 안에 InjectionMetadata.inject()메소드보면
객체를 주입할 때 ReflectionUtils 클래스를 사용하는 것을 볼 수 있다. 즉, @Autowired는 리플렉션을 통해 수행된다고 나타나는 것이다.
주입할 스프링 빈이 없이도 동작 해야할때가 있는데 @Autowired required 옵션의 기본값이 true로 되어 있어서 오류가 발생한다.
//호출 자체가 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
System.out.println("setNoBean2 = " + member);
}
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
스프링 컨테이너는 스프링 빈을 생성하고 관리한다. 스프링 컨테이너는 IOC Container 혹은 DI Container라고 부릅니다.
왜냐하면 스프링 컨테이너가 IoC,DI를 다 진행하기 때문이다. bean들을 생성하고, 의존 관계까지 연결 해주는 역할을 합니다.
(김영한님 인프런 스프링 -기본편 강의)
BeanFactory와 Application Context로 나눌수 있는데
https://devmango.tistory.com/174
https://steady-coding.tistory.com/600
https://code-lab1.tistory.com/127
https://www.nowwatersblog.com/springboot/springstudy/IOC & DI
https://lee1535.tistory.com/117
https://velog.io/@ohzzi/Spring-DIIoC-IoC-DI-그게-뭔데
스프링 핵심 원리 -기본편 학습 자료 - 김영한
스프링 애플리케이션은 스프링 컨테이너가 객체(빈)의 생성, 빈들 간의 관계 설정, 빈을 사용 및 삭제 등의 작업을 담당하는데 이를 IoC 컨테이너 라고 한다.
객체(빈)을 IoC 컨테이너가 알아서 관리 해주면서 개발자가 복잡하고 실수하기 쉬운 로우 레벨 기술에 신경 안쓰고, 비즈니스 로직에 더 집중할수 있다.
객체(빈)들 간의 의존관계를 외부에서 결정하고 주입하는 것이다.
DI를 사용하면 의존대상이 변화했을때 이에 맞게 수정안해도 됩니다.
생성자,setter,필드 주입이 있다.
생성자 주입은 생성자 호출 할때 딱 1번만 호출하고 변경되지 않기때문에 불변,필수 의존관계에 사용되고,
setter 주입은 선택,변경 있는 의존관계에 사용되며, 옵션으로 사용할때 쓰인다.
필드 주입은 애플리케이션 외부에서 변경이 불가능 하기 때문에 테스트 하기 힘들고, 주로 애플리케이션과 관계없는 테스트코드나 @Configuration 같은 스프링 설정 목적으로 쓰인다.
서로 다른 빈들이 서로를 참조를 주입하면서 생기는 현상입니다.필드 주입 ,setter 주입을 사용하면 애플리케이션 구동 당시엔 발생하지 않고, 객체(메소드)를 호출 하는 시점에 발생한다.
IoC는 외부에서 관리 하는것이므로, bean들을 스프링 컨테이너에서 관리한다.
DI는 스프링 컨테이너가 bean 사이의 의존 관계를 정보를 바탕으로 자동으로 연결 해준다.
스프링이 실행 될때(스프링 서버가 올라갈 때) 애플리케이션 컨텍스트가 @Bean,@Service 등 같은 어노테이션을 이용해 등록한 스프링 빈들을 생성하고, @Autowired 가 붙은 위치에 의존성 주입을 한다.