springboot做爲一個基於spring開發的框架,天然也繼承了spring的容器屬性。容器中的bean天然成爲了springboot各類功能的基礎。本節就來分析一下springboot如何將各類bean加載進容器中。web
開始分析以前首先咱們先概覽一下springboot框架究竟加載了多少bean。在main函數中添加以下代碼,運行。spring
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
}
複製代碼
不出意外的話,控制檯會打印出上百個bean的名字。雖然咱們僅僅只寫了兩個類而已!那麼這些類的加載有何規則呢?相比於spring的xml配置文件,springboot的自動化配置又是如何實現的?這些都將在本節揭曉。springboot
public ConfigurableApplicationContext run(String... args) {
...
//建立ApplicationContext
context = createApplicationContext();
...
//作一些初始化配置
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
...
}
複製代碼
首先咱們進入SpringApplication的run方法中,在run方法中咱們看到和ApplicationContext有關的代碼一共有4行,第一行建立了ApplicationContext,第二行作了一些初始化配置,第三行調用了refresh方法,讀過spring源碼的話應該知道這個方法包含了ApplicationContext初始化最重要也最大部分的邏輯,因此這行待會會重點分析,最後一行是一個空方法,留着子類覆寫。bash
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
...
}
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
複製代碼
首先進入create方法,在SpringApplication初始化的時候,咱們已經知道了這是一個網絡服務,因此這邊建立的類是DEFAULT_SERVLET_WEB_CONTEXT_CLASS類,(org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext) 在這邊直接調用了無參構造函數。先進入構造函數看一下作了那些事情。網絡
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
複製代碼
初始化了reader和scanner組件,reader是用來註冊bean的,scanner是用來掃描bean的。這兩個組件初始化的邏輯都不復雜,讀者能夠自行理解。可是重點關注一個地方。在reader的構造函數中:app
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry, getOrCreateEnvironment(registry));
}
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
...
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
registerAnnotationConfigProcessors(registry, null);
}
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
...
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
...
}
複製代碼
一個ConfigurationClassPostProcessor的bean被注入到了容器中,這個地方留意一下,後面這個bean很重要。框架
建立完成了以後,咱們看一下prepareContext方法ide
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
...
load(context, sources.toArray(new Object[0]));
...
}
複製代碼
prepareContext方法中,調用了一些監聽器,和初始化接口,可是最重要的是load這個方法。load這個方法,將咱們main方法的這個類傳入了容器中。這個類上面有一個很是重要的註解SpringBootApplication。函數
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
}
}
複製代碼
下面就進入到了最重要的refresh方法,若是讀過《spring源碼深度解析》這本書的話,這個地方的邏輯應該感到很親切,沒讀過的話強烈建議讀一下,無論spring怎麼發展,基礎仍是那些的。post
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
...
}
複製代碼
因此refresh方法中的邏輯我也很少介紹了,直接進入主題。invokeBeanFactoryPostProcessors方法。
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
...
}
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
...
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
...
}
複製代碼
在invokeBeanFactoryPostProcessors方法中,從容器中獲取了BeanDefinitionRegistryPostProcessor類型的類,而後執行了這些類的postProcessBeanDefinitionRegistry方法。還記得上面我讓大家重點關注的ConfigurationClassPostProcessor麼,他就是實現了BeanDefinitionRegistryPostProcessor,因此這個地方會調用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法。那麼咱們進入方法瞧瞧。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
...
processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
...
//判斷@Configuration註解
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
...
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
//解析帶有@Configuration註解的類
parser.parse(candidates);
...
}
複製代碼
processConfigBeanDefinitions方法主要有兩個邏輯,首先判斷類上是否帶有@Configuration註解,而後解析該類。其實在這兒,主要解析的就是@SpringBootApplication註解。由於點開@SpringBootApplication註解的源碼
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@Configuration
public @interface SpringBootConfiguration {
}
複製代碼
@SpringBootApplication註解上面有@SpringBootConfiguration註解,然後者又包含了@Configuration註解,因此這個地方,解析的就是帶有@SpringBootApplication註解的類。進入parse方法。
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
...
this.deferredImportSelectorHandler.process();
}
複製代碼
主要有兩個邏輯,咱們一個一個來分析。首先再次進入parse方法
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
...
SourceClass sourceClass = asSourceClass(configClass);
do {
//進入這個方法
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
複製代碼
在doProcessConfigurationClass中,咱們看到了熟悉的Component,PropertySources,ComponentScan,ImportResource,以及Import註解,上述幾個註解的功能你們應該都很熟悉了,我就很少介紹了,這些註解在這兒就完成了他們的使命,通過這個方法後,咱們本身寫的類就會所有進入springboot容器中了。
下面開始分析this.deferredImportSelectorHandler.process();
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
...
}
複製代碼
進入方法後發現若是deferredImportSelectors爲空的話,就什麼都作不了。可是調用debug後發現這個地方是有值的,那麼他是何時被放進來的呢。咱們回頭看剛剛的doProcessConfigurationClass方法。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
...
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
...
}
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
...
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(
configClass, (DeferredImportSelector) selector);
...
}
}
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
...
this.deferredImportSelectors.add(holder);
}
}
複製代碼
在processImports發現了添加的痕跡。可是添加有個前提條件是要import導入的類selector instanceof DeferredImportSelector,這個條件是怎麼實現的呢?答案就在@SpringBootApplication註解中。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
複製代碼
因此到這兒咱們就知道了deferredImportSelectors裏面有一個元素,就是這邊的AutoConfigurationImportSelector。
因此到這兒,咱們就能夠接着分析process方法了
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
//註冊
deferredImports.forEach(handler::register);
//解析
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
複製代碼
一個註冊方法,一個解析方法,註冊方法邏輯比較簡單,咱們直接進入解析方法。
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
//這個地方看一下getImports方法
grouping.getImports().forEach(entry -> {
...
//這個方法標記一下,processImport待會回來
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
...
}
}
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
//重點看process方法
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
...
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
annotationMetadata);
...
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
...
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
...
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
...
return configurations;
}
複製代碼
SpringFactoriesLoader.loadFactoryNames這個方法熟悉麼,一直在用,因此話很少說,先看看getSpringFactoriesLoaderFactoryClass返回了一個什麼類。返回的是EnableAutoConfiguration.class; 因此進入配置文件查看。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
...
...
...
複製代碼
你應該會看到這麼長長的一串配置,這裏就是springboot自動化配置的中心了。我就以aop來展現一下springboot是如何簡化spring的配置的。
首先通過咱們剛剛的一串邏輯org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,這個類會被加載進容器中,那麼這個類,和aop又有啥關係呢。
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
複製代碼
查看該類的源碼,發現該類加載時有兩個判斷條件,容器中須要有EnableAspectJAutoProxy.class, Aspect.class, Advice.class,AnnotatedElement.class這幾個註解,或者有spring.aop相關的配置。(關於Conditional條件的機制後面再詳細解讀,這個地方大概瞭解一下便可)
若是咱們在啓動時的類上添加了EnableAspectJAutoProxy註解的話,該註解會加載AspectJAutoProxyRegistrar類,這個類又會向容器注入AnnotationAwareAspectJAutoProxyCreator類,然後者正是aop的核心類。只要這個類進入容器,容器就帶有了aop功能(aop如何實現的看我推薦的那本書,書上很詳細)。
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
複製代碼
那麼若是我沒有顯示的添加EnableAspectJAutoProxy註解會怎樣呢?若是沒有顯示添加的話,只要知足其餘條件,AopAutoConfiguration類依然會被加載進容器,而他進入容器後,裏面獲得兩個靜態類也會被掃描進容器,而這兩個類都是帶有EnableAspectJAutoProxy註解的,因此aop功能依然能夠實現。
因此當咱們得到了自動化配置的這些支持後,就該回到剛剛標記的processImport方法了。
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(
entry.getMetadata());
try {
//剛剛標記的方法
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
複製代碼
這個方法會把咱們得到的自動化配置相關支持所有導入容器,這樣在通過spring那一套加載邏輯以後,咱們的springboot項目就能夠得到各類咱們配置的功能了。