티스토리 뷰
IoC(Inversion of Control)
IoC란 제어의 역전이라는 의미로
메서드나 객체의 호출 작업을 개발자가 결정하는 것이 아닌 외부에서 결정하는 것을 의미합니다.
간단히 말해, "제어의 흐름을 바꾼다" 라고 합니다.
객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지보수를 편리하게 할 수 있게 해줍니다.
아래 코드와 같이 A 클래스는 B 클래스를 직접 생성하고 있으며, A 객체가 생성되면 B 객체도 같이 생성되고 있습니다.
이 방법은 A 객체가 B 객체를 new 생성자를 통해 직접 생성하는 방법입니다.
class B {
// B 클래스의 구현 내용
}
class A {
private B b;
public A() {
b = new B(); // A는 B를 직접 생성하고 관리함
}
}
위에 코드와 달리 아래코드는 개발자가 객체 간의 관계를 설정하기 위한 설정만을 제공하며, 프레임워크가 런타임에 객체를 생성하고 필요한 곳에 주입하는 코드입니다.
class B {
// B 클래스의 구현 내용
}
class A {
private B b;
public A(B b) {
this.b = b; // 의존성이 외부에서 주입됨
}
}
class IoCContainer {
public static void main(String[] args) {
B b = new B(); // 객체 생성
A a = new A(b); // 생성된 객체를 A에 주입
}
}
이 코드를 보시면 A 클래스가 B 클래스를 직접 생성하지 않습니다.
대신 A의 생성자를 통해 B의 인스턴스를 받아옵니다.
이처럼 IoC에서는 객체 생성의 제어권이 사용자 코드에서 외부로 넘어가므로 "제어의 역전"이라고 합니다
그럼 이러한 방식을 사용하는 이유는 무엇일까?
바로 객체A가 구체적인 클래스 B의 인스턴스에 의존하지 않고, 인터페이스나 추상 클래스 등을 통해 의존성을 주입받을 수 있다는 점입니다.
이를 통해서 결합도를 낮추고, 객체 A와 B사이의 의존성을 관리하기 용이해집니다.
이에 따라, 유연하고 확장성 높은 코드를 작성할 수 있습니다.
스프링 애플리케이션에서는 객체를 Bean 이라고 부르며, 프로젝트가 실행될 때 객체(빈)의 생성과 의존 관계 설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신에 스프링 컨테이너가 담당합니다.
이를 스프링 컨테이너가 코드 대신 객체에 대한 제어권을 갖고 있어 IoC 컨테이너라고도 부릅니다.
즉, 스프링 컨테이너를 IoC 컨테이너(혹은 Bean 컨테이너)라고도 부릅니다.
IoC 컨테이너
스프링에서는 IoC를 담당하는 컨테이너를 IoC 컨테이너, DI 컨테이너, Bean 컨테이너 등으로 불리고 있습니다.
위에서 설명했듯이 스프링 애플리케이션에서는 객체(빈)의 생성과 의존 관계 설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신에 스프링 컨테이너가 담당하고 있습니다.
IoC 컨테이너의 장점으로는 스프링 애플리케이션의 객체(빈)를 IoC 컨테이너가 관리해줌으로써 개발자의 부담이 줄고 비즈니스 로직에 더욱 집중할 수 있다는 점입니다.
추가로 IoC 컨테이너를 사용하여 의존성 주입(DI)을 통해, 객체는 필요한 의존 객체를 직접 생성할 필요 없으며, 이는 코드의 재사용성과 유지보수성을 향상시킵니다. (결합도 감소)
그리고 의존성 주입(DI)을 통해, 애플리케이션의 구성 요소를 쉽게 교체하거나 업데이트를 할 수 있습니다.
(애플리케이션의 유연성과 확장성 향상)
아직까지 감이 안오시거나 이해가 되지 않을 수 있습니다.
의존성 주입(DI)에 대해 아래에서 설명 한 후 더 이해하기 쉽게 자세히 설명을 하겠습니다.
DI(Dependency Injection)
DI란 의존성 주입이라고 부르며
객체를 직접 생성하는 것이 아닌 외부(IoC 컨테이너)에서 생성한 후 주입시켜주는 방식입니다.
의존성 주입을 통해 모듈 간의 결합도가 낮아지고 유연성이 높아집니다.
또한 모의 객체를 주입할 수 있기 때문에 단위 테스트가 쉬워지는 장점이 있습니다.
예시로는 아까 위에서 IoC에 대해 보았던 코드를 다시 확인해보시면 될 것 같습니다.
DI(의존성 주입)는 IoC(제어의 역전)를 실현하는 구체적인 방법 중 하나로 A 와 B 사이의 의존성을 외부에서 주입해주는 것을 말합니다.
스프링에서는 위 사진과 같이 외부의 대상이 IoC 컨테이너가 되어, 빈(객체)을 알아서 주입해 줍니다.
DI(의존 관계 주입) 구현
의존 관계 주입으로는 3가지 방법이 존재합니다.
- 생성자 주입 (Constructor Injection)
- 수정자 주입 (Setter Injection)
- 필드 주입 (Field Injection)
각각의 주입들에 대해 알아보고 순환 참조에 대해서도 알아보도록 하겠다.
생성자 주입 (Constructor Injection)
@Service
public class DataManager {
private final Storage storage;
@Autowired //생성자가 1개라면 생략가능
public DataManager(Storage storage) {
this.storage = storage;
}
}
생성자에 @Autowirde 어노테이션을 붙여 의존성을 주입받을 수 있으며, 가장 권장되는 주입 방식이다.
생성자를 통해 의존관계를 주입하는 방법이며, 생성자를 호출 시에 딱 한 번만 호출되는 것을 보장한다.
스프링 4.3 버전 이후라면 생성자가 1개만 존재할 때, @Autowired를 생략해도 주입이 되며, 주입받은 필드에 final 키워드를 사용함으로써 주입돼야 하는 것을 보장한다.
그렇다면 생성자 주입을 가장 권장하는 이유는 무엇일까?
생성자 주입이 가지는 장점은 다음과 같습니다.
- 의존관계 설정이 되어 있지 않을 경우에 컴파일 타임에 알 수 있습니다.
- 의존성 주입이 필요한 필드를 final 키워드로 선언이 가능하다.
- 스프링에서 순환 참조 감지 기능을 제공하며 순환 참조 시 에러를 보여준다.
- 테스트 코드 작성이 용이합니다.
수정자 주입 (Setter Injection)
@Service
public class DataManager {
private final Storage storage;
@Autowired
public void setStorage(Storage storage) {
this.storage = storage;
}
}
영어 그대로 setter를 사용하여 의존관계를 주입하는 방법이다.
수정자 주입의 경우 final 키워드 선언이 불가능하며, setter 메서드에 @Autowired 를 붙어 사용한다.
필드 주입 (Field Injection)
@Service
public class DataManager {
@Autowired
private Storage storage;
}
필드 주입이란 필드에 직접 의존관계를 주입하는 방법이다.
이는 코드가 짧아지는 장점이 있지만, 외부에서 변경이 불가능하고 테스트 코드를 작성하기 힘들다는 단점이 존재한다.
또한. final 키워드 선언이 불가능해지고 의존관계를 파악하기 힘들어진다.
(불변성을 보장하지 않고, 코드를 볼 때 한번에 의존 관계를 파악하기 힘들다.)
순환 참조
순환 참조란 두 개 이상의 빈(객체)이 서로를 참조하면서 서로가 서로에 생성에 필요한 상황을 말한다.
예를 들어, A클래스는 B클래스에 의존하고, B클래스는 A클래스에 의존하는 상황을 순환 참조라고 볼 수 있다.
순환 참조는 두 가지 상황에서 발생할 수 있다.
- 필드 주입 / 수정자 주입인 경우
- 생성자 주입인 경우
하나씩 간단하게 살펴보자.
필드 주입 / 수정자 주입인 경우
@Component
public class ClassA {
@Autowired
private ClassB b;
public void bMethod() {
b.call();
}
}
@Component
public class ClassB {
@Autowired
private ClassA a;
public void aMethod() {
a.call();
}
}
위 코드를 보면, ClassA는 b의 call 메서드를, ClassB는 a의 call 메서드를 서로 호출하고 있는 상황이다.
이 상황에서는 서로 주거니 받거니 호출을 반복하면서 끊임없이 호출하다 결국 StackOverflowError 에러를 발생시킨다.
이처럼 필드 주입이나 수정자 주입은 객체 생성 후에 비즈니스 로직 상에서 순환 참조가 일어나기 때문에 컴파일 단계에서는 순환 참조를 알아차릴 수 없으며 해당 메서드가 호출이 되는 시점에 예외가 발생해 프로그램에 문제가 생긴다.
생성자 주입인 경우
@Component
public class ClassA {
private final ClassB b;
@Autowired
public ClassA(ClassB b) {
this.b = b;
}
public void bMethod() {
b.call();
}
}
@Component
public class ClassB {
private final ClassA a;
@Autowired
public ClassB(ClassA a) {
this.a = a;
}
public void AMethod() {
a.call();
}
}
생성자 주입은 스프링 컨테이너가 빈을 생성하는 시점에 순환 참조를 확인하기 때문에 컴파일 단계에서 에러를 발생시켜 순환 참조를 잡아 낼 수 있다.
결론적으로, 아주 불가피한 상황이 아니라면 순환참조되는 설계를 지양하는 것이 좋다.
마지막으로 챗GPT 4.0이 그려준 순환 참조에 관한 그림을 보며 이 글을 마치겠다.
참고(출처)
https://velog.io/@gillog/Spring-DIDependency-Injection
https://velog.io/@ohzzi/Spring-DIIoC-IoC-DI-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0
https://mozzi-devlog.tistory.com/18
https://steady-coding.tistory.com/600
+ 내게 힘이 되어주는 챗GPT 4.0 님
'Spring | Spring Boot' 카테고리의 다른 글
[Spring] 싱글톤 패턴이란? 그리고 스프링에서의 싱글톤 (0) | 2024.04.25 |
---|---|
[Spring] 빈 생명주기 콜백 - 꼬리에 꼬리를 물고 완벽 이해하기 (1) | 2024.04.24 |
[Spring] 스프링과 스프링부트(Spring Boot) (1) | 2024.04.20 |
[Spring] 스프링(Spring)이란? (0) | 2024.03.20 |
[Spring] 스프링 컨테이너와 자동/수동 빈 등록 개념 정리 (0) | 2024.03.12 |
- Total
- Today
- Yesterday
- Thymeleaf
- 객체지향설계원칙
- RequiredArgsConstruct
- 인터페이스 추상클래스 비교
- 타임리프 기본기능
- 스프링특징
- 스프링 컨테이너
- 추상클래스
- HTTP요청
- redirectattribute
- 스프링
- Overloding
- HttpServletRequest
- 네이버지도크롤링
- erd editor
- 인식안됨
- Servlet
- Spring
- 인터페이스
- 요청매핑
- 크롤링
- 요청데이터
- 스프링http
- Java
- 타임리프
- 빈생명주기콜백
- erd툴
- 자바
- 인터페이스 추상클래스 차이
- 스프링 빈
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |