一個面試題引發的SpringBoot啓動解析

SpringBoot的故事從一個面試題開始

Spring Boot、Spring MVC 和 Spring 有什麼區別?

分開描述各自的特徵:
Spring 框架就像一個家族,有衆多衍生產品例如 boot、security、jpa等等。但他們的基礎都是Spring 的ioc和 aop,ioc 提供了依賴注入的容器, aop解決了面向橫切面的編程,而後在此二者的基礎上實現了其餘延伸產品的高級功能。css

Spring MVC提供了一種輕度耦合的方式來開發web應用。它是Spring的一個模塊,是一個web框架。經過Dispatcher Servlet, ModelAndView 和 View Resolver,開發web應用變得很容易。解決的問題領域是網站應用程序或者服務開發——URL路由、Session、模板引擎、靜態Web資源等等。web

Spring Boot實現了自動配置,下降了項目搭建的複雜度。它主要是爲了解決使用Spring框架須要進行大量的配置太麻煩的問題,因此它並非用來替代Spring的解決方案,而是和Spring框架緊密結合用於提高Spring開發者體驗的工具。同時它集成了大量經常使用的第三方庫配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot應用中這些第三方庫幾乎能夠零配置的開箱即用(out-of-the-box)。面試

因此,用最簡練的語言歸納就是:
Spring 是一個「引擎」;
Spring MVC 是基於Spring的一個 MVC 框架;
Spring Boot 是基於Spring4的條件註冊的一套快速開發整合包。spring

 

 

SpringBoot究竟是怎麼作到自動配置的?sql

從代碼裏看項目SpringBoot的項目啓動類只有一個註解@SpringBootApplication和一個run方法。編程

 
  1. @SpringBootApplicationspringboot

  2. public class Application {性能優化

  3. public static void main(String[] args) {mybatis

  4. SpringApplication.run(Application.class, args);架構

  5. }

  6. }

  7. 複製代碼

直接看@SpringBootApplication的代碼:

 
  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @SpringBootConfiguration

  6. @EnableAutoConfiguration

  7. @ComponentScan(

  8. excludeFilters = {@Filter(

  9. type = FilterType.CUSTOM,

  10. classes = {TypeExcludeFilter.class}

  11. ), @Filter(

  12. type = FilterType.CUSTOM,

  13. classes = {AutoConfigurationExcludeFilter.class}

  14. )}

  15. )

  16. public @interface SpringBootApplication {

  17. @AliasFor(

  18. annotation = EnableAutoConfiguration.class

  19. )

  20. Class<?>[] exclude() default {};

  21.  
  22. @AliasFor(

  23. annotation = EnableAutoConfiguration.class

  24. )

  25. String[] excludeName() default {};

  26.  
  27. @AliasFor(

  28. annotation = ComponentScan.class,

  29. attribute = "basePackages"

  30. )

  31. String[] scanBasePackages() default {};

  32.  
  33. @AliasFor(

  34. annotation = ComponentScan.class,

  35. attribute = "basePackageClasses"

  36. )

  37. Class<?>[] scanBasePackageClasses() default {};

  38. }

  39. 複製代碼

@SpringBootApplication:包含了@SpringBootConfiguration(打開是@Configuration),@EnableAutoConfiguration,@ComponentScan註解。

@Configuration

JavaConfig形式的Spring Ioc容器的配置類使用的那個@Configuration,SpringBoot社區推薦使用基於JavaConfig的配置形式,因此,這裏的啓動類標註了@Configuration以後,自己其實也是一個IoC容器的配置類。
對比一下傳統XML方式和config配置方式的區別:

  • XML聲明和定義配置方式:
 
  1. <?xml version="1.0" encoding="UTF-8"?>

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

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"

  4. xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"

  5. xsi:schemaLocation="http://www.springframework.org/schema/beans

  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

  7. http://www.springframework.org/schema/aop

  8. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd

  9. http://www.springframework.org/schema/context

  10. http://www.springframework.org/schema/context/spring-context-3.0.xsd

  11. http://www.springframework.org/schema/tx

  12. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

  13. ">

  14. <bean id="app" class="com..." />

  15. 複製代碼

  • 用一個過濾器舉例,JavaConfig的配置方式是這樣:
 
  1. @Configuration

  2. public class DruidConfiguration {

  3. @Bean

  4. public FilterRegistrationBean statFilter(){

  5. //建立過濾器

  6. FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());

  7. //設置過濾器過濾路徑

  8. filterRegistrationBean.addUrlPatterns("/*");

  9. //忽略過濾的形式

  10. filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

  11. return filterRegistrationBean;

  12. }

  13. }

  14.  
  15. 複製代碼

任何一個標註了@Configuration的Java類定義都是一個JavaConfig配置類。
任何一個標註了@Bean的方法,其返回值將做爲一個bean定義註冊到Spring的IoC容器,方法名將默認成該bean定義的id。

@ComponentScan

@ComponentScan對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描並加載符合條件的組件(好比@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。
咱們能夠經過basePackages等屬性來細粒度的定製@ComponentScan自動掃描的範圍,若是不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。
注:因此SpringBoot的啓動類最好是放在root package下,由於默認不指定basePackages。

@EnableAutoConfiguration

核心內容)看英文意思就是自動配置,歸納一下就是,藉助@Import的幫助,將全部符合自動配置條件的bean定義加載到IoC容器。

 
  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @AutoConfigurationPackage

  6. @Import({AutoConfigurationImportSelector.class})

  7. public @interface EnableAutoConfiguration {

  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

  9.  
  10. Class<?>[] exclude() default {};

  11.  
  12. String[] excludeName() default {};

  13. }

  14. 複製代碼

