SpringBoot 啓動機制

  • 一、前言
  • 二、起步依賴
  • 三、自動配置
    1.基於java代碼的bean配置
    2.自動配置條件依賴
    3.Bean參數的獲取
    4.Bean的發現
    5.Bean 加載
  • 四、總結

一、前言

springboot通過默認配置了很多框架的使用方式幫我們大大簡化了項目初始搭建以及開發過程。本文的目的就是一步步分析springboot的啓動過程,這次主要是分析springboot特性自動裝配。

那麼首先帶領大家回顧一下以往我們的web項目是如何搭建的,通常我們要搭建一個基於Spring的Web應用,我們需要做以下一些工作:

1、pom文件中引入相關jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相關jar …

2、配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 …

3、配置數據庫連接、配置spring事務

4、配置視圖解析器

5、開啓註解、自動掃描功能

6、配置完成後部署tomcat、啓動調試  …

花在搭建一個初始項目,可能一個小時就過去了或者半天救過了,但是用了SpringBoot之後一切都會變得非常便捷,下面我們來分析一下SpringBoot的起步依賴以及自動配置。

二、起步依賴

1.在pom文件裏面引入以下jar:

<modelVersion>4.0.0</modelVersion>
   <parent>
   	<groupId>org.springframework.boot</groupId>
   	<artifactId>spring-boot-starter-parent</artifactId>
   	<version>2.0.4.RELEASE</version>
   	<relativePath /> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.example</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>demo</name>
   <description>Demo project for Spring Boot</description>

   <properties>
   	<java.version>1.8</java.version>
   </properties>

   <dependencies>
   	<dependency>
   		<groupId>org.springframework.boot</groupId>
   		<artifactId>spring-boot-starter-web</artifactId>
   	</dependency>

   	<dependency>
   		<groupId>org.springframework.boot</groupId>
   		<artifactId>spring-boot-starter-test</artifactId>
   		<scope>test</scope>
   	</dependency>

   	<!--mybatis 開發包 -->
   	<dependency>
   		<groupId>org.mybatis.spring.boot</groupId>
   		<artifactId>mybatis-spring-boot-starter</artifactId>
   		<version>1.3.2</version>
   	</dependency>
   	<!--springboot web模塊支持 -->
   	<dependency>
   		<groupId>org.springframework.boot</groupId>
   		<artifactId>spring-boot-starter-web</artifactId>
   	</dependency>
   	<dependency>
   		<groupId>mysql</groupId>
   		<artifactId>mysql-connector-java</artifactId>
   		<scope>runtime</scope>
   	</dependency>
   </dependencies>

   <build>
   	<plugins>
   		<plugin>
   			<groupId>org.springframework.boot</groupId>
   			<artifactId>spring-boot-maven-plugin</artifactId>
   		</plugin>
   	</plugins>
   </build>

spring-boot-starter-web包自動幫我們引入了web模塊開發需要的相關jar包。mybatis-spring-boot-starter幫我們引入了dao開發相關的jar包。spring-boot-starter-xxx是官方提供的starter,xxx-spring-boot-starter是第三方提供的starter。截圖看一下我們的mybatis-spring-boot-starter
在這裏插入圖片描述
可以看出mybatis-spring-boot-starter並沒有任何源碼,只有一個pom文件,它的作用就是幫我們引入其它jar。

2.配置數據源

spring:
 datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    hikari:
     # 最小空閒連接數量
     minimum-idle: 5
     # 連接池最大連接數,默認是10
     maximum-pool-size: 60
     # 此屬性控制從池返回的連接的默認自動提交行爲,默認值:true
     auto-commit: true
     # 一個連接idle狀態的最大時長(毫秒),超時則被釋放(retired),缺省:10分鐘
     idle-timeout: 600000
     # 此屬性控制池中連接的最長生命週期,值0表示無限生命週期,默認1800000即30分鐘
     max-lifetime: 1800000
     # 數據庫連接超時時間,默認30秒,即30000
     connection-timeout: 60000

stater機制幫我們完成了項目起步所需要的的相關jar包。那問題又來了,傳統的spring應用中不是要在application.xml中配置很多bean的嗎,比如dataSource的配置,transactionManager的配置 … springboot是如何幫我們完成這些bean的配置的?下面我們來分析這個過程。

