使用過springboot的同窗應該已經知道,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的起步依賴以及自動配置。java
1.在咱們的pom文件裏面引入如下jar:mysql
<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 web
2.配置數據源redis
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的配置的?下面咱們來分析這個過程spring
以mybatis爲例,在上面的截圖中,咱們發現mybatis-spring-boot-starter這個包幫咱們引入了mybatis-spring-boot-autoconfigure這個包,以下圖: sql
從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是經常使用的條件依賴註解有:tomcat
@ConditionalOnBean,僅在當前上下文中存在某個bean時,纔會實例化這個Bean。springboot
@ConditionalOnClass,某個class位於類路徑上,纔會實例化這個Bean。mybatis
@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後自動配置才生效。
到此咱們已經知道了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又是如何被發現與加載的?
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容器。
若是要讓一個普通類交給Spring容器管理,一般有如下方法:
一、使用 @Configuration與@Bean 註解
二、使用@Controller @Service @Repository @Component 註解標註該類,而後啓用@ComponentScan自動掃描
三、使用@Import 方法
springboot中使用了@Import 方法
@EnableAutoConfiguration註解中使用了@Import({AutoConfigurationImportSelector.class})註解,AutoConfigurationImportSelector實現了DeferredImportSelector接口,
DeferredImportSelector接口繼承了ImportSelector接口,ImportSelector接口只有一個selectImports方法。
selectImports方法返回一組bean,@EnableAutoConfiguration註解藉助@Import註解將這組bean注入到spring容器中,springboot正式經過這種機制來完成bean的注入的。
咱們能夠將自動配置的關鍵幾步以及相應的註解總結以下:
一、@Configuration&與@Bean------>>>基於java代碼的bean配置
二、@Conditional-------->>>>>>設置自動配置條件依賴
三、@EnableConfigurationProperties與@ConfigurationProperties->讀取配置文件轉換爲bean。
四、@EnableAutoConfiguration、@AutoConfigurationPackage 與@Import->實現bean發現與加載。