이 글은 스프링 핵심 원리 - 기본편을 기반으로 작성되었습니다.
여러가지 싱글톤을 구현하는 방법..
싱글톤 컨테이너
웹 어플리케이션과 싱글톤
스프링은 보통 웹 서비스에서 사용된다.
웹은 다양한 사용자가 동시에 많은 요청을 한다는 특징이 있다.
그렇다면 지금까지 개발해온 것들은 그 요청을 잘 수행할까?
아래 그림을 보며 생각해보자.
클라이언트 한명 당 하나의 객체가 생성되는 모습을 볼 수 있다.
너무나 메모리 낭비가 심하다. 이는 결국 비용적 문제로 이어질 수 있다.
음 그럼 어떤 해결방법이 있을까?
3형제가 있는데 장난감을 하나씩 사주는건 너무 비싸다.
그러면 하나사서 나눠쓰라고 하면 좋지 않을까!
객체도 하나로 나눠쓰는게 좋지 않을까..!
일단 객체가 생성되는지 확인해보자.
싱글톤 패턴
객체를 하나만 만들어 사용하는건 또 어떻게 하는가....
의외로 간단한데 private로 인스턴스(구현 객체)를 생성해서
불러오는 매서드를 만들고 사용하면 된다!
package singletonTest;
public class SingletonService {
private static SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {}
public void logic(){
System.out.println("싱글톤 객체 로직 호출");
}
}
위에서의 테스트와 달리 같은 객체를 사용하고 있음이 확인된다!
와! 그럼 싱글톤 무적이네! 라고 한다면 아니다.
구현 코드가 많다, 구체 클래스에 의존한다, 테스트가 어렵다 등의 여러 문제들이 있다.
스프링 컨테이너는 위 문제점들을 모두 해결하고 싱글톤으로 관리해준다.
스프링 컨테이너
스프링에서 빈을 관리해 주기 때문에 여러개의 객체 생성의 걱정없이 개발을 진행할 수 있다.
싱글톤을 적용하여 그림은 아래와 같이 바뀌었다.
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
//AppConfig appConfig = new AppConfig();
ApplicationContext 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);
}
싱글톤의 주의점
하나만 생성해서 좋은 것만 있는 것은 아니다.
하나이기에 항상 같은 상태를 유지해야하고 특정 클라이언트에게 객체가 변화해선 안된다.
class StatefulServiceTest {
@Test
void statefulServiceSingleton() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService service1 =ac.getBean(StatefulService.class);
StatefulService service2 =ac.getBean(StatefulService.class);
service1.order("유라면",10000);
service2.order("김비빔",20000);
int price = service1.getPrice();
System.out.println("가격: "+ price);
Assertions.assertThat(service1.getPrice()).isEqualTo(20000);
}
static class TestConfig{
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
고객 유라면의 결제 금액은 10000원 인데 두번째 김비빔도 결제를 위해 주문을 한 상황이라면
유라면은 10000원이 아닌 20000원을 결제하게 된다.
이걸 해결하는건 지금은 간단하다.
공유되던 변수를 제거하고 원하는 가격 부분을 반환할 수 있도록 변경하면 된다.
하지만 실무에서 상속과 여러가지 요인들이 결합하면 다 뒤집어야하니 주의해야 한다...!!!!
public class StatefulService {
//private int price;
public int Fixorder(String name, int price){
System.out.println("이름: "+ name + " 가격: " + price);
//this.price = price; // 문제가 되는 부분
return price;
}
/*
public void order(String name, int price){
System.out.println("이름: "+ name + " 가격: " + price);
this.price = price; // 문제가 되는 부분
}
public int getPrice(){
return price;
}
*/
}
@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();
}
}
여태 잘 됐는데? 문제 없는데?? 라고 생각이 든다. 하지만 구조를 살펴보면
memberService , orderService , memberRepository 에서 중복적으로 등록이후에 호출이 된다는 것을 알 수 있다.
그럼 memberRepository 만 3번 호출되는 건데,
하지만 확인해보면 한번씩 호출됨을 알 수가 있다.
이 마술같은 일을 해주는 것이 @Configuration 어노테이션이다.
어떻게 해주는가
아래 그림을보면 내 설정파일을 상속받는 CGLIB 가 있는데 설정파일을 상속받아
컨테이너에 내 코드가 아니라 상속받은 코드를 설정으로 입력한다.
이것이 중복이 일어나도 싱글톤을 유지할 수 있는 방법이다! 어노테이션 하나로 해결된다니 혁신적이다.
스프링 만세
자동 빈 등록과 자동 의존성 주입
빈 으로 등록하는 것들이 별로 없으면 상관이 없지만 그게 100개 200개가 된다면...?
하나하나 하는 것은 그냥 가내수공업과 같다.. 그럼 자동으로 등록하는 방법을 알아보자
새로운 설정파일을 만든다.
package hello.demo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.springframework.context.annotation.ComponentScan.*;
@Configuration
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
Configuration.class))
public class AutoAppConfig {
}
필터는 이전 설정파일이 겹치면 자동등록의 의미가 없으니 사용하였다.
빈 자동등록은 2가지를 알면 되는데
@ComponentScan , @Component
컴포넌트 스캔은 모든 파일을 탐색하고 컴포넌트 어노테이션이 등록되어있는 부분을 모두 컨테이너에 등록해준다.
내가 이해한 이미지는 컴포넌트 스캔은 반장 , 컴포넌트는 손들기로 설정하고
반장이 손든 친구들을 모두 검사하는 방식이다.
@Component("service")
이런식으로 이름을 붙여서 손을 들 수 있다. 빈에 저 이름으로 등록
이렇게 등록하면 하나 의문이 생길 수 있는데 의존성 주입은 어떻게 하는가... 이다.
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
기존은 이런 방식으로 지정하여 주입을 해줬지만 저걸 다 써주면 수동이랑 다를 것이 없다.
그렇기에 자동으로 주입해주는 @Autowired 가 있다.
오토 와이어드 , 자동연결..? 바로 이해가 되는 느낌이다.
사용방법은 컴포넌트나 오토와이어드나 같다. 필요한거 위에 어노테이션만 붙이면 된다.
@Component
public class MemberServiceImpl implements MemberService {
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
탐색 위치 등록
탐색 한다고 했는데 어디부터 하는걸까?
아마 모든 패키지를 돌아다니며 찾을 것이다. 하지만 그건 너무 오래 걸릴거 같기도 하다.
그래도 이런 걱정은 할 필요가 없는데 기본적으로는 아래와 같은 설정파일의 경로부터 찾는다고 한다.
package hello.demo;
지정하는 방법은 아래와 같이 설정하면 hello.demo.member 부터 탐색한다.
@ComponentScan(
basePackages = "hello.demo.member",
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
Configuration.class))
위에서 컴포넌트 등록을 component로 손을든다 라고 했는데 이것만 있는 것이 아니다.
아래와 같은 것들은 안에 컴포넌트를 포함하기에 똑같은 손들기이다.
필터
내가 원하는 것을 등록을 하거나 등록을 하지 않을 수 있는데 필터로 이를 적용한다.
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyExcludeComponent.class)
)
위와 같은 사용법이고 사실 include는 잘 사용되지 않고 exclude는 가끔 사용되는 편이다.
중복등록과 충돌
여러 사람과 개발을 하다보면 등록시에 충돌이 발생할 수 있다.
보통 수동등록과 자동등록이 충돌하는 상황이 발생하는데
이때 스프링은 수동등록을 기본으로 설정해 버리고 우선순위로 결정한다.
하지만 부트를 사용하면 오류를 발생하는 것을 확인할 수 있는데
부트에서 이 관련설정을 false가 기본값이 되도록 설정하였기 때문이다.
application.properties 에서 변경이 가능하다.
후기..
뭔가 하다보니 느끼는 거지만 이미 좋은 기능들이 많이 개발되어 있으니까 잘 배워서 써먹을 수 있도록 잘 공부해야겠다는 생각이 들고 스프링은 어노테이션을 공부하는게 스프링 사용에 있어서는 본질적인 공부가 아닐까? 라는 생각이 든다.
역시 누군가는 모두 정리해 놓으셨군요! 블로그 만세
'WINK-(Web & App) > Spring Boot 스터디' 카테고리의 다른 글
[2024-2 Spring Boot 스터디] 조상혁 #6주차 (3) | 2024.12.02 |
---|---|
[2024-2 Spring Boot 스터디] 김아리 #5주차 (0) | 2024.11.28 |
[2024-2 Spring Boot 스터디] 김문기 #5주 (0) | 2024.11.27 |
[2024-2 SpringBoot 스터디] 정호용 #5주차 섹션 6~7 (0) | 2024.11.25 |
[2024-2 SprintBoot 스터디] 윤성욱 #4주차 (0) | 2024.11.21 |