Spring源碼學習(八):Spring Boot原理

目錄

1.Spring Boot簡介

2.Spring Boot啓動原理 —— 構造方法

2.1 deduceFromClasspath方法

2.2 配置ApplicationContextInitializer、ApplicationListener

3.Spring Boot啓動原理 —— run方法

3.1 SpringApplicationRunListener

3.2 環境準備

3.2.1 prepareEnvironment

3.2.2 ApplicationEnvironmentPreparedEvent事件的處理

3.2.3 配置是否忽略Bean信息

3.3 打印Banner

3.4 創建容器

3.5 準備容器

3.5.1 準備

3.5.2 加載

3.6 容器刷新

3.7 啓動Runners

3.8 異常處理

4. Starter機制的實現

4.1 @SpringBootApplication

4.2 selectImports方法

4.2.1 讀取註解屬性

4.2.2 讀取並處理配置

4.2.3 激活過濾器

4.2.4 觸發事件

4.3 配置類的解析和裝載

5. 重要註解

5.1 @ComponentScan

5.2 @Conditional

5.3 @Value


1.Spring Boot簡介

Spring MVC非常強大,但是也存在以下問題:

  1. 配置繁瑣,哪怕一個最小項目,也要配置至少三個XML文件
  2. 依賴Tomcat等Web容器運行,增加了維護難度
  3. 與常用組件集成起來不方便,例如與Mybatis集成,每個項目都要配置數據源、事務等,但是這些配置實際都是通用的,完全可以按照約定提供默認情況

Spring Boot就是爲了解決這一情況的,它具有如下特點(來源於百度百科):

  1. 創建獨立的Spring應用程序
  2. 嵌入的Tomcat,無需部署WAR文件
  3. 簡化Maven配置
  4. 自動配置Spring
  5. 提供生產就緒型功能,如指標,健康檢查和外部配置
  6. 絕對沒有代碼生成並且對XML也沒有配置要求

一個最簡單的Spring Boot項目組成如下:

pom.xml(部分):

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.1.0.RELEASE</version>
        </plugin>
    </plugins>
</build>

主類:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Controller:

@RestController
public class TestController {
    @GetMapping("/")
    public String hello(){ return "hello"; }
}

運行主類後,在瀏覽器訪問localhost:8080就可以看到打印出了“hello”。

2.Spring Boot啓動原理 —— 構造方法

Spring Boot項目是通過SpringApplication.run方法作爲入口啓動的,它接受了兩個參數:一個是Class類型,另一個是String[]類型,run方法做了兩次跳轉:第一次將Class對象封裝爲Class[]數組,轉發給自身的重載;第二次用Class[]對象實例化SpringApplication對象,然後將String[]對象傳入run成員方法(main函數調用的是靜態方法):

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

首先來看它的構造函數,resourceLoader默認爲null,主要是一些成員屬性的設置:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

2.1 deduceFromClasspath方法

從方法名和上下文不難看出,deduceFromClasspath的作用就是推斷Web程序類型:

static WebApplicationType deduceFromClasspath() {
	if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
		return WebApplicationType.REACTIVE;
	}
	for (String className : SERVLET_INDICATOR_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return WebApplicationType.NONE;
		}
	}
	return WebApplicationType.SERVLET;
}
  • Reactive類型:這是Spring 5新增的類型,需要有DispatcherHandler類實現,且沒有DispatcherServlet和ServletContainer實現
  • Servlet類型:傳統Web應用,需要有ConfigurableWebApplicationContext、DispatcherServlet/ServletContainer實現
  • None類型:不是Web應用

2.2 配置ApplicationContextInitializer、ApplicationListener

這一步由兩個方法構成:setInitializers/setListeners和getSpringFactoriesInstances,前者就是賦值操作,主要來看後者。getSpringFactoriesInstances方法源碼如下,顯而易見,它的核心方法是loadFactoryNames和createSpringFactoriesInstances:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

首先來看loadFactoryNames:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

