스프링 IoC컨테이너
스프링에서 의존관계 주입(Dependency Injection, DI)을 이용하여 애플리케이션을 구성하는 여러 빈(Bean)들의 생명주기(Lifecycle)와 애플리케이션의 서비스 실행 등을 관리하며 생성된 인스턴스들에게 기능을 제공하는 것을 부르는 말이다.
스프링 IoC 컨테이너 생성과정
- 스프링 컨테이너 생성
- 스프링 빈 등록 : 스프링 설정파일 (Java, xml ... ) 기반으로 컨테이너에 스프링 빈 등록
- 스프링 빈 의존관계 설정 : 스프링 설정파일 (Java, xml ... ) 기반으로 컨테이너에 스프링 빈의 의존관계 주입(DI)
DI ( Dependancy Injection : 의존성 주입)
두 객체 간의 관계(의존성)를 맺어주는 것
기존의 객체 생성
public class Store {
private Item item;
public Store() {
item = new ItemImpl1();
}
}
Store 클래스가 ItemImpl1에 의존하고 있어서 ItemImpl1이 변경이 필요한경우 Store 클래스의 코드를 수정해야된다.
의존관계가 주입되는 방식이 아닌 객체
- 객체 생성
- 클래스 내부에 의존성 객체 생성
생성자 주입 방식
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
DI를 사용하게 되면 Item을 특정하지 않고 Store를 생성할때 item도 함께 주입받게 된다
의존관계가 주입되는 방식인 객체
- 객체생성
- 의존성 객체 주입
DI의 종류
필드주입
@Service
public class Store {
@Autowired
private Item item;
}
장점
- 코드의 간결함
단점
- 외부 수정 불가능
- 불변한 상태 만들수 없음
- 테스트 코드 작성 시 객체수정 불가
- DI 지원하는 프레임워크 필요
- 객체의 역할이 많아지면서 단일책임원칙 위배 가능
수정자주입
@Service
public class Store {
private Item item;
@Autowired
public setItem(Item item) {
this.item = item;
}
}
장점
- 선택적의존성 사용가능
단점
- 임의로 다른 클래스에서 사용이 가능해져서 Error발생 위험
- 순환참조 문제 발생 가능
OCP(Open-Closed Principle, 개방-폐쇄 원칙)
소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해서는 열려있어야 하고, 수정에 대해서는 닫혀있어야 한다는 프로그래밍 원칙
수정자 방식은 OCP를 위반하게됨.
생성자주입
@Service
public class Store {
private final Item item;
@Autowired
public Store(Item item) {
this.item = item;
}
}
생성자가 2개이상일 경우는 @Autowired 필수.
장점
- 명시적인 의존성 표현
- 결합도 감소
- 불변성 보장
- 순환 참조를 컴파일 단계에서 찾아낼 수 있다.
- 테스트하기 쉬움
생성자주입 세부사항
롬복 @Require ArgsConstructor
롬복 라이브러리를 사용하게 된다면 생성자 주입 코드가 더욱 간결하게 표현이 가능하다.
build.gradle에 해당 코드를 추가한다.
//gradle 5.x 미만
implementation 'org.projectlombok:lombok'
//gradle 5.x 이상
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
java: variable member not initialized in the default constructor
만약 생성자를 롬복을 이용하여 만들었는데 해당 오류가 발생한다면
build.gradle에서 gradle 버전에 맞게 롬복 설정을 했는지 다시 한번 봐보자.
Lombok 은 Java에서 자주 반복되는 코드(Getter, Setter, 생성자 등.. )들을 어노테이션을 통해 자동으로 생성해 주는 라이브러리이다.
그중 @RequireArgsConstructor 어노테이션을 사용하게 되면
클래스의 모든 필수 인자에 대한 생성자를 자동으로 생성해주게 된다.
@Service
@RequireArgsConstuctor
public class Store {
private final Item item;
/*
@Autowired
public Store(Item item) {
this.item = item;
}
*/
}
주입 시킬 객체 Item에 final 키워드를 붙임으로써 Store 객체가 스프링 컨테이너에 Bean으로 등록될 때
Item을 주입시켜준다.
생성자 주입 시 테스트 이점
테스트코드가 특정 프레임워크에 의존하는것은 좋지않다.
순수 자바로 가능한 테스트를 작성하는것이 좋다.
테스트 하고자 하는 클래스에 필드 주입이나 수정자 주입으로 빈이 주입되어있으면,
Mockito를 이용해 목킹 한 후 테스트를 진행해야된다.
하지만 생성자 주입을 하는 경우.
생성자 주입을 하는 경우 단순히 객체 생성 후 생성자에 넣으면 된다.
생성자 주입 구성 클래스
@Configuration
public class AppConfig {
@Bean
public Item item1() {
return new ItemImpl1();
}
@Bean
public Store store() {
return new Store(item1());
}
}
구성영역
@Service
@RequireArgsConstuctor
public class Store {
private final Item item;
}
사용영역
AppConfig 클래스처럼
특정 클래스에서 구성을 책임지는 방식을 통해 생성자 주입을 하게되면,
Item이 변경되더라도 유연한 구조를 가져갈 수 있다.
사용영역과 구성영역으로 나눔으로써
클라이언트 코드에서는 추상화인 인터페이스만 의존하므로
OCP와 DIP을 만족할 수 있다.
정리하자면 스프링에서 생성자 주입을 적극적으로 사용하자.
순환참조란?
순환 참조 에러는 A객체가 B객체를 참조하고, B객체가 A객체를 서로 참조하고 있을 때 발생
@Service
public class Member {
@Autowired
private Level level;
public void validate() {
level.validate();
}
}
@Service
public class Level {
@Autowired
private Member member;
public void validate() {
member.validate();
}
}
Member 서비스의 validate() 메서드는 Level을 참조하고,
Level 서비스의 validate() 메서드는 Member 을 참조하는 경우
어플리케이션 실행단계에서는 오류가 나지 않지만, 해당 메서드가 실행되는경우
계속 반복하여 StakOverflowError가 발생합니다.
@Service
@RequireArgsConstuctor
public class Member {
private final Level level;
public void validate() {
level.validate();
}
}
@Service
@RequireArgsConstuctor
public class Level {
private fianl Member member;
public void validate() {
member.validate();
}
}
생성자 주입을 통한 의존성 주입입니다.
실행 시 오류메시지가 발생합니다.
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| level defined in file [demo\out\production\classes\com\example\demo\service\Level.class]
↑ ↓
| member defined in file [demo\out\production\classes\com\example\demo\service\Member.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
Process finished with exit code 1
왜 오류가 발생하는 시점이 다르냐면,
Field 선언과 Constructor 선언의 차이를 알아야한다.
Field 선언
Field 선언 시 Member 클래스가 로드되었을때 Level class 타입의 level reference 변수만 가지고 있다.
즉, 클래스 A의 객체가 생성되었을때 level을 생성할 필요가 없다.
Constructor 선언
생성자로써 객체가 생성될때 실행되는 메서드, 객체 A가 생성될때 객체 B를 생성하여 담는다.
따라서 실행 시점에 따라 프레임워크에서 오류를 발견할 수 있는 생성자 주입 방식을 사용하는게 권장된다.
참조
https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring
https://velog.io/@alicesykim95/Spring-Container-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88
https://steady-coding.tistory.com/600
https://jurogrammer.tistory.com/79
'스터디 > 2023_스프링부트' 카테고리의 다른 글
[study] HTTP 웹 기본 지식 (1) | 2023.07.23 |
---|---|
[study]자바의정석 11~14 chapter (0) | 2023.07.18 |
[study] 자바의 정석 - chapter 6~9 (0) | 2023.07.09 |
[study] http 멱등성 알아보자 (0) | 2023.07.03 |
[study]스프링 기본 및 핵심원리 (0) | 2023.06.26 |