這一次的SpringBoot啟動解析,是因為一個面試題引起的

點贊再看,養(yǎng)成習(xí)慣!

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框架。通過Dispatcher Servlet, ModelAndView 和 View Resolver,開發(fā)web應(yīng)用變得很容易。解決的問題領(lǐng)域是網(wǎng)站應(yīng)用程序或者服務(wù)開發(fā)——URL路由、Session、模板引擎、靜態(tài)Web資源等等。
Spring Boot實現(xiàn)了自動配置,降低了項目搭建的復(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到底是怎么做到自動配置的?
從代碼里看項目SpringBoot的項目啟動類只有一個注解@SpringBootApplication和一個run方法。

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

直接看@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}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

@SpringBootApplication:包含了@SpringBootConfiguration(打開是@Configuration),@EnableAutoConfiguration,@ComponentScan注解。
@Configuration
JavaConfig形式的Spring Ioc容器的配置類使用的那個@Configuration,SpringBoot社區(qū)推薦使用基于JavaConfig的配置形式,所以,這里的啟動類標注了@Configuration之后,本身其實也是一個IoC容器的配置類。
對比一下傳統(tǒng)XML方式和config配置方式的區(qū)別:

  • XML聲明和定義配置方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context-3.0.xsd
                        http://www.springframework.org/schema/tx 
                        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    ">
    <bean id="app" class="com..." />
  • 用一個過濾器舉例,JavaConfig的配置方式是這樣:
@Configuration
public class DruidConfiguration {    
    @Bean
    public FilterRegistrationBean statFilter(){
        //創(chuàng)建過濾器
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        //設(shè)置過濾器過濾路徑
        filterRegistrationBean.addUrlPatterns("/*");
        //忽略過濾的形式
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

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

@ComponentScan

@ComponentScan對應(yīng)XML配置中的元素,@ComponentScan的功能其實就是自動掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。
我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現(xiàn)會從聲明@ComponentScan所在類的package進行掃描。
注:所以SpringBoot的啟動類最好是放在root package下,因為默認不指定basePackages。

@EnableAutoConfiguration

(核心內(nèi)容)看英文意思就是自動配置,概括一下就是,借助@Import的幫助,將所有符合自動配置條件的bean定義加載到IoC容器。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

    String[] excludeName() default {};
}

里面最關(guān)鍵的是@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器。該配置模塊的主要使用到了SpringFactoriesLoader。

SpringFactoriesLoader詳解

SpringFactoriesLoader為Spring工廠加載器,該對象提供了loadFactoryNames方法,入?yún)閒actoryClass和classLoader即需要傳入工廠類名稱和對應(yīng)的類加載器,方法會根據(jù)指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件,傳入的工廠類為接口,而文件中對應(yīng)的類則是接口的實現(xiàn)類,或最終作為實現(xiàn)類。

public abstract class SpringFactoriesLoader {
    //...
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        ...
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ....
    }
}

所以文件中一般為如下圖這種一對多的類名集合,獲取到這些實現(xiàn)類的類名后,loadFactoryNames方法返回類名集合,方法調(diào)用方得到這些集合后,再通過反射獲取這些類的類對象、構(gòu)造方法,最終生成實例。


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


AutoConfigurationImportSelector

繼續(xù)上面講的AutoConfigurationImportSelector.class。該類主要關(guān)注selectImports方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }

該方法在springboot啟動流程——bean實例化前被執(zhí)行,返回要實例化的類信息列表。如果獲取到類信息,spring可以通過類加載器將類加載到j(luò)vm中,現(xiàn)在我們已經(jīng)通過spring-boot的starter依賴方式依賴了我們需要的組件,那么這些組件的類信息在select方法中就可以被獲取到。

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;
    }

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

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,從而加載里面的類。我們就選取這個mybatis-spring-boot-autoconfigure下的spring.factories文件

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

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

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration
{
  private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
  private final MybatisProperties properties;
  private final Interceptor[] interceptors;
  private final ResourceLoader resourceLoader;
  private final DatabaseIdProvider databaseIdProvider;
  private final List<ConfigurationCustomizer> configurationCustomizers;
  • @Spring的Configuration是一個通過注解標注的springBean,
  • @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class})這個注解的意思是:當(dāng)存在SqlSessionFactory.class, SqlSessionFactoryBean.class這兩個類時才解析MybatisAutoConfiguration配置類,否則不解析這一個配置類。我們需要mybatis為我們返回會話對象,就必須有會話工廠相關(guān)類
  • @CondtionalOnBean(DataSource.class):只有處理已經(jīng)被聲明為bean的dataSource
  • @ConditionalOnMissingBean(MapperFactoryBean.class)這個注解的意思是如果容器中不存在name指定的bean則創(chuàng)建bean注入,否則不執(zhí)行

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

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

因為maven依賴的傳遞性,我們只要依賴starter就可以依賴到所有需要自動配置的類,實現(xiàn)開箱即用的功能。也體現(xiàn)出Springboot簡化了Spring框架帶來的大量XML配置以及復(fù)雜的依賴管理,讓開發(fā)人員可以更加關(guān)注業(yè)務(wù)邏輯的開發(fā)。

再貼個盜的圖SpringBoot的啟動結(jié)構(gòu)圖

寫在最后的話

今天,我們介紹了一個面試題引起的SpringBoot啟動解析

想要獲得更多關(guān)于spring boot的資料,徹底吃透,可以私信我關(guān)鍵詞[資料],或者看下方,獲取更多java相關(guān)知識.

~聯(lián)系方式~
wechat: Mlzg5201314zz
扣扣:2967728282///

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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