기존의 AppConfig를 통한 방법은 클라이언트가 요청을 할 때마다 객체들을 생성하는 문제점이 있다 -> 엄청난 메모리 낭비
아래의 memberService로 테스트를 해보자
테스트를 돌려보면 생성된 MemberServiceImpl이 다른것을 확인할 수 있다
참고로 테스트는 위처럼 하나하나 출력해보는 것보다 아래처럼 assertThat으로 자동화 하는습관을 들이는 것이 좋다
아무튼 이렇게 요청할 때 마다 객체가 생성되는것을 어떻게 하면 방지할 수 있을까?
->객체를 한 개만 생성하고 그것을 공유하면 됨 = 싱글톤 패턴
test/hello.core/Singleton/SingletonService
1. private static 으로 실행할 때 static영역에 SingletonSerivice객체를 하나 만든다
2. 접근은 getInstance()로 한다-> 이 메서드를 호출하면 항상 같은 인스턴스를 반환함
3. private 생성자인데 외부에서 new키워드로 객체를 생성하는것을 막는다
테스트 해보면 따로 호출했지만 같은 객체를 반환 받은 모습을 확인할 수 있다
이것도 아래처럼 assertThat으로 하면 좋다
isSameTo는 주소값을 비교하는 메서드이다
따라서 new로 생성된 두 객체를 비교할 때 사용에 적합함
Car car = new Car();
왜냐면 위 처럼 car에는 새로생성된 Car객체의 주소값이 저장되기 때문
isEqualTo는 대상의 내용자체를 비교한다.
String a = "apple";
String b = "apple";
싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
의존관계상 클라이언트가 구체 클래스에 의존한다. DIP를 위반한다.
->구현클래스.getInstance() 이런식으로 호출해야해서..
ex) memberServiceImpl.getInstance()
클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
테스트하기 어렵다.
내부 속성을 변경하거나 초기화 하기 어렵다.
private 생성자로 자식 클래스를 만들기 어렵다.
결론적으로 유연성이 떨어진다.
안티패턴으로 불리기도 한다
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다.
지금까지 우리가 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다
그냥 스프링 컨테이너 사용하면 싱글톤 패턴을 사용한다고 보면 됨
싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를
유지(stateful)하게 설계하면 안된다.
무상태(stateless)로 설계해야 한다!
아래 예시
위의 테스트 결과는 20000이 나온다
즉 손님1이 10000원을 주문하고 손님2가 20000원을 주문한 상황인데
손님1의 결제내역을 봤을 때 20000원이 나오는 상황이다
price라는 변수를 지금 같이 사용(공유필드) 하고 있기 때문에 이런상황이 발생하는 것임
아래처럼 price를 유지해두지 말고 그때 그때 return price 해주면 해결됨 (이런 간단한 상황에서는)
AppConfig의 자바 코드를 보면 분명히 각각 2번 new MemoryMemberRepository 호출해서 다른인스턴스가 생성되어야 하는데?
테스트를 해보면 같은 인스턴스가 불리는것을 알 수 있음
AppConfig에 @Configuration 을 적용했기 때문에 가능한 일임
내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다
main/java/hello/core/AutoAppConfig (새로 작성함)
위의 코드를 보면 기존의 AppConfig와 다르게 @Bean으로 등록해 둔 클래스가 한 개 도없음
그렇지만 컴포넌트 스캔을 사용하면 기존 AppConfig마냥 따로 안써줘도 됨 컴포넌트스캔을 사용하려면 @componentScan을 사용하면 되고 저기 excludeFilters는 스프링 빈으로 등록 안할것들을 써주면 됨
그러면 이제 @Component 붙은 것들을 다 스프링 컨테이너에 등록해줌
->아래와 같이 구조체 클래스에 가서 @Component 미리 등록해주면 됨
이때 의존성 주입은 생성자 위에 @autowired를 붙여주면 알아서 타입에 맞는 구현체를 찾아서 이어줌
basePackges = ~
member패키지만 스캔함 (모든 패키지를 다 뒤지면 너무 오래걸림)
여러개 둘 수도 있음
그럼 basePackages로 지정하지 않으면?
->만약 지정하지 않으면 @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다
그래서 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것을 권장함
예를 들어서 프로젝트 구조가 아래와 같이 되어있으면
com.hello
com.hello.serivce
com.hello.repository
com.hello 프로젝트 시작 루트, 여기에 AppConfig 같은 메인 설정 정보를 두고, @ComponentScan 애노테이션을 붙이라는 말임
컴포넌트 스캔은 @Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.
@Component : 컴포넌트 스캔에서 사용
@Controlller : 스프링 MVC 컨트롤러에서 사용
@Service : 스프링 비즈니스 로직에서 사용
@Repository : 스프링 데이터 접근 계층에서 사용
@Configuration : 스프링 설정 정보에서 사용
includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다
-> @Component면 충분하기 때문에 이런게 있다 정도만 알고 필요할 때 더 공부해서 사용할 예정
-> 충돌
-> 수동이 우선권을 가짐