裏面最關鍵的是@Import(EnableAutoConfigurationImportSelector.class),藉助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration能夠幫助SpringBoot應用將全部符合條件的@Configuration配置都加載到當前SpringBoot建立並使用的IoC容器。該配置模塊的主要使用到了SpringFactoriesLoader。
在這裏順便給你們推薦一個架構交流羣:617434785,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源。相信對於已經工做和遇到技術瓶頸的碼友,在這個羣裏會有你須要的內容。

SpringFactoriesLoader詳解

SpringFactoriesLoader爲Spring工廠加載器,該對象提供了loadFactoryNames方法,入參爲factoryClass和classLoader即須要傳入工廠類名稱和對應的類加載器,方法會根據指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件,傳入的工廠類爲接口,而文件中對應的類則是接口的實現類,或最終做爲實現類。

 
  1. public abstract class SpringFactoriesLoader {

  2. //...

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

  4. ...

  5. }

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

  7. ....

  8. }

  9. }

  10. 複製代碼

因此文件中通常爲以下圖這種一對多的類名集合,獲取到這些實現類的類名後,loadFactoryNames方法返回類名集合,方法調用方獲得這些集合後,再經過反射獲取這些類的類對象、構造方法,最終生成實例。

 

 

下圖有助於咱們形象理解自動配置流程(盜個圖)

 

 

 

 

AutoConfigurationImportSelector

繼續上面講的AutoConfigurationImportSelector.class。該類主要關注selectImports方法

 
  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {

  2. if (!this.isEnabled(annotationMetadata)) {

  3. return NO_IMPORTS;

  4. } else {

  5. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

  6. AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

  7. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

  8. configurations = this.removeDuplicates(configurations);

  9. Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);

  10. this.checkExcludedClasses(configurations, exclusions);

  11. configurations.removeAll(exclusions);

  12. configurations = this.filter(configurations, autoConfigurationMetadata);

  13. this.fireAutoConfigurationImportEvents(configurations, exclusions);

  14. return StringUtils.toStringArray(configurations);

  15. }

  16. }

  17. 複製代碼

該方法在springboot啓動流程——bean實例化前被執行,返回要實例化的類信息列表。若是獲取到類信息,spring能夠經過類加載器將類加載到jvm中,如今咱們已經經過spring-boot的starter依賴方式依賴了咱們須要的組件,那麼這些組件的類信息在select方法中就能夠被獲取到。

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

  2. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

  3. 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.");

  4. return configurations;

  5. }

  6. 複製代碼

方法中的getCandidateConfigurations方法,其返回一個自動配置類的類名列表,方法調用了loadFactoryNames方法,查看該方法

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

  2. String factoryClassName = factoryClass.getName();

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

  4. }

  5. 複製代碼

自動配置器會跟根據傳入的factoryClass.getName()到項目系統路徑下全部的spring.factories文件中找到相應的key,從而加載裏面的類。咱們就選取這個mybatis-spring-boot-autoconfigure下的spring.factories文件

 
  1. # Auto Configure

  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

  3. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

  4. 複製代碼

進入org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration中,又是一堆註解

 
  1. @org.springframework.context.annotation.Configuration

  2. @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})

  3. @ConditionalOnBean({DataSource.class})

  4. @EnableConfigurationProperties({MybatisProperties.class})

  5. @AutoConfigureAfter({DataSourceAutoConfiguration.class})

  6. public class MybatisAutoConfiguration

  7. {

  8. private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

  9. private final MybatisProperties properties;

  10. private final Interceptor[] interceptors;

  11. private final ResourceLoader resourceLoader;

  12. private final DatabaseIdProvider databaseIdProvider;

  13. private final List<ConfigurationCustomizer> configurationCustomizers;

  14. 複製代碼

  • @Spring的Configuration是一個經過註解標註的springBean,

  • @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class})這個註解的意思是:當存在SqlSessionFactory.class, SqlSessionFactoryBean.class這兩個類時才解析MybatisAutoConfiguration配置類,不然不解析這一個配置類。咱們須要mybatis爲咱們返回會話對象,就必須有會話工廠相關類

  • @CondtionalOnBean(DataSource.class):只有處理已經被聲明爲bean的dataSource

  • @ConditionalOnMissingBean(MapperFactoryBean.class)這個註解的意思是若是容器中不存在name指定的bean則建立bean注入,不然不執行

以上配置能夠保證sqlSessionFactory、sqlSessionTemplate、dataSource等mybatis所需的組件都可被自動配置,@Configuration註解已經提供了Spring的上下文環境,因此以上組件的配置方式與Spring啓動時經過mybatis.xml文件進行配置起到一個效果。
只要一個基於SpringBoot項目的類路徑下存在SqlSessionFactory.class, SqlSessionFactoryBean.class,而且容器中已經註冊了dataSourceBean,就能夠觸發自動化配置,意思說咱們只要在maven的項目中加入了mybatis所須要的若干依賴,就能夠觸發自動配置,但引入mybatis原生依賴的話,每集成一個功能都要去修改其自動化配置類,那就得不到開箱即用的效果了。因此Spring-boot爲咱們提供了統一的starter能夠直接配置好相關的類,觸發自動配置所需的依賴(mybatis)以下:

 
  1. <dependency>

  2. <groupId>org.mybatis.spring.boot</groupId>

  3. <artifactId>mybatis-spring-boot-starter</artifactId>

  4. </dependency>

  5. 複製代碼

由於maven依賴的傳遞性,咱們只要依賴starter就能夠依賴到全部須要自動配置的類,實現開箱即用的功能。也體現出Springboot簡化了Spring框架帶來的大量XML配置以及複雜的依賴管理,讓開發人員能夠更加關注業務邏輯的開發。

再貼個盜的圖SpringBoot的啓動結構圖

 

相關文章
相關標籤/搜索