springboot-啟動原理

SPRINGBOOT啟動流程及其原理

Spring Boot、Spring MVC 和 Spring 有什么區(qū)別?

分別描述各自的特征:

Spring 框架就像一個家族,有眾多衍生產(chǎn)品例如 boot、security、jpa等等;但他們的基礎(chǔ)都是Spring 的ioc和 aop,ioc 提供了依賴注入的容器, aop解決了面向切面編程,然后在此兩者的基礎(chǔ)上實現(xiàn)了其他延伸產(chǎn)品的高級功能。

Spring MVC提供了一種輕度耦合的方式來開發(fā)web應(yīng)用;它是Spring的一個模塊,是一個web框架;通過DispatcherServlet, ModelAndView 和 View Resolver,開發(fā)web應(yīng)用變得很容易;解決的問題領(lǐng)域是網(wǎng)站應(yīng)用程序或者服務(wù)開發(fā)——URL路由、Session、模板引擎、靜態(tài)Web資源等等。

Spring Boot實現(xiàn)了auto-configuration自動配置(另外三大神器actuator監(jiān)控,cli命令行接口,starter依賴),降低了項目搭建的復(fù)雜度。它主要是為了解決使用Spring框架需要進行大量的配置太麻煩的問題,所以它并不是用來替代Spring的解決方案,而是和Spring框架緊密結(jié)合用于提升Spring開發(fā)者體驗的工具;同時它集成了大量常用的第三方庫配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot應(yīng)用中這些第三方庫幾乎可以零配置的開箱即用(out-of-the-box)。

所以,用最簡練的語言概括就是:

Spring 是一個“引擎”;

Spring MVC 是基于Spring的一個 MVC 框架;

Spring Boot 是基于Spring4的條件注冊的一套快速開發(fā)整合包。

一 springboot啟動原理及相關(guān)流程概覽

springboot是基于spring的新型的輕量級框架,最厲害的地方當屬自動配置。那我們就可以根據(jù)啟動流程和相關(guān)原理來看看,如何實現(xiàn)傳奇的自動配置。

image

二 springboot的啟動類入口

用過springboot的技術(shù)人員很顯而易見的兩者之間的差別就是視覺上很直觀的:springboot有自己獨立的啟動類(獨立程序)

|

1

2

3

4

5

6

|

@SpringBootApplication

public class Application {

public static void main(String[] args) {

SpringApplication.run(Application.``class``, args);

}

}

|

從上面代碼可以看出,Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)最為耀眼,所以要揭開SpringBoot的神秘面紗,我們要從這兩位開始就可以了。

三 單單是SpringBootApplication接口用到了這些注解

|

1

2

3

4

5

6

7

8

9

10

11

12

|

@Target``(ElementType.TYPE) ``// 注解的適用范圍,其中TYPE用于描述類、接口(包括包注解類型)或enum聲明

@Retention``(RetentionPolicy.RUNTIME) ``// 注解的生命周期,保留到class文件中(三個生命周期)

@Documented // 表明這個注解應(yīng)該被javadoc記錄

@Inherited // 子類可以繼承該注解

@SpringBootConfiguration // 繼承了Configuration,表示當前是注解類

@EnableAutoConfiguration // 開啟springboot的注解功能,springboot的四大神器之一,其借助@import的幫助

@ComponentScan``(excludeFilters = { ``// 掃描路徑設(shè)置(具體使用待確認)

@Filter``(type = FilterType.CUSTOM, classes = TypeExcludeFilter.``class``),

@Filter``(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.``class``) })

public @interface SpringBootApplication {

...

}

|

image

在其中比較重要的有三個注解,分別是:

1)@SpringBootConfiguration // 繼承了Configuration,表示當前是注解類

2)@EnableAutoConfiguration // 開啟springboot的注解功能,springboot的四大神器之一,其借助@import的幫助

