上一章,我們介紹了在AnnotationConfigApplicationContext初始化的時候,會建立AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner兩個物件:
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { …… public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } …… }
我們已經知道AnnotatedBeanDefinitionReader物件建立的大致流程,AnnotatedBeanDefinitionReader是用來註冊配置類的。現在我們要來學習ClassPathBeanDefinitionScanner,首先從ClassPathBeanDefinitionScanner的名字我們大概可以知道,這個類是用來掃描BeanDefinition的類路徑的,那麼,我們要怎麼使用這個類來掃描類路徑呢?來看下面的測試用例:
@Test public void test04() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.scan("org.example.service"); ac.refresh(); System.out.println(ac.getBean("orderService")); }
執行結果:
org.example.service.OrderService@4ba2ca36
上面的測試用例,我們不再像之前在配置類上用@ComponentScan標記要掃描的類路徑,並將配置類作為引數傳給AnnotationConfigApplicationContext建立物件。而是在呼叫AnnotationConfigApplicationContext無參構造方法建立物件後,再呼叫ac.scan(String... basePackages)將類路徑傳入,而ac.scan(String... basePackages)方法也是呼叫scanner.scan(String... basePackages)方法來完成類的掃描。
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { …… private final ClassPathBeanDefinitionScanner scanner;
…… @Override public void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); this.scanner.scan(basePackages); } …… }
<1>和<2>兩塊程式碼最終效果看起來一樣,都能掃描我們設定的路徑,根據類生成BeanDefinition再生成bean,可能有人會懷疑,掃描用@ComponentScan所標記的類路徑,是否是AnnotationConfigApplicationContext的scanner物件? 這裡筆者可以告訴大家:掃描@ComponentScan標記的類路徑需要用到ClassPathBeanDefinitionScanner類,但並非用AnnotationConfigApplicationContext的scanner物件,而是在程式碼的某處建立了ClassPathBeanDefinitionScanner物件再呼叫scan(String... basePackages)方法掃描@ComponentScan標記的路徑,至於是哪裡建立新的ClassPathBeanDefinitionScanner物件再掃描@ComponentScan標記的路徑後面會講,只是這裡我們要知道AnnotationConfigApplicationContext的scanner物件僅僅用來幫助我們新增掃描路徑,而實際的開發中很少用到。
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);//<1> ———————————————————————————————————————————————————————————————————————————————————————————————— AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();//<2> ac.scan("org.example.service"); ac.refresh();
下面,我們來看看ClassPathBeanDefinitionScanner的scan(String... basePackages)方法都做了些什麼:
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { private final BeanDefinitionRegistry registry; …… public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages);//<1> // Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);//<2> } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); } protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//<3> for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);//<4> if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);//<5> } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);//<6> definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry);//<7> } } } return beanDefinitions; } …… }
- 進入到scan(String... basePackages)方法後,會在<1>處將要掃描的類路徑傳遞給doScan(String... basePackages),由doScan(String... basePackages)代替其完成掃描。
- includeAnnotationConfig欄位預設為true,會進入到AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry registry)方法,這個方法在上一章有講過,會預先註冊一些BeanDefinition到BeanDefinitionRegistry。
- 進入到doScan(String... basePackages)後,在<3>處會呼叫父類ClassPathScanningCandidateComponentProvider的findCandidateComponents(String basePackage)方法,這個方法可以針對我們傳入的一個類路徑,掃描路徑下所有元件並返回其BeanDefinition,這裡返回的是BeanDefinition的集合。
- 在<3>處拿到BeanDefinition集合後會迴圈每一個BeanDefinition,在<4>處用beanName生成器根據BeanDefinition生成beanName。
- 在程式碼<5>處會判斷BeanDefinition能否轉型成AnnotatedBeanDefinition(註解BeanDefinition),如果可以則會將BeanDefinition傳入到AnnotationConfigUtils.processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd),在這個方法中會獲取AnnotatedBeanDefinition的元資訊並設定其屬性,比如這個類是否標記了@Lazy、@Primary、@DependsOn、@Description……等。
- 最後,在<6>處會根據BeanDefinition和beanName生成一個BeanDefinitionHolder物件,在<7>處將BeanDefinitionHolder和registry物件傳入registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)方法,將BeanDefinition和beanName註冊到registry。
從上面的程式碼我們得知,spring掃描類路徑是呼叫ClassPathScanningCandidateComponentProvider.findCandidateComponents(String basePackage)方法,所以我們繼續追蹤到這個方法:
public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage);//<1> } } private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern;//<2> Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);//<3> …… for (Resource resource : resources) { if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//<4> if (isCandidateComponent(metadataReader)) {//<5> ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);//<6> sbd.setSource(resource);//<7> if (isCandidateComponent(sbd)) {//<8> candidates.add(sbd); } } …… } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; }
- 在findCandidateComponents(String basePackage)方法中,一般是呼叫<1>處的程式碼進行類掃描,即else分支,if分支一般是使用spring註解@Index進行掃描效能的提升,否則不會進入。
- 在scanCandidateComponents(String basePackage)方法的<2>處會根據類路徑生成一個spring自定義的表示式packageSearchPath,在<3>處spring可以解析這個表示式並返回一個Resource陣列,每個Resource元素都代表類路徑下的一個class檔案的路徑。
- 之後會迴圈每個Resource元素,在<4>處獲取其後設資料,並在<5>處判斷通過父類ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader metadataReader)方法判斷Resource元素所對應的class是否是一個元件,比如標記了@Component、@Service、@Controller……等。
- 通過<5>處的判斷如果一個類是一個元件,在<6>處會根據後設資料生成一個ScannedGenericBeanDefinition物件,這裡我們又看到一個BeanDefinition的實現,然後在<7>處設定BeanDefinition的元資訊,即:sbd.setSource(resource),之前說過,一個物件的元資訊是類,一個類的元資訊是類檔案路徑,而BeanDefinition是用於描述類的,所以它的元資訊也是類檔案路徑。
- 最後,會在<7>處呼叫父類ClassPathScanningCandidateComponentProvider的過載方法isCandidateComponent(AnnotatedBeanDefinition beanDefinition),在這個方法決定描述類的BeanDefinition是否有資格加入到<8>處candidates集合,什麼樣的類才可以加入到candidates集合呢?比如:這個類是一個頂級類或者靜態巢狀內部類,這個類不需要藉助其他類來構造例項;或者這個類並不是一個介面類,或者這個類是個抽象類,但內部方法有用@Lookup註解來標記。這裡不理解的不要心急,下面還會講到ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader metadataReader)和isCandidateComponent(AnnotatedBeanDefinition beanDefinition)這兩個過載方法。
現在,我們來除錯一下上面的程式碼,看看packageSearchPath和resources的內容,首先我們來看我們類路徑下的檔案:
D:\F\java_space\spring-source\spring-beanFactoryPostProcessor\target\classes\org\example\service 的目錄 2020/11/19 08:22 <DIR> . 2020/11/19 08:22 <DIR> .. 2020/11/19 08:22 490 HelloService$BarService.class 2020/11/19 08:22 609 HelloService$FooService.class 2020/11/19 08:22 318 HelloService$Hello.class 2020/11/19 08:22 524 HelloService.class 2020/11/19 08:22 559 OrderService.class 2020/11/19 08:22 1,783 Test1BeanFactoryPostProcessor.class 2020/11/19 08:22 555 UserService.class 7 個檔案 4,838 位元組 2 個目錄 102,708,711,424 可用位元組
然後除錯進入上面的程式碼,可以看到packageSearchPath的內容為:classpath*:org/example/service/**/*.class,之前說過,這裡spring會自定義表示式,通過表示式可以掃描這個路徑下的類檔案。
現在我們來看下ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader metadataReader)和isCandidateComponent(AnnotatedBeanDefinition beanDefinition)兩個過載方法的實現:
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { …… private final List<TypeFilter> includeFilters = new LinkedList<>(); private final List<TypeFilter> excludeFilters = new LinkedList<>(); …… protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class));//<1> ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } } …… protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) {//<2> if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } for (TypeFilter tf : this.includeFilters) {//<3> if (tf.match(metadataReader, getMetadataReaderFactory())) { return isConditionMatch(metadataReader); } } return false; } protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));//<4> } …… }
- 在建立ClassPathScanningCandidateComponentProvider物件時,一般會呼叫到registerDefaultFilters()方法,在這個方法中會往includeFilters欄位加入需要掃描的註解,如:在<1>處加入對標記了@Component類的過濾。
- isCandidateComponent(MetadataReader metadataReader)在<2>和<3>會按照我們在@CommponScan設定的excludeFilters和includeFilters來過濾要掃描的類。
- <4>處的isCandidateComponent(AnnotatedBeanDefinition beanDefinition)會獲取beanDefinition的後設資料,根據後設資料判斷這個beanDefinition是否是一個候選元件,比如:metadata.isIndependent()要求一個類必須是頂級類或者是靜態內部巢狀類,即這個類不需要依賴其他類來生成,而內部巢狀類必須依賴外部類來生成物件;metadata.isConcrete()判斷一個類是否是抽象類或者介面;metadata.isAbstract()判斷是否是一個抽象類,metadata.hasAnnotatedMethods(Lookup.class.getName())則判斷這個類中是否有標記了Lookup的方法。如果一個抽象類中沒有標記Lookup的方法,則不能成為候選元件。
現在,我們來分析下下面的類哪些可以成為候選元件。
package org.example.service; import org.springframework.stereotype.Component; public class HelloService { @Component public class FooService { } @Component public static class BarService { } @Component public interface Hello { void sayHello(); } }
- FooService是巢狀內部類,需要依賴HelloService來生成物件,所以它無法成為一個候選元件。
- BarService是靜態巢狀內部類,可以獨立生成物件,所以它可以成為一個候選元件。
- Hello是介面,無法獨立生成物件,所以它無法成為一個候選元件。
現在,我們回到AnnotationConfigApplicationContext(Class<?>... componentClasses)的構造方法:
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner; …… public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this();//<1> register(componentClasses);//<2> refresh(); } …… @Override public void register(Class<?>... componentClasses) { Assert.notEmpty(componentClasses, "At least one component class must be specified"); this.reader.register(componentClasses);//<3> } …… }
在<1>處,會初始化reader和scanner,我們可以呼叫AnnotationConfigApplicationContext.scan(String... basePackages)傳入類路徑,這個方法會進而根據傳入的類路徑用scanner來掃描元件。當然,我們一般不用這種方式,而是在建立AnnotationConfigApplicationContext物件時將配置類傳入,讓spring自行讀取配置類裡的類路徑。之前筆者已經大致講解完<1>處this()方法,現在我們要來學習<2>處的配置類註冊方法,如我們所見,<2>處會進而將配置類傳給<3>處的reader物件。reader物件會根據配置類生成對應的BeanDefinition註冊進spring容器。
AnnotatedBeanDefinitionReader.register(Class<?>... componentClasses)經過一系列的呼叫,會來到下面的AnnotatedBeanDefinitionReader.doRegisterBean(...)方法,這裡我們又看到一個BeanDefinition的實現——AnnotatedGenericBeanDefinition,這段程式碼在<1>處將配置類生成一個對應的AnnotatedGenericBeanDefinition,在<2>處生成beanName,在<3>處將beanName和BeanDefinition包裝成一個BeanDefinitionHolder物件,最後在<4>處將beanName和BeanDefinition註冊進原先的AnnotationConfigApplicationContext物件。
public class AnnotatedBeanDefinitionReader { …… private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);//<1> …… abd.setScope(scopeMetadata.getScopeName()); String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));//<2> …… BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);//<3> definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);//<4> } …… }
在生成配置類對應的BeanDefinition並註冊進spring容器後,AnnotationConfigApplicationContext就會呼叫父類的refresh()方法,我們先大致看一下refresh()方法:、
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { …… @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { …… // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//<1> …… try { …… // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory);//<2> …… // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);//<3> …… } catch (BeansException ex) { …… // Destroy already created singletons to avoid dangling resources. destroyBeans(); …… } …… } } }
程式碼<1>處會返回AnnotationConfigApplicationContext父類GenericApplicationContext的beanFactory屬性,其型別為DefaultListableBeanFactory。之後將beanFactory傳給<2>處和<3>處的方法,從方法註釋可以看到,<2>處會呼叫bean工廠後置處理器,<3>處會用beanFactory來初始化剩餘的非懶載入單例物件,即我們編寫的dao、service、controller……。