Application 등록 과정 - Generic 1편

Dev StoryTeller·2021년 2월 10일
0

일반 Context 등록은 앞서 봤던 AbstractApplicationContext와 GenericXmlApplicationContext 등을 이용한다.

1. Generic 사용 방법

우선 Generic의 사용 방법부터 알아보자.

// Context 생성
GenericApplicationContext context = new GenericApplicationContext();

// xml 파일을 불러옴
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(context);
xmlReader.loadBeanDefinitions(new ClassPathResource("test.xml"));

// properties 파일을 불러옴
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(context);
propReader.loadBeanDefinitions(new ClassPathResource("testBean.properties"));

// context 초기화
context.refresh();

xml 파일은 알다시피 컨텍스트를 설정하는 파일이고,
properties 파일속성을 설정하는 파일인데, key-value 형식의 간단한 설정에 쓰인다.


2. 전체적인 과정

이번 코드의 전체적인 흐롬은 다음과 같다.

  1. Ant 패턴 형식의 경로(문자열) 입력

  2. ClassPathResource가 Resource 형태로 바꿔줌

  3. 생성된 리더가 Resource를 읽고 내부의 Bean 정의 등록

  4. refresh() 메소드로 Bean을 등록

2. 세부 과정

1. Context 생성

GenericApplicationContext context = new GenericApplicationContext();

빈 ApplicationContext를 하나 생성한다.

말은 간단하지만 사실 굉장히 설정하는 것이 많다.
Bean을 생성하는 BeanFactoryApplication Listener, Logger, 잠시 뒤 중요하게 다룰 BeanFactoryPostProcessor 등 Context 자체를 설정한다.

지금 여기서 눈여겨 봐야 할 Context 속성들은 다음 2가지이다.

  • BeanFactory - DefaultListableBeanFactory
  • BeanFactoryPostProcessors - 아직 null

두 클래스 모두 Bean 생성에 매우 중요한 클래스이다.
BeanFactory는 당연하게도 Bean을 생성하는 중요한 클래스이다.
BeanFactoryPostProcessorsBean이 등록되기 전/후로 해야 할 작업들을 하는 클래스인데,
@Autowired가 작동되는 이유가 바로 이 덕분이다.

잠시 뒤 refresh()에서 중요하게 쓰이니, 꼭 알아두자!


2. xml 리더 생성

// 리더기 생성
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(context);

XML 파일을 읽을 수 있는 xmlReader를 생성하는 과정이다.
하나씩 천천히 살펴보자


1. XmlBeanDefinitionReader - 생성자 실행

XML에서 Bean을 읽기 위한 리더이다.
xml 파일을 파싱하고 BeanDefinition을 등록하는 등의 역할을 한다
하지만 간단한 설정만 하고, 실제 역할은 BeanDefinitionDocumentReader가 담당한다.

내부적으로 보면, 구현한 인터페이스인 AbstractBeanDefinitionReader를 생성함을 볼 수 있다.
좀더 자세히 들어가보자.


2. AbstractBeanDefinitionReader - 공통 속성 정의

모든 Bean 리더의 공통 속성을 정의하기 위한 추상 클래스이다.

여기선 XML 파일을 읽기 위해 리소스 로더를 설정하고,
Context의 환경을 상속받는 역할을 한다.

각각 if문에서 앞서 정의한 리소스 로더와 환경을 설정한다.

리소스 로더도 위처럼 Context로 설정이 되는데,
이것 또한 Context가 ResourLoader를 구현/상속받은 클래스라 가능한 것이다.

따라서 공통 리소스 로더 속성은 다음과 같다.

  • resourceLoader - GenericApplicationContext

3. XmlBeanDefinitionReader - 리더 속성 정의

이제 다시 돌아가서 공통이 아닌 xml 리더의 속성을 정의한다.
여러가지 속성을 정의 하지만, 역시 살펴봐야 할 것은 다음과 같다.

  • documentReaderClass - DefaultBeanDefinitionDocumentReader
  • documentLoader - DefaultDocumentLoader

