본문 바로가기

EFUB

[퍼비톡] Application Context

안녕하세요!

지난 9월 19일 오후 7시 EFUB 동아리에서 진행한 “퍼비톡”에서 application context에 대해서 발표를 진행했습니다.

피피티와 함께 발표 내용을 공유하고자 글을 작성하게 되었습니다.

영상링크 -> https://www.youtube.com/watch?v=tAXa8-qWewA&t=65s 

 

 

저는 스프링의 핵심 개념인 Application context 발표를 맡게 된 권지윤입니다.

🍀 목차 

 

 

먼저, 들어가기 전 중요한 개념들에 대해서 간단하게 설명하고

-> 스프링컨테이너 종류인 Beanfactory와 Application에 대해서 소개 후

-> springBootApplication 을 실행하면 어떤 일이 일어나는지 applicationContext 중심으로 살펴보겠습니다. 

 

🍀 1. 들어가며

 

공식문서에 따르면 ApplicationContext 인터페이스는 Spring IoC 컨테이너를 나타낸다고 합니다.
그러다면 spring 컨테이너는 어떤 것일까요?
 

💚[Spring Container]

 

 

저는 전적으로 믿어야하는 김주형쌤과 비슷하다고 생각했습니다.

 

스프링컨테이너는 “컨테이너” 혹은 ioc 컨테이너라고도 부르는데요,
그럼 우선 IoC가 어떤 개념인지 살펴볼까요? 
 

 

우리가 프레임워크 없이 개발할 때에는 "객체의 생명주기"(객체의 생성, 설정, 초기화, 메소드 호출, 소멸)을 프로그래머가 "직접 관리"해야합니다

하지만, 프레임워크를 사용하면 객체의 생명 주기를 모두 프레임워크에 대신 알아서 다 해줍니다.

 

이와 같이 "개발자가 작성한 객체나 메서드의 제어를 개발자가 아니라 외부에 위임하는 설계 원칙"을
제어의 역전이라고 한다.
 

 

스프링 컨테이너는 자바 객체의 생명주기를 관리하는데 이러한 자바 객체“스프링 빈”이라고 합니다.

 

 

그렇다면 스프링 빈에 대해서 구체적으로 알아볼까요?

 

💚[Spring Bean]

 
 
공식문서에 따르면 스프링 빈은 스프링 컨테이너에 의해 인스턴스화되고 조립되고 관리되는 객체라고 합니다.

그리고 스프링 IOC 컨테이너가 메타정보(BeanDefinition)만을 보고, 이를 기반으로 스프링 빈을 생성합니다.

 
BeanDefinition의 객체 필드는 다음과 같습니다.
클래스 이름과 빈의 동작 구성을 나타내는 스코프, 라이프 사이클 콜백 등이 있고 다른 객체와의 관계 의존성 등이 있습니다.

여기서 의존성이 나왔는데요!

 

 

이 의존성은 어떤 객체에 스프링 컨테이너가 또 다른 객체와 의존성을 맺어주는 행위라고 할 수 있습니다.

이 기능에 중점을 둔다면 스프링 컨테이너를 "DI 컨테이너"라고 부르기도 합니다!

 

정리를 하자면,

여기까지 스프링 컨테이너란, "스프링 빈(Bean)의 생성과 소멸 등의 라이프사이클을 관리하고, 빈들의 의존성을 관리한다"라고 말할 수 있을 것 같습니다.

 

 

🍀 2. BeanFactory or ApplicationContext? 

이 스프링 컨테이너에는 2가지 종류가 있는데요, BeanFactory와 이를 상속한 application context입니다.

그 두가지는 이렇게 상속관계라고 할 수 있습니다.

 

먼저, BeanFactory에 대해서 알아보겠습니다.
스프링 컨테이너의 최상위 인터페이스이며, 스프링 빈을 관리하고 조회하는 역할을 합니다.
공식 문서에는 두 컨테이너 중 특별한 이유가 없다면 application Context를 사용하라라고 되어 있는데요,
 
 
 

그 이유는 애플리케이션을 개발할 때는 빈 관리, 조회하는 기능외에 수많은 부가 기능이 필요하기 때문입니다.

 

 
공식문서에 나와있는 표인데요, 이를 보시면 빈을 생성하고 연관관계를 형성하는 작업은 모두 해당하지만 그 이외의 기능이 필요하다면 applicationcontext를 사용해야할 것을 알 수 있습니다. 

 

 

🍀 3. ApplicationContext?

그럼 이제 본격적으로 application context에 대해서 beanfactory와 비교하며 살펴보도록 하겠습니다.
 

Applicatoin context는 더 고급 생명 주기관리 기능을 제공합니다.

객체가 만들어지는 방식, 시점, 전략등을 다르게 할 수 있습니다.
그리고 후처리나 정보 조합 인터셉트 등과 같은 다양한 기능이 존재합니다.

더 자세히 살펴보겠습니다.

 

💚 [다양한 설정 형식 지원]

먼저, 다양한 설정 형식을 지원합니다.
 
BeanFactory은 주로 XML 기반의 설정을 사용하며, 기본적인 DI(Dependency Injection) 기능만 제공합니다.
 
하지만 ApplicationContext은 다음과 같이 어노테이션 등 여러 가지 형식을 지원합니다.

 

 

예시를 들어보겠습니다.
스프링 컨테이너는 빈 자동 주입을 해주는데요 각 빈 별로 지정이 가능합니다.
이때, 지정할 수 있는 방식에 대한 차이가 있습니다.

 

beanFactory는 다음과 같이 xml 로 작성하지만

 

 

application context의 경우 @Autowired 어노테이션으로도 자동 주입이 가능합니다.

 

💚 [Spring Bean의 생성 / 로딩 시점]

다음으로는 생성,로딩 시점입니다.

BeanFactory는 빈이 필요한 시점에 생성합니다 미루고 닥쳐서 하는 MBTI로 치면 P와 같은 면을 보입니다.

지연 로딩의 장점으로는 최대한 불러오는 것을 늦춰 메모리 운용의 효율성을 높일 수 있습니다.

 

반면, ApplicationContext는 컨테이너가 시작할 때 모든 싱글턴 빈을 미리 생성합니다. 이건 미리미리 하는 J와 같은 면을 보입니다.

즉시로딩은 애플리케이션 구동 시점에서 설정이나 주변 환경의 오류를 즉시 발견할 수 있는 장점이 있습니다.

 

 

아까 레이지 로딩의 장점으로 메모리 효율성을 높일 수 있다고 했는데 application context는 @Lazy 어노테이션으로 지연 초기화를 활성화할 수 있습니다.
 

 

💚 [ 후처리 ]

다음으로는 후처리 부분입니다.

applicationContext는 BeanFactory와 달리BeanFactoryPostProcessor과 BeanPostprocessor을 "자동으로 등록"해줍니다.

 

BeanFactoryPostProcessor는 빈에 대한 조작을 하기도 전에 빈 설정정보(BeanDefinition)를 불러오고 조작하는 것이고
BeanPostprocessor은 객체생성 후 초기화되는 시점 전과 후에 부가적인 작업을 하는 것입니다.
이렇게 들어보면 전자가 후자보다 앞에서 실행되는 느낌이 들죠?
맞습니다. 그림에서 보시는 것 처럼 전후에 각각 실행한다고 보시면 됩니다.
 

 

좀 더 자세히 알아보자면,

먼저, BeanFactoryPostProcessor애플리케이션 컨텍스트가 초기화되는 도중에 한 번만 호출되며
실제 빈들이 생성되기 전에 빈의 정의나 설정 메타데이터에 작동합니다.

일반적으로 빈의 스코프, 프로퍼티 값, 설정 메타데이터 등을 변경하거나, 런타임 시에 추가적인 설정이나 처리가 필요할 때 사용합니다.

postProcessBeanFactory메서드를 구현해서 사용하는데, 마지막에 SpringBootApplication.run()을 실행하는 과정 중에 등장할 예정입니다.

 

 

이에 대한 예시로 @Value 어노테이션이 있습니다.

이 코드는 제가 지금 하고 있는 프로젝트의 설정 파일과 JWT를 생성하는 클래스입니다. 

저희는 보통 application.yml 파일(혹은 프로퍼티스 파일)을 사용하여 애플리케이션의 여러 설정을 관리할 수 있는데요, 이 파일은 자동으로 로드되며 오른쪽 사진과 같이 @Value 애노테이션은 런타임 시에 해당 프로퍼티의 값을 해당 필드에 주입합니다.

 

 
 
다음으로 BeanPostProcessor는 모든 빈들이 만들어진 직후에 객체의 내용이나 객체 자체를 변경하기 위한 것인데요
Spring 컨테이너가 빈을 인스턴스화, 설정, 초기화를 완료하기 전이나 후에 특별한 로직을 구현하고 싶다면 하나 이상의 BeanPostProcessor 구현체를 연결할 수 있습니다.
다음과 같이 두 개의 콜백 메서드를 포함하고 있습니다.
 
예시로는 @Autowired 어노테이션이 있는데요 위와 같이 BeanPostProcessor구현체인 AutowiredAnnotationBeanPostProcessor이 있고
이것이 동작하면 @Autowired 라는 Annotation을 찾아 해당 타입의 Bean을 주입해 줍니다.
 

💚 [ BeanScope ]

빈스코프는 스프링 빈이 존재할수 있는 범위를 의미하는데요,
BeanFactory는 싱글톤과 프로토타입만을 지원하지만 application은 더 많은 타입을 지원합니다.

 

 

💚 [ 부가 기능 ]

공식 문서에는 'org.springframework.context" 패키지는 애플리케이션 프레임워크 지향적인 스타일의 추가 기능을 제공한다고 작성되어 있습니다. 아까 applicationContext는 빈생성, 의존성 주입 외에 수많은 부가 기능을 제공한다고 말씀 드렸는데 그 "부가 기능"에 해당하는 것이라고 볼 수 있습니다. 

application Context는 이러한 관계를 가지고 있고 하나씩 살펴보면

 

• 메시지 파일을 모아놓고 각 국가마다 지역화함으로써 각 지역에 맞춘 메시지를 제공할 수 있습니다.

 

 
파일, 클래스패스, 외부 등에서 리소스를 getResource로 편리하게 조회할 수 있습니다.

 

 
ApplicationEventPublisher인데요,
❤️
 
스프링의 event에 대해서 잠깐 설명하자면, 이벤트를 사용하는 이유는 서비스 간의 강한 의존성을 줄이기 위해서 사용한다고 보시면 됩니다.
 

 

만약 주문을 하면 "주문 처리 후 -> 푸쉬 알림 and 메일 전송"의 기능이 있다면 

주문 로직에 푸쉬 알림과 메일 전송 로직의 의존성 주입으로 인해 다음과 같이 의존성 주입이 많아집니다. (주문 로직에는 알림, 메일 전송 뿐만 아니라 다양한 로직이 있으니까 의존성 주입이 많아질 것입니다)

@RequiredArgsConstructor
@Service
public class 주문{
    private final 푸쉬알림 pushAlarmService;
    private final 메일전송 mailService;
    
    public 주문처리(){
    	//다른 로직
    	publicAlarmService.send();
        mailService.send();
    }
	
}

 

 

 

하지만 이벤트로 분리된 부분을 이렇게 이벤트로 구현한다면 의존성을 줄일 수 있고 이벤트는 비동기 방식으로 처리하기 때문에 전체 프로세스가 끝나는 시간도 짧아지게 됩니다.

@RequiredArgsConstructor
@Service
public class 주문{
    private final ApplicationEventPublisher publisher; //이벤트
    
    public 주문처리(){
    	//다른 로직
    	publisher.publishEvent(new OrderedEvent(productName));
    }
	
}

 

 

