深刻理解SpringBoot核心原理(一)--------启动机制(starter机制)

1、前言

  使用过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

2、起步依赖

  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

avatar
能够看出mybatis-spring-boot-starter并无任何源码,只有一个pom文件,它的做用就是帮咱们引入其它jar。

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

3、自动配置------------

1.基于java代码的bean配置

以mybatis为例,在上面的截图中,咱们发现mybatis-spring-boot-starter这个包帮咱们引入了mybatis-spring-boot-autoconfigure这个包,以下图: sql

avatar
里面有MybatisAutoConfiguration这个类,打开这个类看看有些什么东西。
avatar
熟悉@Configuration&、@Bean这两个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是经常使用的条件依赖注解有: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后自动配置才生效。

avatar

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又是如何被发现与加载的?

3.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容器。

4.Bean 加载

若是要让一个普通类交给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的注入的。

4、总结

咱们能够将自动配置的关键几步以及相应的注解总结以下:

一、@Configuration&与@Bean------>>>基于java代码的bean配置

二、@Conditional-------->>>>>>设置自动配置条件依赖

三、@EnableConfigurationProperties与@ConfigurationProperties->读取配置文件转换为bean。

四、@EnableAutoConfiguration、@AutoConfigurationPackage 与@Import->实现bean发现与加载。

相关文章
相关标签/搜索