[본 포스팅은 인프런 스프링 핵심 원리 - 기본 편을 기반으로 작성하였습니다.]
스프링 컨테이너를 사용하지 않았고 싱글톤 패턴마저 사용하지 않았더라면 AppConfig는 이러했을 것이고
AppConfig.java
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
AppConfig에 있는 메소드를 사용할 때는 이러했을 것이다.
void pureContainer(){
AppConfig appConfig = new AppConfig();
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
}
(위 코드를 실행시켰을 때 서로 다른 객체를 사용하는 것을 볼 수 있다.)
이러한 형식으로 사용할 때마다 new를 통해 객체를 생성해서 사용했었다.
하지만 이렇게 계속 객체를 생성하면 문제점이
이렇게 클라이언트가 여러 명이 있을 때 memberService 메소드를 계속 호출하면 메모리에 계속 쌓이게 될 것이다.
그것을 방지하기 위해 싱글톤 패턴을 사용하여 객체는 하나만 생성되어 공유하면서 사용하게 할 수 있다.
그렇다면 싱글톤 패턴은 어떻게 만드냐면
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService(){
}
클래스에 이렇게만 넣어주면 된다.
참고로 private SingletonService(){}는 다른 클래스에서 싱글톤 패턴을 사용할 때 저 함수가 있어야 new로 생성하는 것을 막을 수 있다.
이렇게 말이다.
그리고 다른 클래스에서 싱글톤 패턴을 사용할 때는
getInstance()를 호출하여 사용해주면 된다(logic은 singletonService에서 구현한 기능 함수)
여기까지 봤을 때 싱글톤은 객체를 하나를 생성해주고 메모리도 효율적인 것을 느낄 수 있다 하지만 문제점은 다음과 같다.
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP를 위반한다.
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
- 테스트하기 어렵다.
- 내부 속성을 변경하거나 초기화 하기 어렵다.
- private 생성자로 자식 클래스를 만들기 어렵다.
- 결론적으로 유연성이 떨어진다.
- 안티패턴으로 불리기도 한다.
그래서 사용하는 것이 스프링 컨테이너다!
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다.
지금까지 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.
스프링 컨테이너를 사용하는 법은 아래의 코드처럼 스프링 컨테이너에 넣어주고 사용하기만 하면 된다!
수정된 AppConfig.java
@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 FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
위 수정된 AppConfig 클래스에 등록된 빈들을 아래처럼 사용한다.
+ 코드를 잘 보면 memberRepository를 MemberServiceImpl에서도 호출하고 OrderServiceImpl에서도 호출해서 두 번 new로 객체를 생성하는 것 같지만 실은 서로 같은 인스턴스를 사용한다!
왜냐하면 스프링 컨테이너를 사용하기 때문
void springContainer(){
// AppConfig appConfig = new AppConfig();
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);
}
즉, 스프링 컨테이너에 등록되었던 빈들이 사용될 때는 싱글톤처럼 객체가 하나만 생성되어 사용된다.
(생성이 안 되어있으면 생성을, 생성되어 있으면 그 객체를 사용)
그래서 위의 코드를 실행해보면 memberService1과 memberService2는 동일한 객체를 공유하며 사용하는 것을 알 수 있다.
참고: 스프링의 기본 빈 등록 방식은 싱글톤이지만, 싱글톤 방식만 지원하는 것은 아니다. 요청할 때마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.
싱글톤 방식의 주의점
- 무상태(stateless)로 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안 된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으며 안 된다.
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다. - 스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다.
스프링을 공부하면서 느낀 것은 효율적으로 서버를 구성해주는 것 같고 선택이 아닌 필수인 것 같다..
'JAVA > Spring' 카테고리의 다른 글
[Spring] 롬복(Lombok) 설치 및 간단하게 사용 (feat. 최신 트렌드) (0) | 2022.09.02 |
---|---|
[Spring] 스프링 컨테이너가 싱글톤을 보장해주는 이유 (0) | 2022.09.01 |
[Spring] 기존 코드를 Spring으로 바꾸는 방법 (Configuration, Bean으로 찍먹) (0) | 2022.08.31 |
[Spring] AppConfig의 역할과 왜 사용되는지 정리 (0) | 2022.08.31 |
스프링 부트 프로젝트 쉽게 생성하기 start.spring.io / Spring initializr (0) | 2022.08.30 |