loadSpringFactories源碼如下,其實就是從properties文件中加載配置:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}
	try {
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		result = new LinkedMultiValueMap<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryClassName = ((String) entry.getKey()).trim();
				for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryClassName, factoryName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

FACTORIES_RESOURCE_LOCATION常量的字面值是“META-INF/spring.factories”。也就是說,這裏查找了以ApplicationContextInitializer和ApplicationListener的全限定名爲鍵的配置值。

在org.springframework.boot:spring-boot包下,可以看到如下配置:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

此外,在spring-boot-autoconfigure下還配置了一些:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

這6+10個類就是Spring Boot默認加載的加載器和監聽器。

spring.factories文件的配置生效需要靠createSpringFactoriesInstances。該方法實際就是反射實例化spring.factories中配置的類,並將實例添加到List中: 

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

3.Spring Boot啓動原理 —— run方法

run方法是Spring Boot的核心,在這裏完成了ApplicationContext的啓動和初始化。它的流程如下:

3.1 SpringApplicationRunListener

首先獲取和啓動了SpringApplicationRunListener,實際也藉助了getSpringFactoriesInstances,Spring Boot默認提供的實現是EventPublishingRunListener:

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

starting方法廣播了一個ApplicationStartingEvent。

3.2 環境準備

先上源碼,一共做了三件事:構造ApplicationArguments對象、準備環境、配置是否忽略Bean信息:

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);

DefaultApplicationArguments構造方法很簡單:

public DefaultApplicationArguments(String... args) {
	Assert.notNull(args, "Args must not be null");
	this.source = new Source(args);
	this.args = args;
}

Source是其內部類,是一個PropertySource實現類。

3.2.1 prepareEnvironment

這裏的核心邏輯是獲取並配置Environment對象,首先調用getOrCreateEnvironment方法,獲取已有實例或根據Web程序類型創建新實例:

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
	}
}

一般來說創建的都是StandardServletEnvironment,它會初始化四個PropertySource:

  • servletContextInitParams
  • servletConfigInitParams
  • System.getenv()
  • System.getProperties()

後兩個來自其父類StandardEnvironment。如果JNDI環境可用,還會額外增加jndiProperties。

然後調用configureEnvironment進行配置,實際做了三件事:配置ConversionService、配置PropertySource、配置Profile,這三個都是很熟悉的概念了,不做贅述:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

之後就是一些後處理操作,主要就是綁定和轉換操作:

bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);

isCustomEnvironment默認是false,所以會進行轉換,這裏是檢查經過一番處理後,environment還是不是創建時的類型。

3.2.2 ApplicationEnvironmentPreparedEvent事件的處理

此時容器環境已經準備完畢,通過SpringApplicationRunListener發出ApplicationEnvironmentPreparedEvent事件:

listeners.environmentPrepared(environment);

該事件會觸發ConfigFileApplicationListener進行處理:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(),event.getSpringApplication());
    }
}

loadPostProcessors方法從spring.factories中加載了EnvironmentPostProcessor對應的類;然後調用它們和自身(ConfigFileApplicationListener自己也實現了EnvironmentPostProcessor接口)的postProcessEnvironment方法,自身的postProcessEnvironment會進行Spring Boot配置文件的加載:

protected void addPropertySources(ConfigurableEnvironment environment,ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    new Loader(environment, resourceLoader).load();
}

重點就是Loader內部類,在它的構造方法中,加載了spring.factories中所有PropertySourceLoader對應的類:PropertiesPropertySourceLoader和YamlPropertySourceLoader。

這兩個加載器可以加載:properties、xml、yml、yaml共四種類型的文件,ConfigFileApplicationListener默認會在:classpath:/classpath:/config/file:./file:./config/ 共四個位置加載名爲application的配置文件。

ConfigFileApplicationListener還提供了一些對配置文件的自定義項:

  • spring.config.location:指定主配置文件的位置
  • spring.config.name:指定主配置文件的名稱
  • spring.profiles.active:指定當前生效的配置文件
  • spring.profiles.include:指定需要引入的配置文件

另一個監聽器DelegatingApplicationListener也會對此事件進行處理,它獲取了容器環境中"context.listener.classes"對應的ApplicationListener的類名,然後進行實例化並調用multicastEvent方法向它們傳遞事件。

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        List<ApplicationListener<ApplicationEvent>> delegates = getListeners(((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
        if (delegates.isEmpty()) {
            return;
        }
        this.multicaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<ApplicationEvent> listener : delegates) {
            this.multicaster.addApplicationListener(listener);
        }
    }
    if (this.multicaster != null) {
        this.multicaster.multicastEvent(event);
    }
}

LoggingApplicationListener會在這一步初始化日誌系統,並在虛擬機上註冊關閉鉤子,實現優雅關機:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	if (this.loggingSystem == null) {
		this.loggingSystem = LoggingSystem
				.get(event.getSpringApplication().getClassLoader());
	}
	initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}

