IoC : Inversion of flow Control 제어의 역전
제어의 흐름을 전통적인 방식과 다르게 뒤바꾸는 것이다.
사용자가 프레임워크의 코드를 호출
Car car = new Car();
car.drive();
-----------------------
void drive(){
engine = new Engine();
engine.start();
}
위 코드가 사용자 코드, 아래 코드가 라이브러리 함수라고 한다면, 전통적인 방식에서는 사용자 코드에서 라이브러리 함수를 호출하여 사용할 수 있다.
사용자 코드에서 라이브러리 함수를 호출하면 해당 함수가 실행되고, 그 후 다시 사용자 코드로 돌아오는 방식이다.
이게 우리가 흔히 사용하는 라이브러리 함수를 호출해서 결과를 받아오는 흐름이다.
IoC는 프레임워크 코드가 사용자 코드를 호출하는 방식으로 작동한다.
(전통적인 방식과 반대로 동작)
Car car = new car();
car.drive(new SuperEngine());
--------------------------------
void drive(Engine engine){
engine.start();
}
사용자가 라이브러리 함수를 호출하기는 한다.
전통적 방식과 다른점은 라이브러리 함수에서 engine.start()를 호출하면 사용자 코드에서 new SuperEngine()을 호출한다.
즉, 라이브러리 함수에서 사용자 코드를 호출하는 것이다.
우리가 제공하는 코드인 사용자코드를 라이브러리 함수에서 호출하는 흐름이다.
이는 Engine에 대한 구체적인 정보를 몰라도 되고, Engine 객체를 직접 생성하지 않고 외부에서 주입받아 사용하는 것이다.
만약 전통적인 방식의 코드에서 engine을 다른 엔진으로 변경하고 싶을 때는 코드를 변경해주어야 한다.
코드를 변경하면 컴파일부터 다 수행해야하기 때문에 매우 복잡하다.
따라서 코드를 분리해주는 것이 좋다.
new Engine() -> 잘 바뀌는 부분
나머지 -> 잘 바뀌지 않는 부분
이렇게 나눌 수 있다.
(관심사의 분리, 잘 바뀌는 것과 바뀌지 않는 것, 중복 코드 제거)
이렇게 나눈 코드는 IoC 방식으로 변환하면 된다.
아래 나머지 부분의 코드는 잘 바뀌지 않는 코드만으로 이루어져있기 때문에 사용할 객체(잘 바뀌는 부분)를 외부에서 주입받는 것이 더 좋다.
이는 @Autowired를 사용하면된다.
해당 어노테이션은 인스턴스 변수, setter, 참조형 매개변수를 가진 생성자, 메서드에 사용 가능하다.
class Car{
@Autowired Engine engine;
@Autowired Engine[] engines;
@Autowired(required=false)
SuperEngine superengine;
}
@Aurowired
public Car(@Value("red") String color, @Value("100") int oil,Engine engine, Door[] doors){
this.color=color;
this.oil=oil;
this.engine=engine;
this.doors=doors;
}
----------------------------------
@Autowired
public void setEngineAndDoor(Engine engine, Door[] doors){
this.engine=engine;
this.doors=doors;
}
여기서 String, int 타입에는 @Value를 붙여주는 이유는 @Autowired는 빈을 자동 주입시켜주는 어노테이션이기 때문에 빈을 제외하고 나머지 자료형은 불러올 수 없다.
따라서 @Value("")를 통해 값을 지정해주는 것이다.
저번에 @Autowired 관련 작성했을 때 말했듯 생성자로 주입받는 것이 가장 좋다.
생성자에 @Autowired는 생략을 해도 알아서 붙여준다. (나머지는 다 알아서 붙여야함)
주의할 점!
기본 생성자가 함께 있을 경우 @Autowired 안붙이면 다 null들어가서 안됨!
기본 생성자가 있을 경우엔 어노테이션 붙이기 ...
class Car{
@Autowired Engine engine;
@Autowired Engine[] engines;
@Autowired(required=false)
SuperEngine superengine;
}
----------------------------------------
@Component
class TurboEngine extends Engine{}
@Component(name="engine")
class SuperEngine extends Engine{}
@Autowired는 빈을 타입으로 검색해서 참조 변수에 자동 주입한다.
만약 검색된 빈이 n개일 때, 그 중 참조변수와 이름이 동일한 것을 주입한다.
주입 대상이 변수일 때 -> 검색된 빈이 2개 이상 -> 예외 발생
주입 대상이 배열일 때 -> 검색된 빈이 2개 이상 -> 정상 작동
둘 다 -> 검색된 빈이 0개일 때 -> 예외 발생
근데 여기서 @Autowired(required=false)를 해주면 검색된 빈이 0개여도 예외가 발생하지 않고 참조 변수에 null값을 넣어준다.
위에 코드에서는
engine에는 SuperEngine이, engines[]에는 SuperEngine과 TurboEngine이 들어간다.
engine에 SuperEgine이 들어가는 이유
SuperEngine과 TurboEngine 둘 다 Engine을 상속받은 것이므로 타입이 Engine이다.
그럴 때는 참조 변수와 이름이 동일한 것으로 주입하는데 참조변수 명이 engine이다.
설정을 해줄 때 TurboEngine은 따로 이름 설정이 없으므로 turboengine으로 설정되었을 것이고, SuperEngine은 engine이라고 설정을 해주었기 때문에 engine으로 설정되었을 것이다.
따라서 두 개 이상 검색이 되었을 때는 이름이 동일한 것으로 불러오므로 SuperEngine이 들어가는 것이 맞다 .
engines[]에 둘 다 들어가는 이유
주입 대상이 배열이면 만족하는 애들을 모두 넣어줄 수 있다.
따라서 그냥 둘 다 넣으면 된다!