日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

Spring扫描类过程解析和案例

發(fā)布時(shí)間:2025/3/19 javascript 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring扫描类过程解析和案例 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 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ù)組
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//獲取掃描的包路徑,我這里的測(cè)試用例。packageSearchPath = classpath*:com/train/**/*.classString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;//重點(diǎn)!!!這里執(zhí)行掃描,并返回Resource[]資源數(shù)組。等等會(huì)介紹里面的代碼Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();//遍歷掃描的結(jié)果for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);if (isCandidateComponent(metadataReader)) {//將Resource對(duì)象轉(zhuǎn)換為BeanDefinition,記錄了SpringBean的描述信息ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);//下面基本都是日志了,無(wú)關(guān)緊要if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);}else {if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}}else {if (traceEnabled) {logger.trace("Ignored because not readable: " + resource);}}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}

上面有個(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底層代碼的,看完不要覺(jué)得實(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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。