3. XML 파일을 Context에 로딩하기

// test.xml 설정 파일을 로딩
xmlReader.loadBeanDefinitions(new ClassPathResource("test.xml"));

이제 Context를 만들었으니, 설정 파일인 test.xml을 Context에 입히는 과정이다.


1. Resource로 변환하기

우리는 파일을 Ant 패턴 경로로 적어서 인자를 넘기는데, 이때 문자열 형식으로 입력하게 된다.

하지만 보다시피 loadBeanDefinitions()는 인자가 Resource 타입이다.

그렇다면 문자열을 Resource 타입으로 바꿔줄 방법이 필요하다.
그 방법에는 2가지가 있다. 모두 알아보자.

  • ResourceLoader를 이용하는 방식
    ResourceLoader의 getResource() 메소드를 사용하는 방법이다.
    방금 전, resourceLoader를 Context로 설정했었다.
    때문에 Context의 getResource()를 사용할 것이다.
context.getResource("test.xml");

getResource()String 형태의 경로를 Resource 타입으로 바꿔준다.
이에 관련한 자세한 과정을 알고 싶다면, 여기를 참조하자.


  • ClassPathResource를 이용하는 방식
    ClassPathResource는 사실상 Resource의 구현체나 마찬가지 이므로, 가장 직관적인 방법이라고 할 수 있다.
// Resource 타입으로 반환
new ClassPathResource("test.xml");

당연히 반환 타입은 Resource이다.


2. 리소스 로딩하기

마지막은 Resource를 Spring이 읽을 수 있게끔 로딩하는 과정이다.

Resource가 Document로 타입이 변환되어, 파싱되고 등록된다.


  • Resource 타입 변환
    Resource는 오버로딩된 load 메소드를 몇단계를 거치며, 타입이 변화한다.

    Resource -> EncodedResource -> InputSource


  • Document 변환
    여기선 Resource가 Document로 한번 더 변환되며, 드디어 등록되는 과정이다.
    위의 doLoadDocument 메소드에서 바로 DocumentLoader가 사용된다.
    보다시피, Document 타입으로 변환해주는 용도이다.


  • Bean 정의 등록
    여기선 DocumentReader가 사용된다.
    Loader와 달리, 본격적으로 Document를 파싱하여 읽기 위한 클래스이다.
    몇 개의 BeanDefinition을 적용했는지 확인하기 위해,
    기존의 갯수를 확인(countBefore)하고 리더로 총 갯수 확인하여, 그 차이만큼을 반환한다.

    파싱하는 과정은 Document를 node 리스트로 분류하여, 각 노드를 읽어내는 방식이다.
    그리고 각각의 node는 어떤 종류인지에 따라 다르게 파싱한다.

3. 중간 점검

이쯤에서 지금껏 눈여겨 봐야할 속성들을 정리해보도록 하겠다.
Context 속성

  • BeanFactory - DefaultListableBeanFactory
  • BeanFactoryPostProcessors - 아직 null

Reader 속성
= 공통

  • resourceLoader - GenericApplicationContext

= 개별

  • documentReaderClass - DefaultBeanDefinitionDocumentReader
  • documentLoader - DefaultDocumentLoader

4. Bean 등록하기

이번 편에서 가장 중요한 부분이다!
사실 앞의 내용은 기억할 필요는 없다. 이 부분만 알고 있어도 충분하다.
그럼 한번 살펴보자.

뭔가 굉장히 많지만, 주석이 코드 한줄에 하나씩 달려있어서 뜯어보기 어렵지 않다.


0. 전체적인 과정

<<준비 과정>>
1. Context와 BeanFactory 사용 준비

<<BeanFactory 관련>>
2. BeanFactory 설정
3. BeanFactoryPostProcessor 실행
4. BeanPostProcessor 실행

<<싱글톤 등록(Bean, 리스너 등)>>
5. 메세지 소스/이벤트 멀티 캐스터 초기화
6. 특수 Bean(테마 소스) 등록
7. 리스너 등록
8. 남아있는 모든 싱글톤(Bean 등) 등록