三、自動配置

1.基於java代碼的bean配置

以mybatis爲例,在上面的截圖中,我們發現mybatis-spring-boot-starter這個包幫我們引入了mybatis-spring-boot-autoconfigure這個包,如下圖:
在這裏插入圖片描述
裏面有MybatisAutoConfiguration這個類,打開這個類看看有些什麼東西。
在這裏插入圖片描述
@Configuration&、@Bean 這兩個註解一起使用就可以創建一個基於java代碼的配置類,可以用來替代相應的xml配置文件。
@Configuration註解的類可以看作是能生產讓Spring IoC容器管理的Bean實例的工廠。
@Bean註解告訴Spring,一個帶有@Bean的註解方法將返回一個對象,該對象應該被註冊到spring容器中。所以上面的MybatisAutoConfiguration這個類,自動幫我們生成了SqlSessionFactory這些Mybatis的重要實例並交給spring容器管理,從而完成bean的自動註冊。

2.自動配置條件依賴

從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;
 ......

首先預習一下Springboot是常用的條件依賴註解有:

@ConditionalOnBean,僅在當前上下文中存在某個bean時,纔會實例化這個Bean。

@ConditionalOnClass,某個class位於類路徑上,纔會實例化這個Bean。

@ConditionalOnExpression,當表達式爲true的時候,纔會實例化這個Bean。

@ConditionalOnMissingBean,僅在當前上下文中不存在某個bean時,纔會實例化這個Bean。

@ConditionalOnMissingClass,某個class在類路徑上不存在的時候,纔會實例化這個Bean。

@ConditionalOnNotWebApplication,不是web應用時纔會實例化這個Bean。

@AutoConfigureAfter,在某個bean完成自動配置後實例化這個bean。

@AutoConfigureBefore,在某個bean完成自動配置前實例化這個bean。

所以要完成Mybatis的自動配置,需要在類路徑中存在SqlSessionFactory.class、SqlSessionFactoryBean.class這兩個類,需要存在DataSource這個bean且這個bean完成自動註冊。進入DataSourceAutoConfiguration這個類,可以看到這個類屬於這個包:org.springframework.boot.autoconfigure.jdbc 這個包又屬於spring-boot-autoconfigure-2.0.4.RELEASE.jar這個包,自動配置這個包幫們引入了jdbc、kafka、logging、mail、mongo等包。很多包需要我們引入相應jar後自動配置才生效。

3.Bean參數的獲取

到此我們已經知道了bean的配置過程,但是還沒有看到springboot是如何讀取yml或者properites配置文件的的屬性來創建數據源的?在DataSourceAutoConfiguration類裏面,我們注意到使用了EnableConfigurationProperties這個註解。

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
		DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

	@Configuration
	@Conditional(EmbeddedDatabaseCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import(EmbeddedDataSourceConfiguration.class)
	protected static class EmbeddedDatabaseConfiguration {

	}
......

DataSourceProperties中封裝了數據源的各個屬性,且使用了註解ConfigurationProperties指定了配置文件的前綴。

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

	private ClassLoader classLoader;

	/**
	 * Name of the datasource. Default to "testdb" when using an embedded database.
	 */
	private String name;

	/**
	 * Whether to generate a random datasource name.
	 */
	private boolean generateUniqueName;

	/**
	 * Fully qualified name of the connection pool implementation to use. By default, it
	 * is auto-detected from the classpath.
	 */
	private Class<? extends DataSource> type;

	/**
	 * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
	 */
	private String driverClassName;

	/**
	 * JDBC URL of the database.
	 */
	private String url;

	/**
	 * Login username of the database.
	 */
	private String username;

	/**
	 * Login password of the database.
	 */
	private String password;

	/**
	 * JNDI location of the datasource. Class, url, username & password are ignored when
	 * set.
	 */
	private String jndiName;

	/**
	 * Initialize the datasource with available DDL and DML scripts.
	 */
	private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;

	/**
	 * Platform to use in the DDL or DML scripts (such as schema-${platform}.sql or
	 * data-${platform}.sql).
	 */
	private String platform = "all";

	/**
	 * Schema (DDL) script resource references.
	 */
	private List<String> schema;

	/**
	 * Username of the database to execute DDL scripts (if different).
	 */
	private String schemaUsername;

	/**
	 * Password of the database to execute DDL scripts (if different).
	 */
	private String schemaPassword;

	/**
	 * Data (DML) script resource references.
	 */
	private List<String> data;

	......

通過以上分析,我們可以得知:@ConfigurationProperties註解的作用是把yml或者properties配置文件轉化爲bean。@EnableConfigurationProperties註解的作用是使@ConfigurationProperties註解生效。如果只配置@ConfigurationProperties註解,在spring容器中是獲取不到yml或者properties配置文件轉化的bean的。

通過這種方式,把yml或者properties配置參數轉化爲bean,這些bean又是如何被發現與加載的?

4.Bean的發現

springboot默認掃描啓動類所在的包下的主類與子類的所有組件,但並沒有包括依賴包的中的類,那麼依賴包中的bean是如何被發現和加載的?我們通常在啓動類中加@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 {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	 ......

實際上重要的有三個Annotation:

@Configuration(@SpringBootConfiguration裏面還是應用了@Configuration)

@EnableAutoConfiguration

@ComponentScan

@Configuration的作用上面我們已經知道了,被註解的類將成爲一個bean配置類。@ComponentScan的作用就是自動掃描並加載符合條件的組件,比如@Component和@Repository等,最終將這些bean定義加載到spring容器中。

@EnableAutoConfiguration 這個註解的功能很重要,藉助@Import的支持,收集和註冊依賴包中相關的bean定義。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

如上源碼,@EnableAutoConfiguration註解引入了@AutoConfigurationPackage和@Import這兩個註解。@AutoConfigurationPackage的作用就是自動配置的包,@Import導入需要自動配置的組件。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}


/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		register(registry, new PackageImport(metadata).getPackageName());
	}

	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new PackageImport(metadata));
	}

}

new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()

new AutoConfigurationPackages.PackageImport(metadata)

這兩句代碼的作用就是加載啓動類所在的包下的主類與子類的所有組件註冊到spring容器,這就是前文所說的springboot默認掃描啓動類所在的包下的主類與子類的所有組件。

那問題又來了,要蒐集並註冊到spring容器的那些beans來自哪裏?
進入 AutoConfigurationImportSelector類,

我們可以發現SpringFactoriesLoader.loadFactoryNames方法調用loadSpringFactories方法從所有的jar包中讀取META-INF/spring.factories文件信息。

下面是spring-boot-autoconfigure這個jar中spring.factories文件部分內容,其中有一個key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定義了需要自動配置的bean,通過讀取這個配置獲取一組@Configuration類。

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
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,\

每個xxxAutoConfiguration都是一個基於java的bean配置類。實際上,這些xxxAutoConfiguratio不是所有都會被加載,會根據xxxAutoConfiguration上的@ConditionalOnClass等條件判斷是否加載;通過反射機制將spring.factories中@Configuration類實例化爲對應的java實列。

到此我們已經知道怎麼發現要自動配置的bean了,最後一步就是怎麼樣將這些bean加載到spring容器。

5.Bean 加載

如果要讓一個普通類交給Spring容器管理,通常有以下方法:

1、使用 @Configuration與@Bean 註解

2、使用@Controller @Service @Repository @Component 註解標註該類,然後啓用@ComponentScan自動掃描

3、使用@Import 方法

springboot中使用了@Import 方法 @EnableAutoConfiguration註解中使用了@Import({AutoConfigurationImportSelector.class})註解,AutoConfigurationImportSelector實現了DeferredImportSelector接口, DeferredImportSelector接口繼承了ImportSelector接口,ImportSelector接口只有一個selectImports方法。

selectImports方法返回一組bean,@EnableAutoConfiguration註解藉助@Import註解將這組bean注入到spring容器中,springboot正式通過這種機制來完成bean的注入的。

四、總結

我們可以將自動配置的關鍵幾步以及相應的註解總結如下:

1、@Configuration&與@Bean------>>>基於java代碼的bean配置

2、@Conditional-------->>>>>>設置自動配置條件依賴

3、@EnableConfigurationProperties與@ConfigurationProperties->讀取配置文件轉換爲bean。

4、@EnableAutoConfiguration、@AutoConfigurationPackage 與@Import->實現bean發現與加載。



原文:
深入理解 SpringBoot 啓動機制(starter 機制)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章