3)@ComponentScan(excludeFilters = { // 掃描路徑設(shè)置(具體使用待確認)

接下來對三個注解一一詳解,增加對springbootApplication的理解:

1)@Configuration注解

按照原來xml配置文件的形式,在springboot中我們大多用配置類來解決配置問題

配置bean方式的不同:

a)xml配置文件的形式配置bean

|

1

2

3

4

5

6

7

|

<?xml version=``"1.0" encoding=``"UTF-8"``?>

<beans xmlns=``"http://www.springframework.org/schema/beans"

xmlns:xsi=``"http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation=``"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"

default``-lazy-init=``"true"``>

``

</beans>

|

b)java configuration的配置形式配置bean

|

1

2

3

4

|

@Configuration

public class MockConfiguration{

//bean定義

}

|

注入bean方式的不同:

a)xml配置文件的形式注入bean

|

1

2

3

|

<bean id=``"mockService" class``=``"..MockServiceImpl"``>

...

</bean>

|

b)java configuration的配置形式注入bean

|

1

2

3

4

5

6

7

|

@Configuration

public class MockConfiguration{

@Bean

public MockService mockService(){

return new MockServiceImpl();

}

}

|

任何一個標注了@Bean的方法,其返回值將作為一個bean定義注冊到Spring的IoC容器,方法名將默認成該bean定義的id。

表達bean之間依賴關(guān)系的不同:

a)xml配置文件的形式表達依賴關(guān)系

|

1

2

3

4

|

<bean id=``"mockService" class``=``"..MockServiceImpl"``>

<propery name =``"dependencyService" ref=``"dependencyService" />

</bean>

<bean id=``"dependencyService" class``=``"DependencyServiceImpl"``></bean>

|

b)java configuration配置的形式表達依賴關(guān)系(重點)

    如果一個bean A的定義依賴其他bean B,則直接調(diào)用對應(yīng)的JavaConfig類中依賴bean B的創(chuàng)建方法就可以了。

|

1

2

3

4

5

6

7

8

9

10

11

|

@Configuration

public class MockConfiguration{

@Bean

public MockService mockService(){

return new MockServiceImpl(dependencyService());

}

@Bean

public DependencyService dependencyService(){

return new DependencyServiceImpl();

}

}

|

2) @ComponentScan注解

作用:a)對應(yīng)xml配置中的元素;

b) (重點)ComponentScan的功能其實就是自動掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義;

c) 將這些bean定義加載到IoC容器中.

我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現(xiàn)會從聲明@ComponentScan所在類的package進行掃描。

注:所以SpringBoot的啟動類最好是放在root ``package``下,因為默認不指定basePackages

3) @EnableAutoConfiguration

此注解顧名思義是可以自動配置,所以應(yīng)該是springboot中最為重要的注解。

在spring框架中就提供了各種以@Enable開頭的注解,例如: @EnableScheduling、@EnableCaching、@EnableMBeanExport等; @EnableAutoConfiguration的理念和做事方式其實一脈相承簡單概括一下就是,借助@Import的支持,收集和注冊特定場景相關(guān)的bean定義。

    • @EnableScheduling是通過@Import將Spring調(diào)度框架相關(guān)的bean定義都加載到IoC容器【定時任務(wù)、時間調(diào)度任務(wù)】
    • @EnableMBeanExport是通過@Import將JMX相關(guān)的bean定義加載到IoC容器【監(jiān)控JVM運行時狀態(tài)】

@EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動配置條件的bean定義加載到IoC容器。

@EnableAutoConfiguration作為一個復(fù)合Annotation,其自身定義關(guān)鍵信息如下:

|

1

2

3

4

5

6

7

8

9

10

|

@SuppressWarnings``(``"deprecation"``)

@Target``(ElementType.TYPE)