<<Refresh 종료>>
9. Context refresh 과정 종료
10. Bean 삭제 및 Context 비활성화

0. 준비 과정

간단한 Context 초기화와 BeanFacotyry를 설정한다.
BeanFactory는 앞에서 말한 설정한 것을 사용한다.

  • BeanFactory - DefaultListableBeanFactory

아래는 prepareBeanFactory으로 BeanFactory를 사용할 준비를 하는 메소드인데, 보면 어떤 느낌인지 알 수 있다.


2. postProcessBeanFactory()

이름 그대로 BeanFactory 설정과 관련된 메소드이다.
Bean을 등록하기 전에 BeanFactory의 설정을 변경하기 위해 사용된다.

위의 코드는 GernericContext의 경우인데 첫 줄부터 해석해보면, BeanPostProcessor을 사용하여 BeanFactory가 알아서 ServletContext를 등록하도록 하였고,
ignore~ServletContext와 관련된 클래스는 BeanFactory가 작업하지 못하도록 무시 하였다.

즉 간단히 말해, ServletContext를 @Autowired 하기위한 작업인 것이다.

이를 사용할 때는 다음과 같이 작성하면 된다.

@Controller
public TestCon implements ServletContextAware {
   // 사용할 ServletContext 지정
   private ServletContext servletContext;
   
   // Context에 ServletContext 설정
   @Override
   public void setServletContext(ServletContext servletContext) {
      this.servletContext = servletContext;
   }

ServletContext는 Bean이 아니기 때문에, @Autowired로 주입할 수 없다.
때문에 사용하려면 이런 방식을 택해야 한다.

하지만 우리는 해당 사항이 없으므로 그냥 넘어간다.


3. invokeBeanFactoryPostProcessors()

BeanFactory의 후처리 메소드이다.
이미 Bean 정의 등록은 끝났지만, Bean이 초기화 되기 전에 해야할 작업이 있다면 이를 구현하여 사용한다.
(아직 써본적이 없어서, 언제 사용하는지 잘 모르겠다)

사용 방법은 이러하다.

public class TestBeanFactory implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (Strint beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);

        }
    }
}

이것 역시 해당 사항이 없으므로, 그냥 넘어간다.


4. registerBeanPostProcessors()

가장 먼저 처리해야 할 Bean 등록을 진행한다.
우리가 진짜 자주 사용하는 @Autowired가 이 단계에서 진행된다.
PriorityOrderd, Orderd, 나머지로 분류되어 등록하며, @Autowired의 경우 나머지PostProcessors로 분류되어 등록된다.

주석이 워낙 잘 달려있어서 직접 읽어보는 것을 추천한다.


5. onrefresh()

특수 Bean을 등록한다곤 하지만, 살펴보니 모두 ThemeSource를 등록하였다.

테마소스stylesheet(CSS 등), image 등의 정적 리소스들을 말한다.
이를 이용하면 우리가 자주 사용하는 테마 변경하기 기능을 손쉽게 구현할 수 있다.


6. finishBeanFactoryInitialization()

마지막으로 남은 모든 싱글톤들을 등록하는 과정이다.
별거없는 평범한 Bean들은 모두 여기서 등록된다.


6. destroyBean()


3. 결론

드디어 대장정이 막을 내렸다...
정신없이 코드를 뜯다보니, 정말 쓸데없는 것까지 살펴본 듯 하다...
(사실 리더 부분을 뜯어본 건 너무 시간 낭비였다.
정말 중요한 건 refresh() 부분인데...ㅇㅁㅇ)

그래도 이왕 열심히 살펴본 내용인데 안넣을 수는 없었다.
이번을 교훈삼아 어느정도로, 어떻게 코드를 살펴봐야 하는지 감을 잡은 것 같아 유익한 공부였다.

이어서>>

profile
개발을 이야기하는 개발자입니다.

0개의 댓글