javascript
Spring扫描类过程解析和案例
文章目錄
- 1. 前言
- 2. 源碼分析
- 2.1 主要入口
- 2.2 scanCandidateComponents
- 2.3 doRetrieveMatchingFiles
- 2.4 問(wèn)題總結(jié)
- 3. 掃描Demo
1. 前言
周所周知,Spring 啟動(dòng)最重要的第一步,就是掃描需要由Spring管理的類信息,例如@Component、@Controller、@Service等等。
通過(guò)這篇文章,你將會(huì)了解類的掃描實(shí)現(xiàn),并且,我們也會(huì)寫一個(gè)Demo來(lái)模擬掃描
PS. 我的學(xué)習(xí)方法是針對(duì)性地查看源碼,帶著目的,挑重點(diǎn)代碼學(xué)習(xí)。而不是一行一行看下去,這樣很容易把自己弄暈了。
2. 源碼分析
2.1 主要入口
掃描類的主入口是 ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry,其調(diào)用堆棧如下(這里了解一下就行,不用記,作為調(diào)試筆記):
1. main 方法 2. AnnotationConfigApplicationContext 構(gòu)造函數(shù) 3. AbstractApplicationContext.refresh 4. AbstractApplicationContext.invokeBeanFactoryPostProcessors 5. PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors 6. PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors第6步 invokeBeanDefinitionRegistryPostProcessors 方法如下,內(nèi)部就是調(diào)用實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor 的對(duì)象方法,而 ConfigurationClassPostProcessor 就是Spring框架自帶的實(shí)現(xiàn)類。
private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {postProcessor.postProcessBeanDefinitionRegistry(registry);}}2.2 scanCandidateComponents
核心方法: ClassPathScanningCandidateComponentProvider.scanCandidateComponents()。
基本內(nèi)容就是這2點(diǎn):
- 掃描類并返回Resource[]數(shù)組
- 遍歷Resource[]數(shù)組并轉(zhuǎn)換為 ScannedGenericBeanDefinition 數(shù)組
上面有個(gè)變量 packageSearchPath ,值是 “classpath*:com/train/**/*.class”。因此大膽猜測(cè)一下,難道掃描類的原理就是掃描編譯后的 class 文件?
2.3 doRetrieveMatchingFiles
重點(diǎn)來(lái)了,完整的方法名是 PathMatchingResourcePatternResolver.doRetrieveMatchingFiles 。
根據(jù)測(cè)試用例,這里的方法參數(shù)如下:
- fullPattern:F:/Source/spring-framework/learning/out/production/classes/com/train/**/*.class
- dir:F:\Source\spring-framework\learning\out\production\classes\com\train
印證了我們上面的猜想,果然是讀取編譯后的 class 文件
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {if (logger.isTraceEnabled()) {logger.trace("Searching directory [" + dir.getAbsolutePath() +"] for files matching pattern [" + fullPattern + "]");}//遞歸算法,層層往下遍歷,讀取class文件并返回 Set<File> 集合for (File content : listDirectory(dir)) {String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {if (!content.canRead()) {if (logger.isDebugEnabled()) {logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +"] because the application is not allowed to read the directory");}}else {doRetrieveMatchingFiles(fullPattern, content, result);}}if (getPathMatcher().match(fullPattern, currPath)) {result.add(content);}}}2.4 問(wèn)題總結(jié)
問(wèn)題一: class 文件的加載順序是怎樣的?
查看 2.3 注釋描述的 listDirectory 方法:
protected File[] listDirectory(File dir) {File[] files = dir.listFiles();if (files == null) {if (logger.isInfoEnabled()) {logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");}return new File[0];}Arrays.sort(files, Comparator.comparing(File::getName));return files;}重點(diǎn)看倒數(shù)第二行,Arrays.sort(files, Comparator.comparing(File::getName));
結(jié)論:每個(gè)package目錄分別按文件名排序加載
問(wèn)題二: 2.2 我們有講過(guò),掃描的結(jié)果應(yīng)該是返回Resource數(shù)組,這里是File集合是如何轉(zhuǎn)換成Resource數(shù)組的?
答案很簡(jiǎn)單,調(diào)試進(jìn)入PathMatchingResourcePatternResolver.doFindMatchingFileSystemResources 源碼
protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {if (logger.isTraceEnabled()) {logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");}//掃描獲取File集合Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);//聲明Resource集合Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());for (File file : matchingFiles) {result.add(new FileSystemResource(file));}return result;}重點(diǎn)查看上面的 for 循環(huán),結(jié)論:
FileSystemResource 構(gòu)造函數(shù)可以接收 File 類型對(duì)象,其內(nèi)部依賴于 File
FileSystemResource 與 Resource 關(guān)系如下
問(wèn)題三: Resource 又是如何轉(zhuǎn)換為 ScannedGenericBeanDefinition?
回到2.2調(diào)試的源碼,關(guān)鍵是這面這句代碼:
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);MetadataReader 是 spring 封裝的元數(shù)據(jù)讀取器,通過(guò) MetadataReaderFactory.getMetadataReader(resource) 即可將Resource對(duì)象轉(zhuǎn)換為元數(shù)據(jù)讀取器。最后,我們就能直接取到需要的元數(shù)據(jù)了。
metadataReader 字段信息如下,其中很重要的一個(gè)就是className。只要有了類名,就能反射得到Java 的 Class,因此也就能進(jìn)一步得到更多的信息。
3. 掃描Demo
假設(shè),現(xiàn)在我們需要在Spring項(xiàng)目掃描指定package包含的所有類,該如何操作呢?
下面我直接貼出代碼,模仿Spring底層代碼的,看完不要覺得實(shí)在太簡(jiǎn)單…
PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();try {//掃描得到Resource數(shù)組Resource[] resources = resourceResolver.getResources("classpath*:com/train/inherit/**/*.class");//遍歷Resources,逐個(gè)獲取信息for (Resource resource : resources) {MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);String className = metadataReader.getAnnotationMetadata().getClassName();Class<?> superClazz = Class.forName(className);//其他操作......}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}總結(jié)
以上是生活随笔為你收集整理的Spring扫描类过程解析和案例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring重写BeanDefiniti
- 下一篇: Spring AOP实现原理解析