@Retention``(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage``【重點注解】

@Import``(AutoConfigurationImportSelector.``class``)【重點注解】

public @interface EnableAutoConfiguration {

...

}

|

其中最重要的兩個注解已經(jīng)標注:1、@AutoConfigurationPackage【重點注解】2、@Import(AutoConfigurationImportSelector.class)【重點注解】

當然還有其中比較重要的一個類就是:EnableAutoConfigurationImportSelector.class

AutoConfigurationPackage注解:

|

1

2

3

4

5

6

7

8

|

@Target``(ElementType.TYPE)

@Retention``(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@Import``(AutoConfigurationPackages.Registrar.``class``)

public @interface AutoConfigurationPackage {

}

|

通過@Import(AutoConfigurationPackages.Registrar.class)

|

1

2

3

4

5

6

7

8

9

10

11

|

static class Registrar ``implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override

public void registerBeanDefinitions(AnnotationMetadata metadata,

BeanDefinitionRegistry registry) {

register(registry, ``new PackageImport(metadata).getPackageName());

}

……

}

|

它其實是注冊了一個Bean的定義;

new PackageImport(metadata).getPackageName(),它其實返回了當前主程序類的****同級以及子級的包組件(重點);

(重點)那這總體就是注冊當前主程序類的同級以及子級的包中的符合條件的Bean的定義?

image

以上圖為例,DemoApplication是和demo包同級,但是demo2這個類是DemoApplication的父級,和example包同級

也就是說,DemoApplication啟動加載的Bean中,并不會加載demo2,這也就是為什么,我們要把DemoApplication放在項目的最高級中。

Import(AutoConfigurationImportSelector.class)注解

image

(重點)可以從圖中看出 AutoConfigurationImportSelector 實現(xiàn)了 DeferredImportSelector 從 ImportSelector繼承的方法:selectImports。

|

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

|

@Override

public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!isEnabled(annotationMetadata)) {

return NO_IMPORTS;

}

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader

.loadMetadata(``this``.beanClassLoader);

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 StringUtils.toStringArray(configurations);

}

|

第9行List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);其實是去加載各個組件jar下的 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。

該方法在springboot啟動流程——bean實例化前被執(zhí)行,返回要實例化的類信息列表;

如果獲取到類信息,spring可以通過類加載器將類加載到j(luò)vm中,現(xiàn)在我們已經(jīng)通過spring-boot的starter依賴方式依賴了我們需要的組件,那么這些組件的類信息在select方法中就可以被獲取到。

|

1

2

3

4

5

|

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(``this``.getSpringFactoriesLoaderFactoryClass(), ``this``.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;

}

|

其返回一個自動配置類的類名列表,方法調(diào)用了loadFactoryNames方法,查看該方法

|

1

2

3

4

|

public static List<String> loadFactoryNames(Class<?> factoryClass, ``@Nullable ClassLoader classLoader) {

String factoryClassName = factoryClass.getName();

return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

}

|

自動配置器會跟根據(jù)傳入的factoryClass.getName()到項目系統(tǒng)路徑下所有的spring.factories文件中找到相應(yīng)的key,從而加載里面的類。

這個外部文件,有很多自動配置的類。如下:

image

(重點)其中,最關(guān)鍵的要屬@Import(AutoConfigurationImportSelector.class),借助AutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件(spring.factories)的bean定義(如Java Config@Configuration配置)都加載到當前SpringBoot創(chuàng)建并使用的IoC容器。就像一只“八爪魚”一樣。

image

自動配置幕后英雄:SpringFactoriesLoader詳解

借助于Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!

SpringFactoriesLoader屬于Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置,加載工廠類

SpringFactoriesLoader為Spring工廠加載器,該對象提供了loadFactoryNames方法,入?yún)閒actoryClass和classLoader即需要傳入工廠類名稱和對應(yīng)的類加載器,方法會根據(jù)指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件;

傳入的工廠類為接口,而文件中對應(yīng)的類則是接口的實現(xiàn)類,或最終作為實現(xiàn)類。

|

1

2

3

4