protected void initialize(ConfigurableEnvironment environment,ClassLoader classLoader) {
	new LoggingSystemProperties(environment).apply();
	LogFile logFile = LogFile.get(environment);
	if (logFile != null) {
		logFile.applyToSystemProperties();
	}
	initializeEarlyLoggingLevel(environment);
	initializeSystem(environment, this.loggingSystem, logFile);
	initializeFinalLoggingLevels(environment, this.loggingSystem);
	registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

3.2.3 配置是否忽略Bean信息

這裏的邏輯很簡單,就是檢查系統屬性有沒有“spring.beaninfo.ignore”配置,沒有則默認true:

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
	if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
		Boolean ignore = environment.getProperty("spring.beaninfo.ignore",Boolean.class, Boolean.TRUE);
		System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,ignore.toString());
	}
}

3.3 打印Banner

Spring啓動時,都會打印一個如下的Banner,表示Spring容器開始啓動:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.0.RELEASE)

它是靠printBanner方法加載的,首先判斷了一下是否需要打印Banner:

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

print方法就是實際執行打印的方法,以標準輸出版本爲例:

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
    Banner banner = getBanner(environment);
    banner.printBanner(environment, sourceClass, out);
    return new PrintedBanner(banner, sourceClass);
}

getBanner按照先圖片,後文字,最後默認的順序加載Banner。Banner文件名必須是banner,文字版只支持txt,圖片則支持gif、jpg、png。此外,在application.properties/yaml中,文字banner位置由spring.banner.location配置,圖片banner由spring.banner.image.location配置:

private Banner getBanner(Environment environment) {
    Banners banners = new Banners();
    banners.addIfNotNull(getImageBanner(environment));
    banners.addIfNotNull(getTextBanner(environment));
    if (banners.hasAtLeastOneBanner()) {
        return banners;
    }
    if (this.fallbackBanner != null) {
        return this.fallbackBanner;
    }
    return new SpringBootBanner();
}

這裏提供一個字符畫網站:字符畫生成

3.4 創建容器

這裏根據Web應用類型,使用反射實例化類,這裏將使用的變量替換爲字面值:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

一般來說實例化的都是AnnotationConfigServletWebServerApplicationContext,它的構造方法初始化了AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner。可以看到,Spring Boot不再支持XML配置Bean,僅支持註解式配置。

3.5 準備容器

該階段實際可以分成兩部分:準備、加載,兩個子階段執行完成後,都會觸發相應的事件。

3.5.1 準備

該階段看起來很簡單,就四行代碼:

context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);

第一行設置容器環境不必多說,從第二行開始看起,postProcessApplicationContext在之前Spring、Spring MVC的初始化過程中都接觸過類似方法,這裏的作用也差不多,都是爲容器擴展一些組件:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    if (this.beanNameGenerator != null) {
        context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator",this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
    if (this.addConversionService) {
        context.getBeanFactory().setConversionService(
            ApplicationConversionService.getSharedInstance());
    }
}

第三行作用就是調用之前構造函數中加載的所有初始化器的initialize方法:

protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

下面介紹一個總共6個初始化器的作用:

  • ConfigurationWarningsApplicationContextInitializer:向容器註冊ConfigurationWarningsPostProcessor
  • ContextIdApplicationContextInitializer:爲容器設置Id
  • DelegatingApplicationContextInitializer:獲取並激活容器環境中由“context.initializer.classes”配置的其它初始化器
  • ServerPortInfoApplicationContextInitializer:自身也是一個ApplicationListener,它的initialize方法就是把自己註冊到容器的ApplicationListener列表中
  • SharedMetadataReaderFactoryContextInitializer:向容器註冊CachingMetadataReaderFactoryPostProcessor
  • ConditionEvaluationReportLoggingListener:向容器註冊ConditionEvaluationReportListener,並判斷容器是否是GenericApplicationContext的實現類,不是則報告容器的類型,防止後續加載失敗

最後,發出ApplicationContextInitializedEvent事件,不過沒有默認監聽器可以處理該事件。

3.5.2 加載

