일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- kotlin
- lambda
- watermark
- flink watermark
- contravariant
- Perfect Watermarks
- event time
- Hadoop
- Perfect Watermark
- covariant
- Coroutine
- processing time
- Heuristic Watermark
- coroutines
- flink
- flink watermarks
- watermarks
- apache flink
- Generics
- 가변성
- MapReduce
- 불공변
- Heuristic Watermarks
- Generic
- HDFS
- ingestion time
- Stream
- java
- 공변
- Today
- Total
mm Home
ComponentScan 동작 원리 본문
대학생때 서블릿으로 개발을 하다가, 입사 이후 스프링을 처음 사용할 때 신기했다. 어떻게 동작하는지 궁금하지 않은가??
스프링 부트를 사용하면서부터는 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 부분도 아래와 같이 스프링을 사용할 때와 동일하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @MyComponent public class OtherService { @MyAutowired private MemberService memberService; public String getOtherService() { String member = memberService.getMember(); return member + " nice to meet you" ; } } |
위에 MyApplicationContext라는 커스텀 컨텍스트를 basePackage로 초기화하고 getBean 하여 사용한 부분이다.
스프링의 AnnotationConfigApplicationContext 가 어노테이션 기반의 Context를 가져오는 것과 동일하게 사용할 수 있도록 구현했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class Application { public static void main(String[] args) { MyApplicationContext myContext = new MyApplicationContext(); try { myContext.initialize( "com.jess.di.project" ); OtherService otherService = (OtherService) myContext.getBean( "otherService" ).orElseThrow(NullPointerException:: new ); TestClass testClass = (TestClass) myContext.getBean( "testClass" ).orElseThrow(NullPointerException:: new ); System.out.println(otherService.getOtherService()); System.out.println(testClass.testMethod()); System.out.println(testClass.test2Method()); } catch (Exception e) { e.printStackTrace(); } } } |
어떻게 구현했는지 살펴보자.
1 2 3 4 5 6 7 8 9 10 | public void initialize(String basePackage) throws IOException { //context에 대한 설정이 있다면 설정 내용에 대한 파싱이 필요하다 parse(); //context의 빈 팩토리가 해야할 post-processing 수행 (BeanDefinition 정의) invokeBeanFactoryPostProcessors(basePackage); //빈을 생성하고, 주입하자 finishBeanFactoryInitialization(beanFactory); } |
위에서 설명한 것처럼 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 |