다음으로는 environment capable(케이퍼블)은 로컬, 개발, 운영 등을 구분해서 처리할 수 있게합니다.

 
막 내용이 쏟아져 나오니까 힘드시죠 마지막입니다

 

4. SpringBootApplication.run() 실행 동작 과정

 

이거 많이 실행해보셨죠? 실행할 때 어떤 동작 과정이 있는지 순차적으로 살펴보겠습니다.

애플리케이션 컨텍스트 생성 전후를 기반으로 나눠서 설명드리겠습니다.

 

[ 애플리케이션 컨텍스트 생성 전 ]

 

1. StopWatch(스탑워치)로 실행 시간 측정 시작합니다.

 

2. 다음으로 BootStrapContext 생성

이는 애플리케이션 컨텍스트가 준비될 때 까지 임시 컨텍스트로, 환경 변수들을 관리하는 스프링의 Environment 객체를 후처리하기 위한 것입니다. 

 

3.  Java AWT Headless Property 설정

디스플레이 장치가 없는 서버 환경에서 UI 클래스를 사용할 수 있도록 하는 옵션입니다. 

 

4. 스프링 애플리케이션 리스너 조회 및 starting 처리

애플리케이션 컨텍스트를 준비할 때 호출되어야하는 리스너들을 찾아서 BootStrapContext(부트스트랩컨텍스트)의 리스너로써 실행하게 합니다.

 

5. Arguments(아규먼트) 래핑 및 Environment 준비

애플리케이션 타입에 맞게 Environment 구현체를 생성합니다. 

 

6. IgnoreBeanInfo 설정

자바 빈즈 작업을 처리하지 않도록 설정해주는 부분입니다.

 

 

7. 배너 출력

이거 많이 보셨죠? 이때 지금 생성되는 것입니다.
 
 
 

[ 애플리케이션 컨텍스트 생성 이후 ]

 
 
이제는 애플리케이션 컨텍스트가 생성된 이후입니다.
 

8. 애플리케이션 컨텍스트 생성

애플리케이션 컨텍스트를 생성하는 코드는 다음과 같은데, 팩토리 클래스에게 생성을 위임하는 것을 확인할 수 있습니다.

 

 

9. Context 준비 단계를 거쳐

 

10. Context Refresh 단계가 되면

여기서 refresh는 빈 팩토리가 생성되고 Context가 설정되고 나서야 할 수 있는 처리들 의미한다고 할 수 있습니다. 

코드를 통해 자세히 봅시다

 

1) 먼저 refresh 준비를 합니다.

2) BeanFactory를 준비하고

3) 아까 살펴보았던 BeanFactoryPostProcessor 메소드를 실행합니다.

4) 그리고 빈으로 등록될 객체들의 정보를 등록합니다. (실제 빈등록이 아닙니다)

5) 그 다음 또 아까 살펴보았던 BeanPostProcessor을 등록합니다.

 

6) MessageSource를 초기화합니다.
7) EventMulticaster를 초기화하고
8) 웹서버가 생성됩니다.
9) 그 다음 Application Listener 조회하고 등록합니다
10) 그 다음 빈을 이제 등록하고 후처리를 합니다.
 

11. Context Refresh 후처리 하고

 
 

12. 실행 시간 출력 및 리스너 started 처리합니다.

아래의 로그 많이 보셨죠?

 
 

 

13. 마지막으로 Runners 실행됩니다

callRunners(context, applicationArguments);​

 

네! 이렇게 길고 긴 여정이 끝났습니다!
단순히 구현만 하는 게 아니라 원리에 잘 알고 구현하시면 더 좋은 코드를 작성하실 수 있을 것 같습니다
읽어주셔서 감사합니다!
 
 

'EFUB' 카테고리의 다른 글

[세션] 온보딩_백엔드  (0) 2022.03.30
[세션] 온보딩_프론트엔드  (0) 2022.03.26
[활동] OT  (0) 2022.03.18