javascript
深入理解 SpringBoot 启动机制(starter 机制)
一、前言
使用過springboot的同學(xué)應(yīng)該已經(jīng)知道,springboot通過默認(rèn)配置了很多框架的使用方式幫我們大大簡化了項(xiàng)目初始搭建以及開發(fā)過程。本文的目的就是一步步分析springboot的啟動(dòng)過程,這次主要是分析springboot特性自動(dòng)裝配。?
那么首先帶領(lǐng)大家回顧一下以往我們的web項(xiàng)目是如何搭建的,通常我們要搭建一個(gè)基于Spring的Web應(yīng)用,我們需要做以下一些工作:?
1、pom文件中引入相關(guān)jar包,包括spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相關(guān)jar ... ?
2、配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 ... ?
3、配置數(shù)據(jù)庫連接、配置spring事務(wù) ?
4、配置視圖解析器 ?
5、開啟注解、自動(dòng)掃描功能 ?
6、配置完成后部署tomcat、啟動(dòng)調(diào)試 ?...... ?
花在搭建一個(gè)初始項(xiàng)目,可能一個(gè)小時(shí)就過去了或者半天救過了,但是用了SpringBoot之后一切都會變得非常便捷,下面我們首先來分析一下SpringBoot的起步依賴以及自動(dòng)配置。
二、起步依賴
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 開發(fā)包 --><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包自動(dòng)幫我們引入了web模塊開發(fā)需要的相關(guān)jar包。mybatis-spring-boot-starter幫我們引入了dao開發(fā)相關(guān)的jar包。spring-boot-starter-xxx是官方提供的starter,xxx-spring-boot-starter是第三方提供的starter。截圖看一下我們的mybatis-spring-boot-starter
?
可以看出mybatis-spring-boot-starter并沒有任何源碼,只有一個(gè)pom文件,它的作用就是幫我們引入其它jar。
2.配置數(shù)據(jù)源
spring:datasource:type:com.zaxxer.hikari.HikariDataSourceurl:jdbc:mysql://127.0.0.1:3306/mybatis_testusername:rootpassword:rootdriver-class-name:com.mysql.jdbc.Driverhikari:# 最小空閑連接數(shù)量minimum-idle:5# 連接池最大連接數(shù),默認(rèn)是10maximum-pool-size:60# 此屬性控制從池返回的連接的默認(rèn)自動(dòng)提交行為,默認(rèn)值:trueauto-commit:true# 一個(gè)連接idle狀態(tài)的最大時(shí)長(毫秒),超時(shí)則被釋放(retired),缺省:10分鐘idle-timeout:600000# 此屬性控制池中連接的最長生命周期,值0表示無限生命周期,默認(rèn)1800000即30分鐘max-lifetime:1800000# 數(shù)據(jù)庫連接超時(shí)時(shí)間,默認(rèn)30秒,即30000connection-timeout:60000stater機(jī)制幫我們完成了項(xiàng)目起步所需要的的相關(guān)jar包。那問題又來了,傳統(tǒng)的spring應(yīng)用中不是要在application.xml中配置很多bean的嗎,比如dataSource的配置,transactionManager的配置 ...?springboot是如何幫我們完成這些bean的配置的?下面我們來分析這個(gè)過程
三、自動(dòng)配置
1.基于java代碼的bean配置
以mybatis為例,在上面的截圖中,我們發(fā)現(xiàn)mybatis-spring-boot-starter這個(gè)包幫我們引入了mybatis-spring-boot-autoconfigure這個(gè)包,如下圖:
?
里面有MybatisAutoConfiguration這個(gè)類,打開這個(gè)類看看有些什么東西。
?
熟悉@Configuration&、@Bean這兩個(gè)bean的同學(xué)或許已經(jīng)知道了。這兩個(gè)注解一起使用就可以創(chuàng)建一個(gè)基于java代碼的配置類,可以用來替代相應(yīng)的xml配置文件。@Configuration注解的類可以看作是能生產(chǎn)讓Spring IoC容器管理的Bean實(shí)例的工廠。@Bean注解告訴Spring,一個(gè)帶有@Bean的注解方法將返回一個(gè)對象,該對象應(yīng)該被注冊到spring容器中。所以上面的MybatisAutoConfiguration這個(gè)類,自動(dòng)幫我們生成了SqlSessionFactory這些Mybatis的重要實(shí)例并交給spring容器管理,從而完成bean的自動(dòng)注冊。
2.自動(dòng)配置條件依賴
從MybatisAutoConfiguration這個(gè)類中使用的注解可以看出,要完成自動(dòng)配置是有依賴條件的。
@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnBean(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) publicclass MybatisAutoConfiguration {privatestaticfinal Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);privatefinal MybatisProperties properties;privatefinal Interceptor[] interceptors;privatefinal ResourceLoader resourceLoader;privatefinal DatabaseIdProvider databaseIdProvider;privatefinal List<ConfigurationCustomizer> configurationCustomizers;......首先預(yù)習(xí)一下Springboot是常用的條件依賴注解有:
@ConditionalOnBean,僅在當(dāng)前上下文中存在某個(gè)bean時(shí),才會實(shí)例化這個(gè)Bean。
@ConditionalOnClass,某個(gè)class位于類路徑上,才會實(shí)例化這個(gè)Bean。
@ConditionalOnExpression,當(dāng)表達(dá)式為true的時(shí)候,才會實(shí)例化這個(gè)Bean。
@ConditionalOnMissingBean,僅在當(dāng)前上下文中不存在某個(gè)bean時(shí),才會實(shí)例化這個(gè)Bean。
@ConditionalOnMissingClass,某個(gè)class在類路徑上不存在的時(shí)候,才會實(shí)例化這個(gè)Bean。
@ConditionalOnNotWebApplication,不是web應(yīng)用時(shí)才會實(shí)例化這個(gè)Bean。
@AutoConfigureAfter,在某個(gè)bean完成自動(dòng)配置后實(shí)例化這個(gè)bean。
@AutoConfigureBefore,在某個(gè)bean完成自動(dòng)配置前實(shí)例化這個(gè)bean。
所以要完成Mybatis的自動(dòng)配置,需要在類路徑中存在SqlSessionFactory.class、SqlSessionFactoryBean.class這兩個(gè)類,需要存在DataSource這個(gè)bean且這個(gè)bean完成自動(dòng)注冊。進(jìn)入DataSourceAutoConfiguration這個(gè)類,可以看到這個(gè)類屬于這個(gè)包:org.springframework.boot.autoconfigure.jdbc 這個(gè)包又屬于spring-boot-autoconfigure-2.0.4.RELEASE.jar這個(gè)包,自動(dòng)配置這個(gè)包幫們引入了jdbc、kafka、logging、mail、mongo等包。很多包需要我們引入相應(yīng)jar后自動(dòng)配置才生效。
?
3.Bean參數(shù)的獲取
到此我們已經(jīng)知道了bean的配置過程,但是還沒有看到springboot是如何讀取yml或者properites配置文件的的屬性來創(chuàng)建數(shù)據(jù)源的?在DataSourceAutoConfiguration類里面,我們注意到使用了EnableConfigurationProperties這個(gè)注解。
@Configuration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @EnableConfigurationProperties(DataSourceProperties.class) @Import({ DataSourcePoolMetadataProvidersConfiguration.class,DataSourceInitializationConfiguration.class }) publicclass DataSourceAutoConfiguration {@Configuration@Conditional(EmbeddedDatabaseCondition.class)@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })@Import(EmbeddedDataSourceConfiguration.class)protectedstaticclass EmbeddedDatabaseConfiguration {} ......DataSourceProperties中封裝了數(shù)據(jù)源的各個(gè)屬性,且使用了注解ConfigurationProperties指定了配置文件的前綴。
@ConfigurationProperties(prefix = "spring.datasource") publicclass 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.*/privateboolean 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配置文件轉(zhuǎn)化為bean。@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在spring容器中是獲取不到y(tǒng)ml或者properties配置文件轉(zhuǎn)化的bean的。
通過這種方式,把yml或者properties配置參數(shù)轉(zhuǎn)化為bean,這些bean又是如何被發(fā)現(xiàn)與加載的?
3.Bean的發(fā)現(xiàn)
springboot默認(rèn)掃描啟動(dòng)類所在的包下的主類與子類的所有組件,但并沒有包括依賴包的中的類,那么依賴包中的bean是如何被發(fā)現(xiàn)和加載的?
我們通常在啟動(dòng)類中加@SpringBootApplication這個(gè)注解,點(diǎn)進(jìn)去看
@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*/......實(shí)際上重要的只有三個(gè)Annotation:
@Configuration(@SpringBootConfiguration里面還是應(yīng)用了@Configuration)
@EnableAutoConfiguration
@ComponentScan
@Configuration的作用上面我們已經(jīng)知道了,被注解的類將成為一個(gè)bean配置類。@ComponentScan的作用就是自動(dòng)掃描并加載符合條件的組件,比如@Component和@Repository等,最終將這些bean定義加載到spring容器中。
@EnableAutoConfiguration 這個(gè)注解的功能很重要,借助@Import的支持,收集和注冊依賴包中相關(guān)的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這兩個(gè)注解。@AutoConfigurationPackage的作用就是自動(dòng)配置的包,@Import導(dǎo)入需要自動(dòng)配置的組件。
@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.*/ staticclass Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {register(registry, new PackageImport(metadata).getPackageName());}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImport(metadata));}}new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
new AutoConfigurationPackages.PackageImport(metadata)
這兩句代碼的作用就是加載啟動(dòng)類所在的包下的主類與子類的所有組件注冊到spring容器,這就是前文所說的springboot默認(rèn)掃描啟動(dòng)類所在的包下的主類與子類的所有組件。
那問題又來了,要搜集并注冊到spring容器的那些beans來自哪里?
進(jìn)入 AutoConfigurationImportSelector類,
我們可以發(fā)現(xiàn)SpringFactoriesLoader.loadFactoryNames方法調(diào)用loadSpringFactories方法從所有的jar包中讀取META-INF/spring.factories文件信息。
下面是spring-boot-autoconfigure這個(gè)jar中spring.factories文件部分內(nèi)容,其中有一個(gè)key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定義了需要自動(dòng)配置的bean,通過讀取這個(gè)配置獲取一組@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,\每個(gè)xxxAutoConfiguration都是一個(gè)基于java的bean配置類。實(shí)際上,這些xxxAutoConfiguratio不是所有都會被加載,會根據(jù)xxxAutoConfiguration上的@ConditionalOnClass等條件判斷是否加載;通過反射機(jī)制將spring.factories中@Configuration類實(shí)例化為對應(yīng)的java實(shí)列。
到此我們已經(jīng)知道怎么發(fā)現(xiàn)要自動(dòng)配置的bean了,最后一步就是怎么樣將這些bean加載到spring容器。
4.Bean 加載
如果要讓一個(gè)普通類交給Spring容器管理,通常有以下方法:
1、使用 @Configuration與@Bean 注解
2、使用@Controller @Service @Repository @Component 注解標(biāo)注該類,然后啟用@ComponentScan自動(dòng)掃描
3、使用@Import 方法
springboot中使用了@Import 方法 @EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector實(shí)現(xiàn)了DeferredImportSelector接口, DeferredImportSelector接口繼承了ImportSelector接口,ImportSelector接口只有一個(gè)selectImports方法。
selectImports方法返回一組bean,@EnableAutoConfiguration注解借助@Import注解將這組bean注入到spring容器中,springboot正式通過這種機(jī)制來完成bean的注入的。
四、總結(jié)
我們可以將自動(dòng)配置的關(guān)鍵幾步以及相應(yīng)的注解總結(jié)如下:
1、@Configuration&與@Bean------>>>基于java代碼的bean配置
2、@Conditional-------->>>>>>設(shè)置自動(dòng)配置條件依賴
3、@EnableConfigurationProperties與@ConfigurationProperties->讀取配置文件轉(zhuǎn)換為bean。
4、@EnableAutoConfiguration、@AutoConfigurationPackage 與@Import->實(shí)現(xiàn)bean發(fā)現(xiàn)與加載。
總結(jié)
以上是生活随笔為你收集整理的深入理解 SpringBoot 启动机制(starter 机制)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手把手教你实现一个 JSON 解析器!
- 下一篇: UidGenerator