SpringBoot的故事從一個面試題開始
Spring Boot、Spring MVC 和 Spring 有什麼區別?
分開描述各自的特徵:
Spring 框架就像一個家族,有眾多衍生產品例如 boot、security、jpa等等。但他們的基礎都是Spring 的ioc和 aop,ioc 提供了依賴注入的容器, aop解決了面向橫切面的編程,然後在此兩者的基礎上實現了其他延伸產品的高級功能。
Spring MVC提供了一種輕度耦合的方式來開發web應用。它是Spring的一個模塊,是一個web框架。通過Dispatcher Servlet, ModelAndView 和 View Resolver,開發web應用變得很容易。解決的問題領域是網站應用程序或者服務開發——URL路由、Session、模板引擎、靜態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的條件註冊的一套快速開發整合包。
![一個面試題引起的SpringBoot啟動解析](http://p2.ttnews.xyz/loading.gif)
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社區推薦使用基於JavaConfig的配置形式,所以,這裡的啟動類標註了@Configuration之後,本身其實也是一個IoC容器的配置類。
對比一下傳統XML方式和config配置方式的區別:
- XML聲明和定義配置方式:
- 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
- ">
- 複製代碼
- 用一個過濾器舉例,JavaConfig的配置方式是這樣:
- @Configuration
- public class DruidConfiguration {
- @Bean
- public FilterRegistrationBean statFilter(){
- //創建過濾器
- FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
- //設置過濾器過濾路徑
- 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對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描並加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。
我們可以通過basePackages等屬性來細粒度的定製@ComponentScan自動掃描的範圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。
注:所以SpringBoot的啟動類最好是放在root package下,因為默認不指定basePackages。
@EnableAutoConfiguration
(核心內容)看英文意思就是自動配置,概括一下就是,藉助@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 {};
- }
- 複製代碼
裡面最關鍵的是@Import(EnableAutoConfigurationImportSelector.class),藉助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器。該配置模塊的主要使用到了SpringFactoriesLoader。
SpringFactoriesLoader詳解
SpringFactoriesLoader為Spring工廠加載器,該對象提供了loadFactoryNames方法,入參為factoryClass和classLoader即需要傳入工廠類名稱和對應的類加載器,方法會根據指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件,傳入的工廠類為接口,而文件中對應的類則是接口的實現類,或最終作為實現類。
- public abstract class SpringFactoriesLoader {
- //...
- public static
List loadFactories(Class factoryClass, ClassLoader classLoader) { - ...
- }
- public static List
loadFactoryNames(Class> factoryClass, ClassLoader classLoader) { - ....
- }
- }
- 複製代碼
所以文件中一般為如下圖這種一對多的類名集合,獲取到這些實現類的類名後,loadFactoryNames方法返回類名集合,方法調用方得到這些集合後,再通過反射獲取這些類的類對象、構造方法,最終生成實例。
![一個面試題引起的SpringBoot啟動解析](http://p2.ttnews.xyz/loading.gif)
下圖有助於我們形象理解自動配置流程(盜個圖)
AutoConfigurationImportSelector
繼續上面講的AutoConfigurationImportSelector.class。該類主要關注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
configurations = this.getCandidateConfigurations(annotationMetadata, attributes); - configurations = this.removeDuplicates(configurations);
- Set
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實例化前被執行,返回要實例化的類信息列表。如果獲取到類信息,spring可以通過類加載器將類加載到jvm中,現在我們已經通過spring-boot的starter依賴方式依賴了我們需要的組件,那麼這些組件的類信息在select方法中就可以被獲取到。
-
- protected List
getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { - List
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方法,其返回一個自動配置類的類名列表,方法調用了loadFactoryNames方法,查看該方法
- public static List
loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) { - String factoryClassName = factoryClass.getName();
- return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
- }
- 複製代碼
自動配置器會跟根據傳入的factoryClass.getName()到項目系統路徑下所有的spring.factories文件中找到相應的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
configurationCustomizers; - 複製代碼
- @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)如下:
-
org.mybatis.spring.boot -
mybatis-spring-boot-starter - 複製代碼
因為maven依賴的傳遞性,我們只要依賴starter就可以依賴到所有需要自動配置的類,實現開箱即用的功能。也體現出Springboot簡化了Spring框架帶來的大量XML配置以及複雜的依賴管理,讓開發人員可以更加關注業務邏輯的開發。
再貼個盜的圖SpringBoot的啟動結構圖
閱讀更多 歪脖攻城獅 的文章