IoC(Inversion of Control, 제어의 역전)
프로그래머가 작성한 프로그램이 재사용 라이브러리의 흐름 제어를 받게 되는 소프트웨어 디자인 패턴을 말한다.
전통적인 프로그래밍에서 흐름은 프로그래머가 작성한 프로그램이 외부 라이브러리의 코드를 호출한다. 하지만 IoC가 적용된 구조에서는 외부 라이브러리의 코드가 프로그래머가 작성한 코드를 호출한다.
Spring에서 IoC
즉 메소드의 호출, 객체의 생성과 소멸, 의존관계 설정 행위 등을 모두 프레임워크가 대신해 준다. → 주도권이 역전됨 → 제어의 역전
프레임워크가 흐름을 주도하며 개발자가 작성한 코드를 사용한다
기존의 흐름은 개발자가 new를 하여 heap 공간에 올리는 행위를 개발자가 했다면 Spring에서는 오브젝트를 클래스로 모두 만들어 놓으면 Spring이 이 클래스를 읽어서 객체로 heap영역에 올리는 것이다
IoC의 목적은 아래와 같다.
- 작업을 구현하는 방식과 작업 수행 자체를 분리한다
- 객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 향상, 코드 중복 방지, 유지 보수를 편하게 할 수 있다.
- 다른 시스템이 어떻게 동작할지 고민하지 않고 정해진 규칙대로만 동작하게 하면 된다.
- 모듈을 바꾸어도 다른 시스템에 부작용을 일으키지 않는다.
이렇게 IoC가 일어나는 공간을 IoC 컨테이너라고 한다.
Spring이 위와 같이 객체를 메모리에 올려서 Bean으로 등록되고 IoC 컨테이너로부터 인스턴스들을 받아온다면 그 인스턴스들은 항상 같은 객체일 것이다
또한 컨테이너 안에 미리 만들어놓은 하나의 객체를 사용하기 때문에 런타임 시에 성능 최적화에도 유리하다 → 메모리 효율성이 좋으며 싱글톤 패턴이 적용되는 것이다.
DI(Dependency Injection, 의존성 주입)
IoC의 구현 방법 중 하나이다.
DI에는 field 주입, setter 주입, constructor 주입이 있다.
Field 주입
@Component
public class TestClass {
@Autowired
private TestRepository testRepository;
public void doTest() {
testRepository.doTest();
}
}
의존성을 주입할 필드 웨이 @Autowired 어노테이션만 붙이면 간단하게 준비할 수 있다.
필드 주입을 사용한다고 하면 생성자나 setter가 없다는 의미가 될 수도 있는데 보통 필드 값은 private으로 선언하는 것을 권장한다.
그럼 필드 주입 외에는 필드 값을 주입해 줄 방법이 없어지고 이는 객체가 프레임워크에 매우 강하게 종속되는 것을 의미한다.
또한 수동으로 특정한 의존성을 넣어주어야 할 때 문제가 생기며 순환 참조의 문제도 생길 수 있다.
Setter 주입
@RestController
@RequestMapping("/api/v1/member")
public class MemberController {
private MemberService memberService
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
...
}
setter를 사용하여 주입하는 방식이다. 마찬가지로 setter 위에 @Autowired 어노테이션을 붙여서 사용한다.
의존성을 주입하려는 필드는 final일 수 없다.
필드 주입과 마찬가지로 순환 참조 문제가 발생할 수 있다.
setter 주입은 의존성의 선택적 주입이 가능하기 때문에 런타임에 의존성을 수정할 필요가 있을 때 사용한다.
Constructor 주입
@RestController
@RequestMapping("/api/v1/member")
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService){
this.memberService = memberService;
}
...
}
생성자를 사용하여 객체를 생성하는 시점에 모든 의존성을 주입해 주는 방식으로 가장 권장하는 방식이다.
생성자 주입을 사용할 경우 최초 빈 생성 시 1회만 의존성 주입이 호출되는 것을 보장할 수 있다.(생성자가 1회만 호출되기 때문)
기존 주입 방식들과는 다르게 final로 선언할 수 있다.
의존성 주입이 1회만 일어나고 final 필드를 사용할 수 있기 때문에 의존성의 불변을 유지해 줄 수 있는 것이 가장 큰 장점이다.
@Autowired가 생략된 것을 볼 수 있는데 스프링 4.3 버전 이후부터 단일 생성자에 대해서는 생략이 가능하다.
생성자 주입 권장 이유
@Autowired 어노테이션 마지막에 보면 필드로 주입된 아주 간단한 형태의 주입 방식이 있다.
하지만 이를 권장하지 않고 생성자 주입을 권장한다고 한다. 수정자 주입도 마찬가지다.
만약 필드 주입, 수정자 주입에서 순환참조가 발생했다고 가정하면 해당 애플리케이션은 실행은 된다. 하지만 순환참조가 발생한 메소드를 호출 시에 에러가 발생한다.
생성자 주입에서 순환참조가 발생한 경우 해당 애플리케이션은 실행조차 되지 않는다.
이러한 이유는 Bean을 주입하는 순서가 다르기 때문이라고 한다.
수정자(setter) 주입의 경우 주입받으려는 Bean의 생성자를 호출하여 Bean을 찾거나 BeanFactory에 등록한다. 그 후에 생성자 인자에 사용하는 Bean을 찾거나 만들고 주입하려는 Bean 객체의 수정자를 호출하여 주입한다.
필드(field) 주입의 경우 수정자 주입과 동일하게 Bean을 생성한 후에 어노테이션이 붙은 필드에 해당하는 Bean을 찾아서 주입하는 방법이다. 즉, Bean을 먼저 생성한 후에 필드에 대해서 주입한다.
생성자(constructor) 주입의 경우 생성자로 객체를 생성하는 시점에 필요한 Bean을 주입한다.
먼저 생성자의 인자에 사용되는 Bean을 찾거나 BeanFactory에서 만들고 그 후에 찾은 Bean으로 주입하려는 Bean의 생성자를 호출한다. 즉, Bean을 먼저 생성하지 않는다.
그래서 생성자 주입에서만 순환 참조가 문제가 된다. 객체 생성 시점에 Bean을 주입하기 때문에 서로 참조하는 객체가 생성되지 않은 상태에서 그 Bean을 참조하기 때문에 오류가 발생한다.
오류가 발생하기 때문에 서버를 실행하기 전에 오류를 찾을 수 있다. 만약 순환 참조가 발생하는데 다른 주입을 사용할 경우 서버는 잘 실행되고 서비스 중에 서버가 고장 날 수 있기 때문에 생성자 주입을 권장한다.
'Java > Spring(Boot)' 카테고리의 다른 글
Transactional 몰랐던 부분(신기해서 적어봄) + Transaction과 아닌 부분 나누기 (0) | 2023.03.21 |
---|---|
JPA와 ORM (0) | 2023.02.18 |
Spring Annotation 정리 (0) | 2023.02.03 |
Spring Framework 개념 정리 (0) | 2023.02.02 |