Spring Core Basic_2
Date:
스프링 컨테이너
- ApplicationContext, BeanFactory 를 스프링 컨테이너라 한다.
- BeanFactory를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext를 스프링 컨테이너라고 한다.
- ApplicationContext는 인터페이스이다. (BeanFactory 인터페이스를 상속받는다)
-
BeanFactory는 스프링 컨테이너의 최상위 인터페이스다.
- getBean()을 비롯한 많은 기능(빈 관리 기능)을 BeanFactory가 제공한다.
-
ApplicationContext가 BeanFactory가 제공하는 기능 이외에 추가적으로 제공하는 기능들은 다음과 같다.
- MessageSource 를 활용한 국제화 기능
- 로컬 개발 환경, 테스트 서버 환경(개발 환경), 실제 프로덕션에 나가는 운영 환경, 스테이지 환경(운영과 가장 비슷한 환경) 별로 연결해야하는 데이터베이스가 다르다. 그러한 환경 변수와 관련된 정보를 처리해주는 기능
- 애플리케이션 내 이벤트를 발행하고 구독하는 모델을 편리하게 지원해주는 기능
- 파일, 클래스패스, 외부 등에서 추상화를 통해 리소스를 편리하게 조회하는 기능
- 스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어 있다. (자바 코드, XML, Groovy)
- 스프링은 BeanDefinition이라는 추상화를 통해 다양한 설정 형식을 지원한다.
- 자바코드(Annotation 방식)이든, Xml이든, Groovy이든 스프링 컨테이너는 BeanDefinition 인터페이스(역할)을 통해 정보를 받아들인다.
- BeanDefinion을 빈 설정 메타정보라고 한다.
- @Bean,
당 각각 하나씩 메타정보가 생성된다. - BeanDefinition 생성
- XML 은 GenericXmlApplicationContext의 XmlBeanDefinitionReader를 통해 appConfig.xml 설정 정보를 읽고 BeanDefinition을 생성한다.
- 자바코드(Annotation) 방식은 AnnotationConfigApplicationContext의 AnnotatedBeanDefinionReader를 통해 appConfig.class 를 읽고 BeanDefiniton을 생성한다.
- BeanDefinition 정보
- BeanClassName: 생성할 빈의 클래스 명( 자바 설정처럼 팩토리 역할의 빈을 사용하면 없음(null) )
- xml 경우에는 hello.core.member.MemberServiceImpl 와 같이 클래스 명이 구체적으로 들어있다.
-
factoryBeanName: 팩토리 역할의 빈을 사용할 경우(자바 코드(Annotation)방식으로 등록하는 경우) ex) appConfig
-
factoryMethodName: 빈을 생성할 팩토리 메서드 ex)memberService, memberRepository, orderService, discountPolicy
-
Scope: 빈 스코프란 빈이 사용되는(존재할 수 있는) 범위를 말한다.
@Scope 어노테이션 (ex : @Scope(value = “prototype”) )을 사용해서 빈 스코프를 정의해 줄 수 있다.
xml에서는 <bean … scope =”prototype”> 이런 식으로 정의한다.
(참고 링크1) (참고 링크2) (참고 링크3) (참고 링크4) (참고 링크5)
-
singleton P: 기본 스코프, 스프링 컨테이너의 시작과 죵료까지 유지되는 가장 넓은 범위의 스코프
-
prototype:
- 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
- 하나의 빈 정의에 대해서 다수의 객체가 존재할 수 있음
- 요청이 오면 항상 새로운 인스턴스를 생성하여 반환하고 이후에 관리하지 않음
- 빈을 받은 클라이언트가 빈을 관리해야 함
-
request: 일반 Spring 애플리케이션이 아닌 Spring MVC Application 에서만 제공
- Web-aware Spirng ApplicaitonContext 안에서만 유효
- 각각의 HTTP 요청별로 인스턴스화 되며 요청이 끝날 때까지 유지되는 스코프
- 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리됨.
-
session: 일반 Spring 애플리케이션이 아닌 Spring MVC Application 에서만 제공
- Web-aware Spirng ApplicaitonContext 안에서만 유효
- 하나의 Bean 정의에 대해서 하나의 HTTP Session의 생명 주기 안에 단 하나의 객체만 존재
- HTTP Session 과 동일한 생명주기를 가지는 스코프
- HTTP Session 별로 인스턴스화 되고 세션이 끝나면 소멸됨
-
global session: 일반 Spring 애플리케이션이 아닌 Spring MVC Application 에서만 제공
- Web-aware Spirng ApplicaitonContext 안에서만 유효
- 하나의 Bean 정의에 대해서 하나의 global HTTP Session의 생명주기 안에 단 하나의 객체만 존재
- 일반적으로 portlet context 안에서 유효
- 포틀릿 스펙은 하나의 포틀릿 웹 응용 프로그램을 구성하는 모든 포틀릿들 사이에서 공유되는 글로벌 세션의 개념을 정의한다.
-
application: 일반 Spring 애플리케이션이 아닌 Spring MVC Application 에서만 제공
- Web-aware Spirng ApplicaitonContext 안에서만 유효
- 웹의 서블릿 컨텍스트(ServeletContext)와 같은 범위로 유지되는(동일한 생명주기를 가지는) 스코프
-
websocket: 일반 Spring 애플리케이션이 아닌 Spring MVC Application 에서만 제공
- Web-aware Spirng ApplicaitonContext 안에서만 유효
- 웹 소켓과 동일한 생명주기를 가지는 스코프
-
thread:
- 새 스레드에서 요청하면 새로운 인스턴스 생성
- 같은 스레드의 요청에는 항상 같은 인스턴스가 반환됨
-
custom:
- org.springframewrok.beans.factory.confgi.Scope 를 구현하여 커스텀 스코프를 스프링 설정에 등록하여 사용
-
-
lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 지연처리 하는지 여부
-
InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
-
DestoryMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정처럼 팩토리 역할의 빈을 사용하면 없음)
- BeanClassName: 생성할 빈의 클래스 명( 자바 설정처럼 팩토리 역할의 빈을 사용하면 없음(null) )
- @Bean,
스프링 컨테이너의 생성 과정
-
스프링 컨네이터 생성
-
스프링 컨테이너 안에는 빈 이름을 KEY 값으로, 빈 객체를 VALUE로 가지는 스프링 빈 저장소가 있다.
-
스프링 컨테이너를 생성할 때는 구성 정보(XML 파일이나 Configuration 어노테이션을 붙인 자바 클래스)를 넘겨주어야 한다.
-
-
스프링 빈 등록
- 빈 저장소에 빈을 등록해준다.
- @Configuration 자바 클래스의 경우, @Bean을 붙인 메소드 이름이 빈 이름이 되고 반환 객체가 빈 객체가 된다.
- 빈 이름을 직접 부여할 수도 있다. ( @Bean(name = “beanName”) )
- 빈 이름은 중복되어서는 안된다. 다른 빈이 무시되거나, 기존 빈을 덮어버려서 설정에 따라 오류가 발생한다.
- 빈 저장소에 빈을 등록해준다.
-
스프링 빈 의존관계 설정 - 준비
-
스프링 빈 의존관계 설정 - 완료
- 설정 정보를 참고해서 의존관계를 주입(DI)한다.
스프링 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다.
하지만 이렇게 자바 코드로 빈을 등록하면 생성자 호출과정에서 의존관계 주입도 한번에 처리된다.
스프링 빈 등록 확인
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
- BeanDefinition.ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
- BeanDefinition.ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
ac.getBeanDefinitionNames ()
: 스프링에 등록된 모든 빈 이름을 조회한다.
ac.getBean()
: 빈 이름으로 빈 객체(인스턴스)를 조회한다.
ac.getBean(빈이름, 타입)
ac.getBean(타입)
두 가지 방법으로 조회가 가능하다.
ac.getBeansOfType(SomeType.class)
을 사용하면 해당 타입의 모든 빈을 조회한다.
부모 타입으로 조회하면, 자식 타입을 포함하여 조회한다.
싱글톤 컨테이너
- 매 요청마다 객체를 새로 생성하면 메모리 낭비가 심하다.
- 객체를 1개만 생성해서 공유하면 해결할 수 있다. (싱글톤 패턴)
싱글톤 패턴 문제점
- 구현 코드량이 많다
- 구체 클래스에 의존해서 DIP, OCP를 위반한다. (구체클래스. getInstance() 이런 식으로) (참고 링크)
- 테스트가 어렵다. (참고 링크)
- 내부 속성을 변경하거나 초기화 하기 어렵다
- private 생성자로 자식 클래스를 만들기 어렵다.
- 유연성이 떨어진다.
- 안티패턴이다.
스프링 컨테이너의 싱글톤 (싱글톤 컨테이너)
스프링 컨테이너는 싱글톤 패턴의 문제점은 해결하면서 장점은 유지한다.
- 지저분한 코드가 필요없다
- DIP,OCP를 위반하지 않으고 테스트가 용이하며 private 생성자로부터 자유롭다.
싱글톤 객체를 생성하고 관리하는 기능(싱글톤 레지스트리)를 가지고 있다.
싱글톤 방식의 주의점
- 싱글톤 방식은 여러 클라이언트에 공유되기 때문에 상태를 유지(stateful)하게 설계하면 안된다.
- 무상태(stateless)로 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 굥유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
- 스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다.
@Configuration
@Configuration은 스프링 컨테이너가 싱글톤 레지스트이도록 만들어준다.
- @Configuration 어노테이션을 붙이면, 스프링이 CGLIB이라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 를 상속받은 임의의 다른 클래스를 빈으로 등록한다.
- 그 임의의 다른 클래스가 싱글톤이 보장되도록 해준다.
- @Configuration이 없으면, 매번 새로 인스턴스가 생성되고 (new MemoryMemberREpository()), 이 인스턴스들은 스프링 컨테이너로 관리되는 빈도 아니다.
@Autowired MemberRepository memberRepository;
이런식으로 @Autowired를 사용하여 의존관계 주입을 구현할 수도 있다.- 스프링 설정 정보는 항상
@Configuration
을 사용하자.
컴포넌트 스캔
@Bean이나 XML의
그리고 그에 따라 누락하는 문제와 반복이 발생한다.
그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
또 의존관계도 자동으로 주입하는 @Autowired
라는 기능도 제공한다.
컴포넌트 스캔을 사용하려면 @ComponentScan을 @Configuration과 함께 설정 정보에 붙여주면 된다.
그러면 @Component 애노테이션이 붙은 클래스들을 스캔해서 빈으로 등록한다.
excludeFilters를 이용해서 컴포넌트 스캔 대상에서 제외할 수 있다.
@ComponentScan(excludeFilter = @Filter(type = FilterType.ANNOTATION, classes= Configuration.class))
@Bean으로 직접 설정 정보를 작성했을 때는, 의존 관계가 메소드 안에서 명시되었지만,
@ComponentScan의 경우에는 @Autowired로 의존관계를 자동으로 주입해준다. (생성자나 Setter, 필드에 붙인다. 주로 생성자에!)
컴포넌트 스캔을 할 때, @Component가 붙은 클래스의 클래스명을 기본 빈의 이름으로 사용하되, 맨 앞글자만 소문자를 사용한다.
- 기본 빈 이름 : RateDiscountPolicy -> rateDiscountPolicy
- 빈 이름 직접 지정: @Component(“rateDiscountPolicy2”) 이런 식으로 직접 이름을 부여할 수도 있다.
탐색 위치와 기본 스캔 대상
@ComponentScan(
basePackages = "hello.core.member",
basePackageClasses = AutoAppConfig.class,
excludeFilter = @Filter(type = FilterType.ANNOTATION, classes= Configuration.class)
)
-
basePackages : 탐색할 패키지의 시작 위치를 지정한다. 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.
-
basePackages ={"hello.core", "hello.service"}
이렇게 여러 패키지의 시작위치를 지정할 수도 있다.
-
-
basePackageClasses: 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.
-
아무것도 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
- 이런 성질을 활용하여, 아무것도 지정하지 않고 정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이 권장된다.
- 프로젝트 메인 설정 정보는 프로젝트를 대표하는 정보이기 때문이라도 프로젝트 시작 루트 위치에 두는 것이 좋다고 여겨진다.
- 스프링 부트의 대표 시작 정보인 @SpringBootApplication를 이 프로젝트 시작 루트 위치에 두는 것이 관례이다.
- @SpringBootApplication 안에 @ComponentScan이 들어있다.
-
컴포넌트 스캔은 @Component 뿐만 아니라 다음 어노테이션들에도 포함되어있다. (해당 클래스의 소스 코드를 살펴보자)
- @Controller: 스프링 MVC 컨트롤러로 인식
- @Service:
- 스프링 비즈니스 로직에서 사용하는데 사실 @Service는 특별한 처리를 하지 않는다.
- 대신 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다.
- @Repository: 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 추상화하여 스프링 예외로 변환해준다.
- @Configuration: 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
-
애노테이션에는 상속관계라는 것이 사실 없다. 이렇게 애노테이션이 특정 애노테이션을 들고 있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능이 아니고, 스프링이 지원하는 기능이다.
필터
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE) //ElementType.TYPE: 클래스 레벨에 붙는다는 뜻
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
이런 식으로 애노테이션을 만들어 줄 수 있다.
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
이렇게 빈으로 등록할 클래스와 등록하지 않을 클래스를 지정할 수 있다.
필터 옵션 (FilterType의 5가지 옵션)
- ANNOTATION: 기본값, 애노테이션을 인식해서 동작
- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.
- ASPECTJ: AspectJ 패턴 사용
- REGEX: 정규 표현식
- CUSTOM: TypeFilter 라는 인터페이스를 구현해서 처리
@Component로 충분해서 includeFilters는 거의 사용하지 않는다.
excludeFilters는 가끔 사용한다.
최근 스프링 부트는 컴포넌트 스캔을 기본으로 제공하기에, 옵션을 변경하여서 사용하기보다 스프링의 기본 설정에 최대한 맞추어 사용하는 것을 권장한다.
중복 등록과 충돌
-
자동 빈 등록 vs 자동 빈 등록 충돌은 에러가 난다.
-
예를들어 @Component("service") 를 두개의 클래스에 붙여줬을 때
-
-
수동 빈 등록 vs 자동 빈 등록 충돌은 수동 빈 등록이 우선권을 가진다.(수동 빈이 자동 빈을 오버라이딩 해버린다.)
-
@Bean(name = "memoryMemberService") 로 등록하여 자동 빈 등록과 겹칠 때
-
이 때 다음과 같은 로그를 남겨준다.
-
Overriding bean definition for bean '클래스이름' with a different definition: replacing
-
-
테스트 코드를 통해 Spring을 돌리면 이와 같이 되지만 스프링 부트에서는 오류를 낸다.
-
위와 같은 결과는(오버라이딩 하는 결과는) 개발자가 의도하지 않고 발생할 수도 있고, 이럴 경우 정말 잡기 어려운 버그가 된다.
-
그래서 최근 스프링부트는 오류가 나도록 기본 설정 값을 바꾸어 주었다.
-
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
-
-
스프링 부트에서도 오버라이딩 하게 바꾸고 싶다면, resources/application.properties에 다음과 같은 설정을 적어준다.
-
spring.main.allow-bean-definiton-verriding=true
-
-
개발할 때는 명확하지 않은 것은(애매한 것은) 최대한 피하자! 개발은 혼자 하는 것이 아니다.
어설픈 추상화는 잡기 어려운 버그가 된다.
-
-
Reference
이 글은 김영한님의 스프링 핵심 원리 - 기본편을 보고 정리해서 작성하였습니다.
[2] [Spring] 스프링 XML 설정 파일 작성 방법 정리
[3] 스프링 컨테이너(Spring Container)를 만드는 2가지 방법 (XML, Annotation) 출처
[5] [Spring] Spring Bean의 개념과 Bean Scope 종류
[6] [Spring] Bean Scope(빈 스코프)의 종류
[7] Bean 스코프란?
[8] 스프링 Bean,Singleton,Bean Scope
[9] [java] 포틀릿과 서블릿의 차이점은 무엇입니까?
[10] [[Spring] Spring Bean Scope 종류](https://charming-jung.tistory.com/54)
댓글