爲了何更好的理解該篇內容,請先閱讀Spring Boot 原理解析—入口SpringApplication。
我們知道在使用Spring Boot時,Spring會自動加載Spring Boot中啓動類包下以及其子包下的帶註解的類,本篇不會講述是如何加載註解類的,因爲這是屬於Spring的內容,我們只講述爲什麼會根據啓動類加載子包下的帶註解的類。在講解Spring Boot源碼之前我們先看一下Spring中包的掃描方式一種是@ComponentScan("cn.org.microservice.spring.ioc.annotation")註解,另一種則是以XML的方式配置:
<context:component-scan base-package="cn.org.microservice.spring.ioc.annotation"/>
如果我們不使用XMl而使用@ComponentScan,但是在註解中不配置然任何包,也就是說直接在@Configuration註解的淚傷註解@ComponentScan,代碼如下所示:
package cn.org.microservice.spring.ioc.annotation;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class ConfigrationBean {
}
然後在其子包下創建幾個Bean,如下所示,我們在其子包下創建了三個Bean類,爲了方便我們只創建類,沒有創建接口。
package cn.org.microservice.spring.ioc.annotation.bean.service;
import org.springframework.stereotype.Service;
@Service
public class ServiceBean {
}
package cn.org.microservice.spring.ioc.annotation.bean;
import org.springframework.stereotype.Component;
@Component
public class BeanA {
}
package cn.org.microservice.spring.ioc.annotation.bean1;
import org.springframework.stereotype.Component;
@Component
public class Bean2 {
}
然後我們使用測試類測試,然後輸出容器中註冊的Bean的名稱。
public class AnnotationTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigrationBean.class);
for(String beanName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanName);
}
}
}
//一下爲輸出內容
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configrationBean
beanA
serviceBean
bean2
通過輸出我們看到容器中居然註冊其子包下的註解類,但是實際上上我們並沒有告訴容器去註冊cn.org.microservice.spring.ioc.annotation子包,但是Spring還是掃描了cn.org.microservice.spring.ioc.annotation子包。SpringBoot用的就是這個原理。通過Spring Boot 原理解析—入口SpringApplication一篇我們知道Spring Boot啓動類上包含Configuration註解和@ComponentScan,我們運行main方法的時候只需要將主類註冊到Spring容器中,後續就會掃描主類包下的子包。下面我們就來看看其中的源碼解析,先看Spring Boot註冊主類的邏輯是在run方法中調用的prepareContext方法:
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
然後我們進入prepareContext方法內部:
private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner){
......
......
//前面一系列準備操作省略,可以自己查看源碼
//獲取資源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加載資源 sources.toArray(new Object[0])就是主類的Class實例
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
//接着我們再看load方法
protected void load(ApplicationContext context, Object[] sources) {
...
//創建BeanDefinitionLoader實例,由BeanDefinitionLoader 加載資源
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
......
//BeanDefinitionLoader 實例加載資源
loader.load();
}
然後我們只需要關注BeanDefinitionLoader的load方法即可:
public int load() {
int count = 0;
//這裏我們傳入的sources爲主類的Class實例:Application.class
for (Object source : this.sources) {
count += load(source);
}
return count;
}
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
//我們傳入的是Class類型的實例,因此走該分支
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
//下面是其他分支,不作介紹
......
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
private int load(Class<?> source) {
if (isGroovyPresent()&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
//非Groovy不作介紹
....
}
//我們知道@Configuration的元註解@Component,因此走該分支
if (isComponent(source)) {
//使用BeanDefinitionRegistry實例註冊Bean
this.annotatedReader.register(source);
return 1;
}
return 0;
}
下面我們看將主類註冊到Spring容器中的邏輯:
public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
//調用registerBean
registerBean(annotatedClass);
}
}
public void registerBean(Class<?> annotatedClass) {
//doRegisterBean纔是註冊Bean的邏輯
doRegisterBean(annotatedClass, null, null, null);
}
<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
//一下爲對BeanDefinition實例的處理
......
//創建BeanDefinitionHolder實例
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
//將BeanDefinition註冊到容器,調用完該方法 Spring Boot的主類已經被註冊到Spring 容器中了。
//registerBeanDefinition方法就是將BeanDefinition註冊到Spring 容器中,這裏不在展示
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
至此,我們已經將主類註冊到Spring 容器中了,接着就是Spring 如何根據註冊到Spring中的主類掃描主類下以及其子包下的註解的類,即如何將其他Bean註冊到Spring容器中。同樣我們在Spring Boot 原理解析—從入口SpringApplication說起一篇中說過Bean的註冊最終是調用最終調用AbstractApplicationContext的refresh()方法。我們就看該方法中的內容,其中調用方法invokeBeanFactoryPostProcessors處理已經註冊到Spring容器中的Bean,也就是我們剛開始時注入到Spring中的主類。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
try {
// Invoke factory processors registered as beans in the context.
//調用工廠處理已經註冊到容器中的Bean
invokeBeanFactoryPostProcessors(beanFactory);
}
......
}
}
然後我們只需要關注invokeBeanFactoryPostProcessors即可,如下代碼爲其調用流程:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
......
}
public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
......
//該方法處理已經註冊到容器中的Bean
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
.....
}
private static void invokeBeanFactoryPostProcessors( Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
//下面將邏輯交給BeanFactoryPostProcessor實現ConfigurationClassPostProcessor處理
for (BeanFactoryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanFactory(beanFactory);
}
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
......
//處理配置Bean定義
processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//獲取已經註冊到Spring容器中的Bean的名稱
String[] candidateNames = registry.getBeanDefinitionNames();
//下面的邏輯是將@Configuration註解的類加入到configCandidates中
// ConfigurationClassParser解析每一個 @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
//解析@Configuration註解的類
parser.parse(candidates);
}
.....
}
真正解析@Configuration註解是ConfigurationClassParser類的parse方法,下面我們查看parse方法代碼:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
//我們前面注入的主類是屬於AnnotatedBeanDefinition,走該分支,其他的代碼省略
if (bd instanceof AnnotatedBeanDefinition) {
//這裏繼續調用了parse的重載方法
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
//下面的其他分支我們不關注
......
}
......
}
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
//調用processConfigurationClass解析配置類
//將Bean名稱和AnnotationMetadata 註解數據封裝爲ConfigurationClass
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//其他邏輯
.....
SourceClass sourceClass = asSourceClass(configClass);
do {
//解析@Configuration註解的類,doProcessConfigurationClass才真正解析@Configuration類
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
......
}
上面的方法中doProcessConfigurationClass才解析@Configuration註解的類,我們查看doProcessConfigurationClass方法源碼:
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)throws IOException {
//是否擁有@ComponentScan註解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(),ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// 配置類上有 @ComponentScan 註解 我們的主類上是有該註解的。
Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
.......
}
}
......
}
doProcessConfigurationClass源碼中會判斷@Configuration註解的類上是否包含@ComponentScan註解,如果包含則處理該註解,這個配置的就是要掃描的包的名稱:@ComponentScan是由ComponentScanAnnotationParser實例進行解析,我們繼續查看的ComponentScanAnnotationParser的parse方法:
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
//獲取@ComponentScan配置的包
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//如果@ComponentScan註解配置的包爲空,則使用@Configuration註解的類的包作爲掃描包
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
//接下李就是掃描寶,然後將包內的Bean註冊到Spring容器中,下面的邏輯就不看了,
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
ComponentScanAnnotationParser的parse方法的邏輯主要是獲取@ComponentScan註解中配置的包,然後交給ClassPathBeanDefinitionScanner掃描包,如果@ComponentScan沒有配置包,使用basePackages.add(ClassUtils.getPackageName(declaringClass))將@Configuration註解的類的包設置爲要掃描的包。
總結:
上面走了那麼多的代碼,其實主要做了兩件事,1.將@SpringBootApplication註解的類註冊到Spring容器,2.調用Spring容器的refresh()方法註冊Bean時,將要掃描的包配置爲@SpringBootApplication註解的類的包。其實我們使用new AnnotationConfigApplicationContext(ConfigrationBean.class)也是一樣的道理,我們可以看下該構造方法:
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
//第一步:註冊@Configuration註解類到Spring容器
register(annotatedClasses);
//調用refresh註冊其他Bean。
//如果@Configuration註解的類上有@ComponentScan註解
//解析時根據@ComponentScan掃描包
//如果@ComponentScan配置了包,則使用@ComponentScan配置的包
//如果@ComponentScan沒有配置包,則掃描其註解的類的包
refresh();
}