在加載開始之前,代碼對BeanFactory進行配置,主要是註冊了一些單例Bean,並允許Bean定義的覆蓋(Spring默認不允許):

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
	beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
	((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
	context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

LazyInitializationBeanFactoryPostProcessor在其postprocessBeanFactory方法中,將所有Bean的lazy-init屬性都設爲true,不過lazyInitialization默認是false,所以不會開啓懶加載。

BeanFactory準備完成後,就可以開始Bean的加載:

Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);

首先獲取Bean的來源,getAllSources方法將所有的Bean來源組合成一個不可變集合,其中primarySources就是我們調用SpringApplication.run時傳入的Class對象所組成的數組:

public Set<Object> getAllSources() {
	Set<Object> allSources = new LinkedHashSet<>();
	if (!CollectionUtils.isEmpty(this.primarySources)) {
		allSources.addAll(this.primarySources);
	}
	if (!CollectionUtils.isEmpty(this.sources)) {
		allSources.addAll(this.sources);
	}
	return Collections.unmodifiableSet(allSources);
}

load方法構造了一個BeanDefinitionReader對象,爲其配置了BeanName生成器、資源加載器、容器環境,然後調用BeanDefinitionReader.load方法進行BeanDefinition的加載:

protected void load(ApplicationContext context, Object[] sources) {
	BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
	if (this.beanNameGenerator != null) {
		loader.setBeanNameGenerator(this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		loader.setResourceLoader(this.resourceLoader);
	}
	if (this.environment != null) {
		loader.setEnvironment(this.environment);
	}
	loader.load();
}

getBeanDefinitionRegistry返回容器本身或者容器包含的BeanFactory對象,createBeanDefinitionLoader實例化了一個BeanDefinitionLoader對象。它的load方法如下,可以加載Class、Resource、Package和字符序列四種源:

private int load(Object source) {
	if (source instanceof Class<?>) {
		return load((Class<?>) source);
	}
	if (source instanceof Resource) {
		return load((Resource) source);
	}
	if (source instanceof Package) {
		return load((Package) source);
	}
	if (source instanceof CharSequence) {
		return load((CharSequence) source);
	}
	throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

這裏以Class版本爲例,它的源碼如下:

private int load(Class<?> source) {
    if (isGroovyPresent()&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
        GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,GroovyBeanDefinitionSource.class);
        load(loader);
    }
    if (isComponent(source)) {
        this.annotatedReader.register(source);
        return 1;
    }
    return 0;
}

這裏對Groovy和Java註解式配置的Bean進行了加載,主要看註解式Bean,isComponent函數表明,這裏僅加載@Component註解的類,register方法實際調用了doRegisterBean,此處該方法只有annotatedClass參數不爲空,所以這裏的源碼去掉了和其他參數有關的語句:

<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;
	}
	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
	abd.setScope(scopeMetadata.getScopeName());
	String beanName = this.beanNameGenerator.generateBeanName(abd, this.registry);
	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

shouldSkip方法對@Conditional註解進行了解析和判斷,resolveScopeMetadata對@Scope註解進行了解析,processCommonDefinitionAnnotations對@Lazy、@DependsOn、@Role、@Description註解進行解析,applyScopedProxyMode根據@Scope註解的proxyMode屬性,對Bean進行代理的創建。

proxyMode屬性是用於解決將session或request作用域的bean注入到單例bean中所遇到的問題。在Spring源碼中,只有單例Bean是提前創建的,其它scope的Bean都是用的時候再創建。這就帶來一個問題,例如一個Bean的scope屬性被設置爲session,如果它一個單例Bean依賴,當單例Bean創建時,由於session還不存在,那麼就會導致單例實例無法創建,這時候如果創建一個代理,就可以騙過Spring。當真正調用session Bean的方法時,再進行懶加載。它的另一個用處是,可以動態注入被單例依賴的非單例Bean,做到每個請求或會話注入的Bean對象都不同。

既然是創建代理,那就肯定有JDK式(基於接口)和CGLIB式(基於類),ScopedProxyMode的兩個值:INTEFACES、TARGET_CLASS對應了兩種代理模式。

最後的registerBeanDefinition實際就是調用Spring容器的registerBeanDefinition和registerAlias方法,可參見:Spring源碼學習(二):默認標籤的解析與Bean的註冊Spring源碼學習(六):Spring MVC的初始化過程,不再贅述。

當Bean加載完成後,就會觸發ApplicationPreparedEvent事件,該事件影響如下:

  • LoggingApplicationListener會響應該事件,將之前初始化的日誌系統作爲單例Bean註冊到準備完畢的容器中;
  • DelegatingApplicationListener會將事件傳遞給手動註冊的監聽器(該監聽器對之後出現的每個事件都執行該操作,所以後面不再對它介紹)
  • ConfigFileApplicationListener會向容器註冊PropertySourceOrderingPostProcessor

3.6 容器刷新

這一步就是調用了容器的refresh方法,相關內容請參見 Spring源碼學習 (一)~(六)。這裏僅介紹onRefresh方法,在Spring默認實現中,這個方法是個空方法,而在ServletWebServerApplicationContext中,給出了擴展:

protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

不難想到,這就是內置Tomcat的啓動入口。createWebServer方法如下:

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",ex);
        }
    }
    initPropertySources();
}

getWebServerFactory查找了ServletWebServerFactory的實現類,其中一個就是TomcatServletWebServerFactory,此外還有JettyServletWebServerFactory,所以可以在Maven裏禁用內置Tomcat,用Jetty替代它。

而getSelfInitializer方法調用了selfInitialize方法,在這裏做了四件事:

  • 準備Web容器,這裏是進行根容器的處理
  • 註冊新的scope:application
  • 註冊servletContext、servletConfig、contextParameters、contextAttributes這四類容器環境Bean
  • 調用所有ServletContextInitializer的onStartup方法

這裏介紹一下各ServletContextInitializer的作用:

  • SessionConfiguringInitializer:處理會話Cookie
  • ServletListenerRegistrationBean:註冊ServletContextAttributeListener、ServletRequestListener、ServletRequestAttributeListener、HttpSessionAttributeListener、HttpSessionListener、ServletContextListener
  • ServletRegistrationBean:配置url-mapping、load-on-startup和文件上傳設置
  • AbstractFilterRegistrationBean:配置url-pattern、servlet-name

注意紅色的ServletContextListener,它就是ContextLoaderListener的基類。

TomcatServletWebServerFactory的getWebServer方法創建和配置了Tomcat對象,並封裝爲TomcatWebServer類型,在TomcatWebServer的構造方法中進行了初始化。根據之前的介紹,Tomcat的start方法最終會通過傳遞,調用DispatcherServlet的init方法。

現在Spring MVC的兩大基礎組件已經準備完畢,之後可以參照 Spring源碼學習(六):Spring MVC的初始化過程 了。

還有需要注意的一點是,finishRefresh方法發出的ContextRefreshdEvent此處會被ClearCachesApplicationListener捕獲,調用ClassLoader的clearCache方法清理資源。

此外還爲容器註冊了虛擬機的關閉鉤子:

if (this.registerShutdownHook) {
	try {
		context.registerShutdownHook();
	}
	catch (AccessControlException ex) {
		// Not allowed in some environments.
	}
}

關於刷新後的一些後操作:

afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
	new StartupInfoLogger(this.mainApplicationClass)
		.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);

afterRefresh方法是擴展點,沒有默認實現。

logStartupInfo默認爲true,所以會創建StartupInfoLogger輸出啓動信息。

然後就是觸發ApplicationStartedEvent事件。

3.7 啓動Runners

這一步的操作就是查找所有ApplicationRunner和CommandLineRunner的實現類,並調用它們的run方法:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

到達這一步時SpringApplication的run方法已經基本執行完,開發者可以在此獲取一些容器信息,或者對容器最後做一些修改。

這一步執行完後,就會觸發ApplicationReadyEvent,表示應用準備完成

3.8 異常處理

和Spring MVC類似,Spring Boot也是集中處理異常,處理異常的方法爲handleRunFailure:

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
	try {
		try {
			handleExitCode(context, exception);
			if (listeners != null) {
				listeners.failed(context, exception);
			}
		}
		finally {
			reportFailure(exceptionReporters, exception);
			if (context != null) {
				context.close();
			}
		}
	}
	catch (Exception ex) {
		logger.warn("Unable to close ApplicationContext", ex);
	}
	ReflectionUtils.rethrowRuntimeException(exception);
}

這裏的主要工作就是創建SpringBootExceptionHandler註冊異常,創建SpringBootExceptionReporter報告異常,然後發出ApplicationFailedEvent和ExitCodeEvent事件。主要會觸發日誌系統的清理和錯誤日誌輸出。

4. Starter機制的實現

Spring Boot一個很吸引人的特性就是starter,許多常用組件都提供了starter依賴包,只要Maven引入就能自動配置,非常高效,它是怎麼實現的呢?關鍵在於@SpringBootApplication註解。

4.1 @SpringBootApplication

它的定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
...
}

@ComponentScan註解其實就相當於<context:component-scan/>配置,啓用了自動掃描,而@SpringBootConfiguration間接被@Component註解,根據 Spring源碼學習(六):Spring MVC的初始化過程 的介紹,SpringBootApplication這個註解類就被自動掃描了。

@EnableAutoConfiguration則是自動化配置的核心註解,打開它的定義:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

可以看到@Import註解,它可以用來引入Bean,顯然AutoConfigurationImportSelector就是自動配置的關鍵類,它實現了一系列接口:

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered{
...
}

