mm Home

ComponentScan 동작 원리 본문

개발/Spring

ComponentScan 동작 원리

jess_m 2017. 9. 14. 15:08

대학생때 서블릿으로 개발을 하다가, 입사 이후 스프링을 처음 사용할 때 신기했다. 어떻게 동작하는지 궁금하지 않은가??


스프링 부트를 사용하면서부터는 component scan 도 관습에 의해 설정할 필요도 없으니 스프링 부트만 사용해본 개발자는 스프링 내부를 더 알기 어렵지 않을까 하는 생각이 들었다. 

어노테이션 기반의 개발 과정이 편리하지만 그 내부를 들여다보기 어려운 점이 있었는데 이번 달에 릴리즈될 스프링 5에서는 다시 처음으로 돌아가는 개발을 할 수 있다고 하니 좀 더 내부를 이해하기 쉬울것 같다는 생각도 들었다.

예전에 스프링을 공부하면서 context와 mvc쪽을 살짝 공부한적이 있었는데 정리해본다.

깊이있게 좀 파악하고 싶은데.. 언제나 깊이있는 공부는 어려운것 같다.





어떻게 component-scan이 동작하고 Bean이 주입되는가?


AnnotationConfigApplicationContext를 쭉 따라가면서 공부를 해보았다.

설정도 많다보니 워낙 복잡하다. 우선 대략적으로 component-scan이 어떻게 진행되는지 살펴보자.



1. Configuration 파싱

ConfigurationClassParser 가 Configuration 클래스를 파싱한다.


2. ComponentScan 설정 내역을 파싱한다.

개발자는 basePackages, basePackesClasses, excludeFilters, includeFilters, lazyInit, nameGenerator, resourcePattern, scopedProxy 등 컴포넌트들을 스캔하기 위한 설정을 할 것이다. ComponentScanAnnotationParser가 컴포넌트 후보를 모두 찾고, 스캔하기 위하여 해당 설정을 파싱하여 가져온다.


3. Class 로딩

위의 basePackage 설정을 바탕으로 모든 클래스를 로딩해야 한다. ( *.class )

클래스로더를 이용하여 모든 자원을 Resource 인터페이스 형태로 불러온다.


4. 빈 정의 설정

클래스 로더가 로딩한 리소스(클래스)를 BeanDefinition으로 정의해놓는다. 그리고 beanName의 key값으로 BeanDefinitionRegistry에 등록해 놓는다. 생성할 빈에 대한 정의(메타데이터 같은)라고 보면 될것 같다.


5. 빈 생성 & 주입

다시 처음으로 돌아가 AbstractApplicationContext에서 보이는 finishBeanFactoryInitialization(beanFactory); 메소드에서 빈을 생성한다.

위에서 설정한 빈 정의를 바탕으로 객체를 생성하고, 주입한다.



쉽게 설명해서

Configuration 클래스 및 Annotation에 사용하는 설정들을 파싱한다. 그리고 basePackage 밑의 모든 .class 자원을 불러와서 component 후보인지 확인하여 BeanDefinition (빈 생성을 위한 정의)을 만든다. 생성된 빈 정의를 바탕으로 빈을 생성하고 의존성있는 빈들을 주입한다.



http://yonggar-ri.tistory.com/entry/Spring-ComponentScan-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EB%B6%84%EC%84%9D

코드 설명은 위 블로그에서 자세히 알 수 있을것 같다.





대충 알겠다. 그럼 Spring을 안쓰고 의존성 주입하는 코드를 짜볼까?


그냥 보기만 하면 잘 모르겠다. 직접 구현해보자.

물론 아주 간단한 기능이며, @Component를 통해 싱글톤 빈 생성과, @Autowired 로 필드에 의존성 주입하는 기능을 직접 구현해보았다.

이름이 Spring의 클래스와 같으니.. @MyComponent, @MyAutowired로 바꾸어보았다.


실제 Service 부분도 아래와 같이 스프링을 사용할 때와 동일하다. 


 


위에 MyApplicationContext라는 커스텀 컨텍스트를 basePackage로 초기화하고 getBean 하여 사용한 부분이다.

스프링의 AnnotationConfigApplicationContext 가 어노테이션 기반의 Context를 가져오는 것과 동일하게 사용할 수 있도록 구현했다.





어떻게 구현했는지 살펴보자.



위에서 설명한 것처럼 invokeBeanFactoryPostProcessors에서 클래스로더를 통해 resources를 모두 불러와 BeanDefinition을 정의한다. 

finishBeanFactoryInitialization 에서 정의된 BeanDefinition을 가지고 빈 생성을 하고, 의존성 있는 곳에 주입한다.


자세히 쓰려고 했는데 글로 쓰기도 어렵고.. 직접 보는게 나을것 같다.


https://github.com/lsm6654/dependency-injection에 코드를 올려놓았다.



외부 라이브러리를 전혀 사용하지 않고 구현하려고 했는데, ClassLoader에서 모든 리소스를 불러오는 부분에서 com.google.common.reflect.ClassPath 를 사용했다. 모든 자원을 basePackage 밑으로 recursive 하게 불러와야하는데.. 구현하기가 쉽지 않을것 같아서 해당 부분만 외부 라이브러리를 사용했다.



'개발 > Spring' 카테고리의 다른 글

Spring 5 features  (0) 2017.11.10
Spring MVC - DispatcherServlet 동작 원리  (2) 2017.09.26
Comments