5

6

7

8

9

10

11

|

public abstract class SpringFactoriesLoader {

//...

public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {

...

}

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {

....

}

}

|

配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應(yīng)的一組@Configuration類

image

上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內(nèi)容,可以很好地說明問題。

(重點)所以,@EnableAutoConfiguration自動配置的魔法其實就變成了:

從classpath中搜尋所有的META-INF/spring.factories配置文件,并將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration對應(yīng)的配置項通過反射(Java Refletion)實例化為對應(yīng)的標注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個并加載到IoC容器。

image

四 springboot啟動流程概覽圖

image
image

五 深入探索SpringApplication執(zhí)行流程

image
image
image

|

1

|

public class EventPublishingRunListener ``implements SpringApplicationRunListener, Ordered

|

EventPublishingRunListener實現(xiàn)了SpringApplicationRunListener接口;

實現(xiàn)了方法,下面是部分方法源碼

|

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

|

@Override

public void starting() {

this``.initialMulticaster.multicastEvent(

new ApplicationStartingEvent(``this``.application, ``this``.args));

}

@Override

public void environmentPrepared(ConfigurableEnvironment environment) {

this``.initialMulticaster.multicastEvent(``new ApplicationEnvironmentPreparedEvent(

this``.application, ``this``.args, environment));

}

@Override

public void contextPrepared(ConfigurableApplicationContext context) {

this``.initialMulticaster.multicastEvent(``new ApplicationContextInitializedEvent(

this``.application, ``this``.args, context));

}

@Override