Aware和Ordered都很熟了,ImportSelector又是什麼?查看該接口的定義,發現它的核心方法是selectImports,該方法又是如何被調用的呢?運用IDEA提供的全局搜索(ctrl+shift+F),不難找到它的調用路徑:

4.2 selectImports方法

下面來看selectImports方法具體做了什麼:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

isEnabled一般都返回true,除非覆蓋spring.boot.enableautoconfiguration配置爲false。

loadMetadata方法會嘗試加載META-INF/spring-autoconfigure-metadata.properties文件的配置,並封裝爲PropertiesAutoConfigurationMetadata對象。

getAutoConfigurationEntry從傳入的AutoConfigurationMetadata和AnnotationMetadata對象讀取鍵值對,對讀取結果做了篩選和觸發,然後封裝爲AutoConfigurationEntry對象:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

4.2.1 讀取註解屬性

getAttributes沒什麼難的,就是把註解的屬性和值讀出來封裝成AnnotationAttributes對象:

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    String name = getAnnotationClass().getName();
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?");
    return attributes;
}

getAnnotationClass方法返回的就是EnableAutoConfiguration.class對象。

現在先不按順序看代碼,跳到getExclusion這行,其實就是把exclude、excludeName屬性的值讀出來,getExcludeAutoConfigurationsProperty方法是從容器環境讀取鍵爲“spring.autoconfigure.exclude”的屬性:

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	Set<String> excluded = new LinkedHashSet<>();
	excluded.addAll(asList(attributes, "exclude"));
	excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
	excluded.addAll(getExcludeAutoConfigurationsProperty());
	return excluded;
}

4.2.2 讀取並處理配置

getCandidateConfigurations讀取spring.factories中,EnableAutoConfiguration鍵對應的值:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
			getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
	Assert.notEmpty(configurations,
			"No auto configuration classes found in META-INF/spring.factories. If you "
					+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

getSpringFactoriesLoaderFactoryClass其實還是EnableAutoConfiguration.class對象。

接下來就是使用removeDuplicates去重,其實就是把List對象先轉成Set,再轉回List。

再把上面getExclusions的結果從configurations對象中排除掉。

4.2.3 激活過濾器

filter方法從spring.factories文件讀取AutoConfigurationImportFilter的實現類,激活它們的Aware接口方法後,通過match方法讓過濾器作用於自動配置類,然後返回過濾之後的結果:

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        invokeAwareMethods(filter);
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {
        return configurations;
    }
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    return new ArrayList<>(result);
}

getAutoConfigurationImportFilters方法調用SpringFactoriesLoader.loadFactories方法:

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
	return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
			this.beanClassLoader);
}

在spring-boot-autoconfigure包下加載瞭如下過濾器:

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

invokeAwareMethods則是通過instanceof關鍵字判斷對象類型,然後調用不同的Aware接口方法:

private void invokeAwareMethods(Object instance) {
    if (instance instanceof Aware) {
        if (instance instanceof BeanClassLoaderAware) {
            ((BeanClassLoaderAware) instance)
                .setBeanClassLoader(this.beanClassLoader);
        }
        if (instance instanceof BeanFactoryAware) {
            ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
        }
        if (instance instanceof EnvironmentAware) {
            ((EnvironmentAware) instance).setEnvironment(this.environment);
        }
        if (instance instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
        }
    }
}

之後就是調用過濾器的match方法,從過濾器的類名不難看出,它們和@Condition註解有關,這裏先不具體介紹。

filter方法最後根據過濾結果,選擇是返回原始配置(無需過濾)或者過濾後的配置。

4.2.4 觸發事件

自動配置的數據加載完後,就可以發出事件了,在此之前,還進行了相關監聽器的加載:

private void fireAutoConfigurationImportEvents(List<String> configurations,Set<String> exclusions) {
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,configurations, exclusions);
        for (AutoConfigurationImportListener listener : listeners) {
            invokeAwareMethods(listener);
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}

這裏加載了spring.factories中配置的AutoConfigurationImportListener實現類,Spring默認配置爲ConditionEvaluationReportAutoConfigurationImportListener,它對事件的處理方法如下,其實就是發出報告而已:

public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
	if (this.beanFactory != null) {
		ConditionEvaluationReport report = ConditionEvaluationReport
				.get(this.beanFactory);
		report.recordEvaluationCandidates(event.getCandidateConfigurations());
		report.recordExclusions(event.getExclusions());
	}
}

4.3 配置類的解析和裝載

