目錄
2.2 配置ApplicationContextInitializer、ApplicationListener
3.1 SpringApplicationRunListener
3.2.2 ApplicationEnvironmentPreparedEvent事件的處理
1.Spring Boot簡介
Spring MVC非常強大,但是也存在以下問題:
- 配置繁瑣,哪怕一個最小項目,也要配置至少三個XML文件
- 依賴Tomcat等Web容器運行,增加了維護難度
- 與常用組件集成起來不方便,例如與Mybatis集成,每個項目都要配置數據源、事務等,但是這些配置實際都是通用的,完全可以按照約定提供默認情況
Spring Boot就是爲了解決這一情況的,它具有如下特點(來源於百度百科):
- 創建獨立的Spring應用程序
- 嵌入的Tomcat,無需部署WAR文件
- 簡化Maven配置
- 自動配置Spring
- 提供生產就緒型功能,如指標,健康檢查和外部配置
- 絕對沒有代碼生成並且對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註解的變量。