https://www.toutiao.com/a6722702681812517379/?tt_from=mobile_qq&utm_campaign=client_share&app=news_article&utm_source=mobile_qq&iid=63023727700&utm_medium=toutiao_ios

一、前言

使用过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、启动调试
  7. ……

花在搭建一个初始项目,可能一个小时就过去了或者半天救过了,但是用了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这两个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 {@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)

这两句代码的作用就是加载启动类所在的包下的主类与子类的所有组件注册到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容器管理,通常有以下方法:

  • 使用 @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发现与加载。

深入理解SpringBoot启动机制(starter机制)相关推荐

  1. 深入理解 SpringBoot 启动机制(starter 机制)

    一.前言 使用过springboot的同学应该已经知道,springboot通过默认配置了很多框架的使用方式帮我们大大简化了项目初始搭建以及开发过程.本文的目的就是一步步分析springboot的启动 ...

  2. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  3. SpringBoot启动全流程源码解析(超详细版)

    我们在使用SpringBoot启动项目的时候,可能只需加一个注解,然后启动main,整个项目就运行了起来,但事实真的是所见即所得吗,还是SpringBoot在背后默默做了很多?本文会通过源码解析的方式 ...

  4. java idle 机制_深入springboot原理——一步步分析springboot启动机制(starter机制)...

    前言 使用过springboot的同学应该已经知道,springboot通过默认配置了很多框架的使用方式帮我们大大简化了项目初始搭建以及开发过程.本文的目的就是一步步分析springboot的启动过程 ...

  5. SpringBoot学习(二)探究Springboot启动机制

    引言: SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起探究一下SpringBoot的启动原 ...

  6. springBoot启动事件监听机制

    springBoot启动之事件监听机制源码解析 1. Java的事件监听机制 在进行正式的分析之前,先介绍一下Java的事件监听机制.参考05–SpringBoot启动之事件监听机制 Java事件监听 ...

  7. onresize事件会被多次触发_玩转SpringBoot之通过事件机制参与SpringBoot应用的启动过程...

    生命周期和事件监听 一个应用的启动过程和关闭过程是归属到"生命周期"这个概念的范畴. 典型的设计是在启动和关闭过程中会触发一系列的"事件",我们只要监听这些事件 ...

  8. Spring的加载机制导致不同SpringBoot启动方式下@Value注解失效

    问题参考链接:https://blog.csdn.net/u011958281/article/details/81531676 背景: 项目使用application.yml文件自定义参数,我在配置 ...

  9. 再次理解STM32中的堆栈机制

    再次理解STM32中的堆栈机制 刚拿到STM32时,你只编写一个死循环 void main() { while(1); }BUILD://Program Size: Code=340 RO-data= ...

  10. SpringBoot默认包扫描机制及@ComponentScan指定扫描路径详解

    SpringBoot默认包扫描机制及@ComponentScan指定扫描路径详解 SpringBoot默认包扫描机制 标注了@Component和@Component的衍生注解如@Controller ...

最新文章

  1. linux中ctrl+z和ctrl+c的区别
  2. ElementUI dialog弹框 退出时重置表单数据
  3. C++ 实例化对象 p-printX()
  4. 华农c语言计算高精度加法_考研计算机 | 运算符与表达式算术运算符
  5. c# 无损高质量压缩图片代码
  6. 成为优秀的Java程序员要具备哪些技能?
  7. PAT乙级(1034 有理数四则运算)
  8. [已解决] 日常开发中禁用Tomcat自动重启
  9. 【android studio】解决android studio drawable新建项目时只有一个drawable目录的问题
  10. Illustrator 教程,如何在 Illustrator 中更改图稿颜色?
  11. 免费好用的 Apple 工具(Windows 适用)
  12. Python 编辑器汇总
  13. 今日头条文章评论内容爬取
  14. 冷酷仙境与世界尽头——《葫芦兄弟》人物赏析  作者:马伯庸
  15. -TEST 16 for NOIP 让scar_lyw都绝望的T3(120-300)
  16. 最好用最清爽的json在线编辑器
  17. Win11怎么在右键菜单添加一键关机选项
  18. 在网页中如何设置背景图片
  19. linux kworker进程占用cpu,为什么kworker cpu使用率如此之高?
  20. SpringBoot项目发布与部署,及使用花生壳映射项目

热门文章

  1. 【iCore4 双核心板_uC/OS-II】例程八:消息邮箱
  2. [Angular2 Animation] Control Undefined Angular 2 States with void State
  3. 【转载】关闭ftp中mput的上传确认提示:prompt
  4. mfc配置GDI+有106个错误
  5. Linux下搭建CACTI的时候总结的一些小知识
  6. Android UI学习之CheckBox
  7. DPDK数据流过滤规则例程解析—— 网卡流处理功能窥探
  8. Ubuntu 16.04下安装激活pycharm 2018.3版本
  9. DMA engine的使用步骤 及 DMA一致性
  10. CentOS 6系统FreeSwitch和RTMP服务 安装及演示(四)