可以看到,selectImports方法只是對spring.factories的配置進行了加載和一些處理,還沒有進行配置類的解析,根據上面的調用路徑,這一步是在ConfigurationClassParser的processImports方法進行的:

String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);

這裏又調用了processImports方法自身,當然傳入的參數發生了變化,所以這次進入了另一個分支,需要調用processConfigurationClass方法:

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }
    ...
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    ...
}

開頭先進行了@Conditional註解的檢查,如果不滿足則直接跳過,不再向下繼續處理。這裏先不管它。

這次又調用了doProcessConfigurationClass方法,在這裏分別對@Import、@ImportResource、@Bean、@Configuration、@ComponentScan、@PropertySource註解的方法,以及父類、接口方法進行了處理。這裏以@Bean方法爲例,首先進行方法的查找:

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
    AnnotationMetadata original = sourceClass.getMetadata();
    Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
    if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
        try {
            AnnotationMetadata asm = this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
            Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
            if (asmMethods.size() >= beanMethods.size()) {
                Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
                for (MethodMetadata asmMethod : asmMethods) {
                    for (MethodMetadata beanMethod : beanMethods) {
                        if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
                            selectedMethods.add(beanMethod);
                            break;
                        }
                    }
                }
                if (selectedMethods.size() == beanMethods.size()) {
                    beanMethods = selectedMethods;
                }
            }
        }
        catch (IOException ex) {
        }
    }
    return beanMethods;
}

這裏使用了兩種方式來解析方法,一種是通過Class對象解析,一種是通過ASM(一種字節碼操作框架)來直接讀取class對象的元數據,然後以解析出來的結果數量多的一個爲準。

然後把結果封裝爲BeanMethod對象,存入configClass對象中:

Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
	configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

然後在ConfigurationClassPostProcessor中對configClass對象進行加載:

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
    this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);

這裏的reader是ConfigurationClassBeanDefinitionReader類型,其loadBeanDefinitions方法是遍歷configClass列表,逐個對它們調用loadBeanDefinitionsForConfigurationClass方法,該方法和解析方法類似,也是按照方法上的註解分別進行加載:

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    if (trackedConditionEvaluator.shouldSkip(configClass)) {
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
            this.registry.removeBeanDefinition(beanName);
        }
        this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
        return;
    }
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

仍然以@Bean註解的方法爲例,由於loadBeanDefinitionsForBeanMethod實在太長,這裏僅提取一些主要邏輯說明。首先是解析出@Bean註解的屬性,從中提取出beanName並註冊爲alias:

AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
for (String alias : names) {
	this.registry.registerAlias(beanName, alias);
}

然後對BeanDefinition進行組裝,這裏組裝了resource、source、class、factory-method、autowire、autowire-candidate、init-method、destroy-method等屬性,都是從@Bean的屬性中讀取,然後通過setter方法設置到BeanDefinition中。

接下來處理了@Scope註解的proxyMode屬性(假如存在的話),確定了代理模式:

ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
	beanDef.setScope(attributes.getString("value"));
	proxyMode = attributes.getEnum("proxyMode");
	if (proxyMode == ScopedProxyMode.DEFAULT) {
		proxyMode = ScopedProxyMode.NO;
	}
}

假如proxyMode不爲NO,則在原始BeanDefinition基礎上創建一個代理,否則直接進入下一步:

BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
	BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
	beanDefToRegister = new ConfigurationClassBeanDefinition((RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
}

最後調用容器的registerBeanDefinition方法進行註冊。在之後Spring容器會對註冊的BeanDefinition自動實例化,從而激活這些方法。舉個例子,在Mybatis的自動配置類MybatisAutoConfiguration中,sqlSessionFactory、sqlSessionTemplate兩個方法被註解了@Bean,從而被自動裝載和實例化,無需在配置文件中重新定義。

5. 重要註解

5.1 @ComponentScan

上面介紹starter機制的原理時,開頭就提到了@ComponentScan註解,可以說沒有它就沒後面的一切,它的作用和XML配置中<context:component-scan>一致,都是進行自動掃描。不過Spring MVC依靠Spring Schema機制,通過NamespaceHandler註冊解析器實現自動掃描,而Spring Boot的實現原理上面也有提及,是通過ConfigurationClassParser的doProcessConfigurationClass方法:

// Process any @ComponentScan annotations
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) {
        Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
        }
    }
}

這裏邏輯也比較簡單,就是解析註解的屬性,然後解析爲BeanDefinition,然後在@ComponentScan註解的value屬性配置的路徑下進行@Component、@ComponentScan、@Import、@ImportResource的掃描,並把掃描結果傳給parse方法進行遞歸解析(doProcessConfigurationClass有parse方法間接調用)。

