이전 포스트에서 스프링 컨테이너에 대한 사용 방법을 알아보았다면,
이번에는 스프링 컨테이너의 구조와 생성 원리에 대해서 알아본다.
스프링 컨테이너의 구조
스프링 컨테이너는 최상위에 BeanFactory 클래스가 있고, 그 하위에 ApplicationContext가 있다.
둘 다 인터페이스로 사용을 위해 구체 클래스를 지정하여 사용한다.
- BeanFactory : 스프링 컨테이너의 최상위 인터페이스, 스프링 빈을 관리하고 조회하는 역할을 담당하며, getBean() 메소드를 제공
- ApplicationContext : BeanFactory 상속받아 기능을 제공하며, 부가 기능을 제공한다.
- 메시지 소스를 활용한 국제화 기능 : 해당 지역에 맞는 언어 출력
- 환경변수 : 로컬/개발/운영 등을 구분하여 처리
- 애플리케이션 이벤트 : 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- 편리한 리소스 조회 : 파일, 클래스 패스 등을 편리하게 조회
ApplicationContext의 구현체는 에너테이션 기반(AnnotationConfigApplicationContext(AppConfig.class)), xml 기반 등으로 설정 클래스를 만들 수 있다.
스프링 컨테이너의 생성 과정
########### AppConfig.java 파일 ############
// 1. 스프링 컨테이너 클래스 생성 (에너테이션 기반)
@Configuration
public class AppConfig {
// 2. 스프링 Bean 등록
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
...
}
########### MemberApp.java 파일 ############
...
// 3. 스프링 컨테이너 생성 = ApplicationContext 인스턴스 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Appconfig.class)
// 4. 스프링 Bean을 통해 구체 클래스 인스턴스 생성
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
...
스프링 컨테이너를 관리하는 객체인 AppConfig 와 이를 사용하는 객체인 MemberApp 로 나누어서 생성과정을 살펴본다.
AppConfig 객체는 ApplicationContext(스프링 컨테이너)를 생성할 때 인자로 사용하는 객체로, 객체 내에 등록된 스프링 빈에 대해서 사용할 수 있게 된다. 스프링 컨테이너에 등록된 객체는 BeanDefinition(스프링 빈 설정 메타정보)를 통해서 정보를 얻을 수 있다.
이렇게 등록된 스프링 빈들은 싱글톤으로 객체를 관리한다.
싱글톤 패턴 & 싱글톤 컨테이너
싱글톤 패턴은 프로그램 내에 객체가 동일한 인스턴스를 공용으로 사용하는 것으로, 호출할 때마다 동일한 객체(인스턴스)를 반환한다.
여러 생성 방법 중 객체를 미리 생성해두는 방법으로 싱글톤 패턴을 만드는 것을 확인해보자.
public class SingletonService {
// 1. private static 변수를 통해 객체 자신의 인스턴스인 클래스 변수를 생성
private static final SingletonService instance = new SingletonService();
// 2. 생성자를 private으로 선언하여 외부에서 new 키워드를 사용하지 못하도록 강제화
private SingletonService() {
}
// 3. public으로 객체를 반환하는 메서드를 제공하여, 객체 인스턴스가 필요시 해당 메서드를 호출할 수 있도록 제공
public static SingletonService getInstance() {
return instance;
}
// 4. 싱글톤 객체에서 제공하는 메소드 구현
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
위와 같이 싱글톤 패턴을 적용하는 객체를 생성할 수 있지만 다음과 같은 문제점들이 발생한다.
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어감 - 인스턴스 내 static 변수 생성, 생성자 선언 등
- 의존관계상 클라이언트가 구체 클래스에 의존함 (DIP 를 위반함)- 싱글톤 객체의 메서드를 직접 호출해서 사용해야 됨
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높음
- 인스턴스를 지정하여 받기 때문에 유연하게 테스트하기 어려움
- 내부 속성을 변경하거나 초기화 하기 어려움
- private 생성자로 자식 클래스를 만들기 어려움
- 유연성이 떨어지며 안티패턴으로 불리기도 함
하지만 스프링 컨테이너를 쓰면 자동으로 싱글톤을 관리하면서 위와 같은 문제를 해결할 수 있다.
스프링 컨테이너는 싱글톤 컨테이너의 역할을 하며, 싱글톤 패턴을 적용하지 않아도 싱글톤 패턴이 적용된 객체를 생성하고 관리한다.
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어감 - 인스턴스 내 static 변수 생성, 생성자 선언 등
→ AppConfig 객체 내 메소드 선언으로 간단하게 객체 생성 가능 - 의존관계상 클라이언트가 구체 클래스에 의존함 (DIP 를 위반함)- 싱글톤 객체의 메서드를 직접 호출해서 사용해야 됨
→ AppConfig 의 Bean 호출로 클라이언트 코드 내부에서 구체 클래스 사용하지 않음 - 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높음
→ 2번과 동일 - 인스턴스를 지정하여 받기 때문에 유연하게 테스트하기 어려움
→ 2번과 동일 - 내부 속성을 변경하거나 초기화 하기 어려움
→ 구성 객체(AppConfig)를 통해 제어하기 때문에 변경에 용이 - private 생성자로 자식 클래스를 만들기 어려움
→ public으로 생성하고, 에너테이션만 추가 - 유연성이 떨어지며 안티패턴으로 불리기도 함
→ 5번과 동일
위의 이점으로 AppConfig를 직접 사용하지 않고, 스프링 컨테이너에 등록하여 사용하는 것이다.
ApplicationContext 객체를 생성하고, 빈을 통해 구현 객체를 찾아서 인스턴스를 생성하는 수고로움을 하는 이유인 것이다.
싱글톤 방식의 주의점
싱글톤은 프로그램 내에 1개의 객체를 사용하기 때문에 특정 클라이언트에 상태를 저장하는 행위를 수행하면 안된다.
즉, 무상태(stateless)로 설계해야하며 아래 내용은 이와 일맥상통하는 내용이다.
- 특정 클라이언트에 의존적인 필드가 있으면 안됨
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안됨
- 가급적 읽기만 가능하도록 설계
- 필드 대신 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야함
@Configuration과 싱글톤
스프링 컨테이너에서 Bean을 생성할 때 OCP를 지키기 위해서 선언된 다른 Bean을 사용해서 생성하는 경우가 있다.
이런 경우 new가 두번 일어나서 두개의 서로 다른 객체가 생성되는 것 처럼 보이지만, 스프링 컨테이너는 싱글톤을 유지한다.
동일한 객체를 유지하는지 확인하기 위해서는 인스턴스를 선언 후 주소를 비교하면 쉽게 확인이 가능하다.
// memberService 생성 시 memberRepository를 호출하여 생성자의 인자로 사용
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
// memberRepository 호출
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
AppConfig 객체에 @Configuration 에너테이션이 붙은 경우, AppConfig 또한 Bean으로 등록된다.
Bean에 등록된 객체를 확인하면, class hello.core.AppConfig$$EnhancerBySpringCGLIB$$900ce159 와 같이 원래 이름과 다르게 등록이 되어있다. 이는 임의의 클래스를 동적으로 스프링 빈을 등록하여 싱글톤이 되도록 보장되도록 해준다.( 바이트 코드 조작을 통해 / 이미 스프링 빈이 있으면 존재하는 스프링 빈 반환 or 스프링 빈에 없으면 새로 생성 후 컨테이너에 등록 )
@Configuration 에너테이션을 사용하지 않으면 원본 클래스가 그대로 나오고 스프링 빈도 그대로 등록되지만, 싱글톤을 보장하지 못한다.
스프링 설정 정보에서는 @Configuration 에너테이션을 넣자
'프로그래밍 기초' 카테고리의 다른 글
스프링 기본편 - 의존 관계 주입 (0) | 2022.07.15 |
---|---|
스프링 기본편 - @ComponentScan (0) | 2022.07.15 |
스프링 기본편 - 스프링 컨테이너 [1/2] (0) | 2022.07.07 |
C++ 문자열(string) split (0) | 2022.06.18 |
스프링 기본편 - 객체지향의 개념 (0) | 2022.06.16 |