들어가며
지난 4편에서 좋은 객체 지향 프로그래밍을 위해 Config 구성 인터페이스를 만들어 역할을 분리하는 것에 대해 공부했습니다. 이번 5편에서 다룰 스프링 컨테이너 즉, IoC 컨테이너는 이렇게 따로 분리해 낸 Config 인터페이스를 @ 어노테이션을 활용해 보다 더 모듈화하고 유지보수에 용이하도록 로직을 다듬으려고 합니다.
스프링 컨테이너란
스프링 컨테이너란?
스프링 컨테이너는 스프링 프레임워크에서 IoC (Inversion of Control)를 구현한 핵심 컴포넌트다. IoC란 객체의 생성, 관리, 생명주기를 개발자가 아닌 프레임워크 또는 컨테이너가 담당하는 디자인 패턴을 말한다.
위 그림과 같이 IoC컨테이너를 이용하면 AnnotationConfigApplicationContext를 읽어 대신 객체를 생성하고 의존관계를 주입해준다는 것인데, 이것은 자바 코드를 통해 직접 구현할 수 있었다.
그럼에도 스프링 컨테이너를 사용하는 이유는 아래에서 설명할 싱글톤 컨테이너의 개념과 연관된다.
//MemberApp 내용 수정!
//AppConfig를 통해 직접 객체를 생성하고 의존관계를 주입했었다.
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
//스프링 컨테이너를 이용해 빈 저장소로부터 빈을 관리한다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
스프링 컨테이너를 사용해야 하는 이유 = 싱글톤 컨테이너
1. 스프링 없는 순수한 DI컨테이너
다시 등장한 자동차 그림이다. 이미 좋은 객체 지향 프로그래밍을 위해 역할 분리를 마친 상태다.
운전자는 K3를 탈지, 아반떼를 탈지, 모델3를 탈지 모르고 자동차가 필요해 호출한다.
그러면 config 구성 창에서 지정된 K3 객체를 new를 통해 생성하게 되는 것이다.
그런데 운전자가 만약 자동차를 한번 더 호출한다고 하자. 이 상황에서 new를 통해 또다른 K3가 생성된다.
결국 운전자가 필요한 자동차는 1대인데 수십 수백대가 생성된다면, 엄청난 자원 낭비인 것이다.
2. 싱글톤 패턴을 적용
아래는 싱글톤 패턴을 위해 임의로 만든 서비스다.
싱글톤 패턴은 new를 통해 객체가 무한정 생성되는 상황을 방지하기 위해 객체가 생성되고 나면 private을 통해 추가 생성을 방지하는 디자인 패턴이다.
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {
}
즉, K3 객체 1대만 생성해서 Config 구성창에서 K3를 운전하도록 지정된 운전자들은 모두 공유하도록 하는 것이다. 렌터카(?)를 생각하면 비슷할지도...
공유되는 인스턴스다 보니 여러가지 문제점이 등장한다.
- .getInstance를 통해 직접 구현체를 의존해 앞서 강조했던 DIP, OCP를 위반한다.
- Stateful이 아닌 Stateless 무상태로 설계해야 한다.
Stateful과 Stateless
Stateful은 자차, Stateless는 렌터카라고 비유해보면, km당 이용 요금을 내야 한다고 하자.
Stateful은 자차이기 때문에 다른 운전자가 이용하지 않아 계기판을 확인하면 바로 내가 탄 키로수를 확인할 수 있다. 그러나 Stateless는 내가 이용하고 바로 확인하지 않고 그 사이에 다른 운전자가 이용하게 된다면, 계기판에는 내가 탄 키로수가 아닌 다른 운전자가 탄 키로수가 적혀 있을 것이다.
자원의 효율 측면에서는 장점을 가지고 있지만 변경과 저장이 어렵다는 점에서 단점을 지니고 있다.
*아래는 간단한 스레드를 통한 예시
//ThreadA: A사용자 1000키로 이용
statefulService1.distance("userA", 1000);
//ThreadB: B사용자 2000키로 이용
statefulService2.distance("userB", 2000);
//ThreadA: 사용자A 키로수 조회
int kilometer = statefulService1.getKilometer();
//ThreadA: 사용자A는 1000키로를 이용했지만, 2000키로 출력
System.out.println("kilometer = " + kilometer);
3. 스프링 컨테이너와 싱글톤
스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다
른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한다.
스프링 컨테이너에서는 이런 논리로 싱글톤을 유지한다고 하는데,, 이 부분은 깊게 파기보다 @Configuration을 사용하면 된다는 정도면 충분하다.
@Configuration, @Bean
@Configuration 애노테이션을 통해 빈 저장소를 만들고, @Bean 애노테이션을 부여해 빈 저장소에 저장한다.
저장된 Bean은 .getBean을 통해 조회 할 수 있다.
@Configuration 을 붙이면 바이트코드를 조작하는 CGLIB 기술을 사용해서 싱글톤을 보장한다.
package hello.core;
import ...
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
@Test 코드를 이용한 컨테이너 테스트
package hello.core.singletone;
import ...
public class SingleToneTest {
@Test
@DisplayName("스프링 없는 순수한 DI컨테이너")
void pureContainer() {
AppConfig appConfig = new AppConfig();
//조회 : 호출할 때 마다 객체를 생성
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonService() {
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
System.out.println("memberService1 = " + singletonService1);
System.out.println("memberService2 = " + singletonService2);
}
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void SpringContainer() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//조회 : 호출할 때 마다 객체를 생성
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
}
'Java Spring > Spring Framework' 카테고리의 다른 글
롬복, 컴포넌트 스캔 ~ 의존 관계 주입 Jav Spring 프레임워크의 기본 (6) (0) | 2023.12.04 |
---|---|
좋은 객체 지향 SOLID를 위한 config 구성창 Jav Spring 프레임워크의 기본 (4) (0) | 2023.11.28 |
회원 도메인 설계! Jav Spring 프레임워크의 기본 (3) (1) | 2023.11.27 |
JAVA는 객체 지향 프로그래밍? Jav Spring 프레임워크의 기본 (2) (0) | 2023.11.27 |
처음 자바 스프링을 공부한다면?Java Spring 프레임워크의 기본 (2) | 2023.11.26 |