關鍵就在於ComponentScanAnnotationParser.parse方法,它創建了一個ClassPathBeanDefinitionScanner對象,然後對@ComponentScan註解的屬性進行解析,並設置到scanner上,然後調用其doScan(basePackages)方法進行掃描:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

對掃描出的BeanDefinition,還應用了BeanPostProcessor,並進行了註冊。

5.2 @Conditional

在4.2.3節曾經看到,自動配置過濾器的作用就是對@Condition註解進行檢查,並排除掉不需要處理的配置。Spring Boot提供了大量的ConditionalOnXXX註解:

這些註解表示了在某些情況下才會生效,例如我們要防止重複創建某個Bean,就可以使用@ConditionalOnMissingBean,例如Mybatis就用到了這個註解:

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}

在OnBeanCondition類中對它進行了處理,自動引入的三個過濾器都繼承了SpringBootCondition,它的matches方法調用了getMatchOutcomes方法,該方法在OnBeanCondition有如下片段:

if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
    BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
    MatchResult matchResult = getMatchingBeans(context, spec);
    if (matchResult.isAnyMatched()) {
        String reason = createOnMissingBeanNoMatchReason(matchResult);
        return ConditionOutcome.noMatch(ConditionMessage
            .forCondition(ConditionalOnMissingBean.class, spec)
            .because(reason));
    }
    matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
        .didNotFind("any beans").atAll();
}

BeanSearchSpec是內部類,在構造方法中對註解的屬性進行了解析和設置,然後從註解、類型、名稱三個角度進行匹配,假如匹配結果不爲空,則說明已經創建過實例,則返回noMatch,它的源碼中將match屬性設爲false:

public static ConditionOutcome noMatch(ConditionMessage message) {
	return new ConditionOutcome(false, message);
}

在這之後,無論是在filter方法還是shouldSkip方法,都會通過調用matches方法得到不滿足條件的結果,從而不進行Bean的創建。其它Conditional註解的原理類似。

5.3 @Value

@Value註解是Spring提供的從properties/yaml配置文件讀取值的功能,可以將值自動注入到對象。下面是一個官方例子:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

//in properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

// in jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

首先來看它的源碼,注意到註釋中提到了AutowiredAnnotationBeanPostProcessor,它會將@Autowired和@Value註冊爲自動綁定類型,在其postProcessPropertyValues方法中,間接調用了InjectedElement的inject方法,並在AutowiredAnnotationBeanPostProcessor的內部類中進行了覆蓋實現。在該方法中,我們可以看到如下語句:

value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

於是來到DefaultListableBeanFactory,可以在doResolveDependency中看到如下片段:

Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
    if (value instanceof String) {
        String strVal = resolveEmbeddedValue((String) value);
        BeanDefinition bd = (beanName != null && containsBean(beanName) ?
            getMergedBeanDefinition(beanName) : null);
        value = evaluateBeanDefinitionString(strVal, bd);
	}
    ...
}

這裏先用AutowireCandidateResolver進行解析,結果不爲null則寫入BeanDefinition,看來就是這裏對@Value進行的處理。那麼看一下AutowireCandidateResolver的實現類,最後發現,在QualifierAnnotationAutowireCandidateResolver裏出現了Value.class的身影,於是定位到它的getSuggestedValue方法:

public Object getSuggestedValue(DependencyDescriptor descriptor) {
    Object value = findValue(descriptor.getAnnotations());
    if (value == null) {
        MethodParameter methodParam = descriptor.getMethodParameter();
        if (methodParam != null) {
            value = findValue(methodParam.getMethodAnnotations());
        }
    }
    return value;
}

可見核心邏輯在findValue:

protected Object findValue(Annotation[] annotationsToSearch) {
    if (annotationsToSearch.length > 0) {
        AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
        if (attr != null) {
            return extractValue(attr);
        }
    }
    return null;
}

valueAnnotationType正是Value.class,extractValue方法就是提取其value屬性的值。接下來的resolveEmbeddedValue調用了各種StringValueResolver進行處理,這裏就包括我們在AbstractEnvironment見過的PropertySourcesPropertyResolver,它會將包含"${"、"}"、":"的佔位符進行替換。

在inject方法的最後,有如下片段:

if (value != null) {
    ReflectionUtils.makeAccessible(field);
    field.set(bean, value);
}

field就是自動綁定的成員變量,即我們使用@Value或@Autowired註解的變量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章