public void contextLoaded(ConfigurableApplicationContext context) {

.....................

|

簡單了解下Bean的生命周期

image

一個Bean的構(gòu)造函數(shù)初始化時是最先執(zhí)行的,這個時候,bean屬性還沒有被注入;

@PostConstruct注解的方法優(yōu)先于InitializingBean的afterPropertiesSet執(zhí)行,這時Bean的屬性竟然被注入了;

spring很多組件的初始化都放在afterPropertiesSet做,想和spring一起啟動,可以放在這里啟動;

spring為bean提供了兩種初始化bean的方式,實現(xiàn)InitializingBean接口,實現(xiàn)afterPropertiesSet方法,或者在配置文件中同過init-method指定,兩種方式可以同時使用;

實現(xiàn)InitializingBean接口是直接調(diào)用afterPropertiesSet方法,比通過反射調(diào)用init-method指定的方法效率相對來說要高點;但是init-method方式消除了對spring的依賴;

如果調(diào)用afterPropertiesSet方法時出錯,則不調(diào)用init-method指定的方法。

Bean在實例化的過程中:

Constructor > @PostConstruct > InitializingBean > init-method

BeanFactory 和ApplicationContext的區(qū)別

BeanFactory和ApplicationContext都是接口,并且ApplicationContext間接繼承了BeanFactory。

BeanFactory是Spring中最底層的接口,提供了最簡單的容器的功能,只提供了實例化對象和獲取對象的功能,而ApplicationContext是Spring的一個更高級的容器,提供了更多的有用的功能。

ApplicationContext提供的額外的功能:獲取Bean的詳細信息(如定義、類型)、國際化的功能、統(tǒng)一加載資源的功能、強大的事件機制、對Web應(yīng)用的支持等等。

加載方式的區(qū)別:BeanFactory采用的是延遲加載的形式來注入Bean;ApplicationContext則相反的,它是在Ioc啟動時就一次性創(chuàng)建所有的Bean,好處是可以馬上發(fā)現(xiàn)Spring配置文件中的錯誤,壞處是造成浪費。

|

1

2

|

public interface ApplicationContext ``extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,

MessageSource, ApplicationEventPublisher, ResourcePatternResolver

|

SpringMVC****處理請求的****流程

1、用戶發(fā)送請求至前端控制器DispatcherServlet

2、DispatcherServlet收到請求調(diào)用HandlerMapping處理器映射器。

3、處理器映射器根據(jù)請求url找到具體的處理器,生成處理器對象Handler及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。

4、DispatcherServlet通過HandlerAdapter(讓Handler實現(xiàn)更加靈活)處理器適配器調(diào)用處理器

5、執(zhí)行處理器(Controller,也叫后端控制器)。

6、Controller執(zhí)行完成返回ModelAndView(連接業(yè)務(wù)邏輯層和展示層的橋梁,持有一個ModelMap對象和一個View對象)。

7、HandlerAdapter將controller執(zhí)行結(jié)果ModelAndView返回給DispatcherServlet

8、DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器

9、ViewReslover解析后返回具體View

10、DispatcherServlet對View進行渲染視圖(將ModelMap模型數(shù)據(jù)填充至視圖中)。

11、DispatcherServlet響應(yīng)用戶

BEANFACTORY和FACTORYBEAN的區(qū)別與聯(lián)系

  1. 兩者都是接口;
  2. BeanFactory主要是用來創(chuàng)建Bean和獲得Bean的;
  3. FactoryBean跟普通Bean不同,其返回的對象不是指定類的一個實例,而是該FactoryBean的getObject方法所返回的對象;
  4. 通過BeanFactory和beanName獲取bean時,如果beanName不加&則獲取到對應(yīng)bean的實例;如果beanName加上&,則獲取到FactoryBean本身的實例
  5. FactoryBean 通常是用來創(chuàng)建****比較復(fù)雜的bean****(如創(chuàng)建mybatis的SqlSessionFactory很復(fù)雜),一般的bean 直接用xml配置即可,但如果創(chuàng)建一個bean的創(chuàng)建過程中涉及到很多**其他的bean **和復(fù)雜的邏輯,用xml配置比較困難,這時可以考慮用FactoryBean。

Bean的循環(huán)依賴

https://blog.csdn.net/itmrchen/article/details/90201279

對于Spring中Bean的管理,下圖一目了然:

image

先調(diào)用構(gòu)造函數(shù)進行實例化,然后填充屬性,再接著進行其他附加操作和初始化,正是這樣的生命周期,才有了Spring的解決循環(huán)依賴,這樣的解決機制是根據(jù)Spring框架內(nèi)定義的三級緩存來實現(xiàn)的,也就是說:三級緩存解決了Bean之間的循環(huán)依賴。我們從源碼中來說明。

先來看Spring中Bean工廠是怎么獲取Bean的(AbstractBeanFactory中):


image
image
image
image

一級一級向下尋找,找出了前面提到的三級緩存,也就是三個Map集合類:

singletonObjects:第一級緩存,里面放置的是已經(jīng)實例化好的單例對象;

earlySingletonObjects:第二級緩存,里面存放的是提前曝光的單例對象;

singletonFactories:第三級緩存,里面存放的是將要被實例化的對象的對象工廠。

所以當一個Bean調(diào)用構(gòu)造函數(shù)進行實例化后,即使set屬性還未填充,就可以通過三級緩存向外暴露依賴的引用值進行set(所以循環(huán)依賴問題的解決也是基于Java的引用傳遞),這也說明了另外一點,基于構(gòu)造函數(shù)的注入,如果有循環(huán)依賴,Spring是不能夠解決的。

還要說明一點,Spring默認的Bean Scope是單例的,而三級緩存中都包含singleton,可見是對于單例Bean之間的循環(huán)依賴的解決,Spring是通過三級緩存來實現(xiàn)的。

同一個類中調(diào)用 @Transaction注解的方法會有事務(wù)效果嗎?

沒有,可以Autowired注入自己,然后再調(diào)用注入的類中的方法,即自己依賴自己,循環(huán)依賴;

這里在一個內(nèi)部調(diào)用應(yīng)該是相當于單純的調(diào)用方法this.methodName(),并沒有AOP動態(tài)代理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容