前言

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

springboot帮我们做了什么

通常搭建一个基于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的起步依赖与自动配置这两个核心原理。

起步依赖

在springboot中我们只需要引入下面简单的几步就可以完成一个ssm后台项目的初始搭建。

1、引入jar

org.springframework.boot

spring-boot-starter-parent

2.0.4.RELEASE

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.2

org.springframework.boot

spring-boot-starter-web

mysql

mysql-connector-java

runtime

com.alibaba

druid

1.0.31

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 中,并没有任何源码,只有一个pom文件,它的作用就是帮我们引入了相关jar包。

2、配置数据源

spring:

datasource:

url: jdbc:mysql://127.0.0.1:3306/mybatis_test

username: root

password: root

driver-class-name: com.mysql.jdbc.Driver

type: com.alibaba.druid.pool.DruidDataSource

dbcp2:

min-idle: 5

initial-size: 5

max-total: 5

max-wait-millis: 200

stater机制帮我们完成了项目起步所需要的的相关jar包。那问题又来了,传统的spring应用中不是要在application.xml中配置很多bean的吗,比如dataSource的配置,transactionManager的配置 ... springboot是如何帮我们完成这些bean的配置的?下面我们来分析这个过程

自动配置

基于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容器中。

传统的基于xml的bean配置方法如下:

相当于用基于java代码的配置方式:

@Configurationpublic classConf {

@BeanpublicCar car() {

Car car= newCar();

car.setWheel(wheel());returncar;

}

@BeanpublicWheel wheel() {return newWheel();

}

}

所以上面的MybatisAutoConfiguration这个类,自动帮我们生成了SqlSessionFactory这些Mybatis的重要实例并交给spring容器管理,从而完成bean的自动注册。

自动配置条件依赖

从MybatisAutoConfiguration这个类中使用的注解可以看出,要完成自动配置是有依赖条件的。

@Configuration

@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})

@ConditionalOnBean({DataSource.class})

@EnableConfigurationProperties({MybatisProperties.class})

@AutoConfigureAfter({DataSourceAutoConfiguration.class})public classMybatisAutoConfiguration {//....

}

这些是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后自动配置才生效。

bean参数获取

到此我们已经知道了bean的配置过程,但是还没有看到springboot是如何读取yml或者properites配置文件的的属性来创建数据源的?

在DataSourceAutoConfiguration类里面,我们注意到使用了EnableConfigurationProperties这个注解。

@Configuration

@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})

@EnableConfigurationProperties({DataSourceProperties.class})

@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})public classDataSourceAutoConfiguration {

...

}

DataSourceProperties中封装了数据源的各个属性,且使用了注解ConfigurationProperties指定了配置文件的前缀。

@ConfigurationProperties(

prefix= "spring.datasource")public class DataSourceProperties implementsBeanClassLoaderAware, InitializingBean {privateClassLoader classLoader;privateString name;private booleangenerateUniqueName;private Class extends DataSource>type;privateString driverClassName;privateString url;privateString username;privateString password;privateString jndiName;

...

}

@EnableConfigurationProperties与@ConfigurationProperties这两个注解有什么用呢?我们先看一个例子:

@Component

@ConfigurationProperties(prefix="spring.datasource")public classPropertiesBean {privateString url;privateString username;privateString password;//省略getter、setter...

@OverridepublicString toString() {return "PropertiesBean{" +

"url='" + url + '\'' +

", username='" + username + '\'' +

", password='" + password + '\'' +

'}';

}

}

@SpringBootApplication

@MapperScan("com.itpsc.mapper*")

@EnableConfigurationPropertiespublic classSpringbootMybatisDemoApplication {public static voidmain(String[] args) {//SpringApplication.run(SpringbootMybatisDemoApplication.class, args);

ConfigurableApplicationContext context = SpringApplication.run(SpringbootMybatisDemoApplication.class, args);//获取yml配置转换后的bean

System.out.println("----------------------"+context.getBean(PropertiesBean.class));

context.close();

}

}

运行结果:

从运行结果可以看出@ConfigurationProperties与@EnableConfigurationPropertie的作用就是:

@ConfigurationProperties注解的作用是把yml或者properties配置文件转化为bean。

@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在spring容器中是获取不到yml或者properties配置文件转化的bean的。

通过这种方式,把yml或者properties配置参数转化为bean,这些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 @interfaceSpringBootApplication {

...

}

实际上重要的只有三个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 @interfaceEnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY= "spring.boot.enableautoconfiguration";

Class>[] exclude() default{};

String[] excludeName()default{};

}

如上源码,@EnableAutoConfiguration注解引入了@AutoConfigurationPackage和@Import这两个注解。@AutoConfigurationPackage的作用就是自动配置的包,@Import导入需要自动配置的组件。

进入@AutoConfigurationPackage,发现也是引入了@Import注解

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@Import({Registrar.class})public @interfaceAutoConfigurationPackage {

}

static class Registrar implementsImportBeanDefinitionRegistrar, DeterminableImports {

Registrar() {

}public voidregisterBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

AutoConfigurationPackages.register(registry,new String[]{(newAutoConfigurationPackages.PackageImport(metadata)).getPackageName()});

}public SetdetermineImports(AnnotationMetadata metadata) {return Collections.singleton(newAutoConfigurationPackages.PackageImport(metadata));

}

}

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

new AutoConfigurationPackages.PackageImport(metadata)

这两句代码的作用就是加载启动类所在的包下的主类与子类的所有组件注册到spring容器,这就是前文所说的springboot默认扫描启动类所在的包下的主类与子类的所有组件。

那问题又来了,要搜集并注册到spring容器的那些beans来自哪里?

进入AutoConfigurationImportSelector类,

public class AutoConfigurationImportSelector implementsDeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {private static final String[] NO_IMPORTS = new String[0];

...publicString[] selectImports(AnnotationMetadata annotationMetadata) {if(!this.isEnabled(annotationMetadata)) {returnNO_IMPORTS;

}else{

AutoConfigurationMetadata autoConfigurationMetadata= AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

AnnotationAttributes attributes= this.getAttributes(annotationMetadata);

List configurations= this.getCandidateConfigurations(annotationMetadata, attributes);

configurations= this.removeDuplicates(configurations);

Set exclusions= this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations= this.filter(configurations, autoConfigurationMetadata);this.fireAutoConfigurationImportEvents(configurations, exclusions);returnStringUtils.toStringArray(configurations);

}

}

...protected ListgetCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

List configurations= SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");returnconfigurations;

}

...

}

SpringFactoriesLoader.loadFactoryNames方法调用loadSpringFactories方法从所有的jar包中读取META-INF/spring.factories文件信息。

private static Map>loadSpringFactories(@Nullable ClassLoader classLoader) {

MultiValueMap result=(MultiValueMap)cache.get(classLoader);if(result != null) {returnresult;

}else{try{

Enumeration ex= classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");

LinkedMultiValueMap result1= newLinkedMultiValueMap();while(ex.hasMoreElements()) {

URL url=(URL)ex.nextElement();

UrlResource resource= newUrlResource(url);

Properties properties=PropertiesLoaderUtils.loadProperties(resource);

Iterator var6=properties.entrySet().iterator();while(var6.hasNext()) {

Entry entry=(Entry)var6.next();

List factoryClassNames=Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));

result1.addAll((String)entry.getKey(), factoryClassNames);

}

}

cache.put(classLoader, result1);returnresult1;

}catch(IOException var9) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);

}

}

}

下面是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等条件判断是否加载。

private static T instantiateFactory(String instanceClassName, ClassfactoryClass, ClassLoader classLoader) {try{

Class ex=ClassUtils.forName(instanceClassName, classLoader);if(!factoryClass.isAssignableFrom(ex)) {throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");

}else{return ReflectionUtils.accessibleConstructor(ex, new Class[0]).newInstance(new Object[0]);

}

}catch(Throwable var4) {throw new IllegalArgumentException("Unable to instantiate factory class: " +factoryClass.getName(), var4);

}

}

如上代码段,通过反射机制将spring.factories中@Configuration类实例化为对应的java实列。到此我们已经知道怎么发现要自动配置的bean了,最后一步就是怎么样将这些bean加载到spring容器。

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方法。

public class AutoConfigurationImportSelector implementsDeferredImportSelector{

...publicString[] selectImports(AnnotationMetadata annotationMetadata) {if(!this.isEnabled(annotationMetadata)) {returnNO_IMPORTS;

}else{

AutoConfigurationMetadata autoConfigurationMetadata= AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

AnnotationAttributes attributes= this.getAttributes(annotationMetadata);

List configurations= this.getCandidateConfigurations(annotationMetadata, attributes);

configurations= this.removeDuplicates(configurations);

Set exclusions= this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations= this.filter(configurations, autoConfigurationMetadata);this.fireAutoConfigurationImportEvents(configurations, exclusions);returnStringUtils.toStringArray(configurations);

}

}

...

}

public interface DeferredImportSelector extendsImportSelector {

@Nullabledefault Class extends DeferredImportSelector.Group>getImportGroup() {return null;

}public interfaceGroup {...}

}

public interfaceImportSelector {

String[] selectImports(AnnotationMetadata var1);

}

我们先通过一个简单例子看看@Import注解是如何将bean导入到spring容器的。

1、新建一个bean

public classUser {privateLong id;privateString name;privateString password;privateString phone;

...

}

2、创建一个ItpscSelector类继承ImportSelector接口并实现selectImports方法

public class ItpscSelector implementsImportSelector {publicString[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.itpsc.entity.User"};

}

}

3、创建ImportConfig类,使用@Configuration、@Import(ItpscSelector.class)注解。

@Configuration

@Import(ItpscSelector.class)public classImportConfig {

}

4、从容器获取bean

@RunWith(SpringRunner.class)

@SpringBootTestpublic classImportSelectorTests {

@Testpublic voidtestSelectImport() {

ApplicationContext ctx= new AnnotationConfigApplicationContext(ImportConfig.class);

String[] beanDefinitionNames=ctx.getBeanDefinitionNames();for(String name : beanDefinitionNames) {

System.out.println(name);

}

}

}

运行结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.context.annotation.internalRequiredAnnotationProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.context.event.internalEventListenerFactory

importConfig

com.itpsc.entity.User

很直观,selectImports方法返回一组bean,@EnableAutoConfiguration注解借助@Import注解将这组bean注入到spring容器中,springboot正式通过这种机制来完成bean的注入的。

总结

我们可以将自动配置的关键几步以及相应的注解总结如下:

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

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

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

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

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

  1. java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...

    Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下我们先从启动类说起有一个Launcher类 ...

  2. java链表实现_链表的原理及java实现

    一:单向链表基本介绍 链表是一种数据结构,和数组同级.比如,Java中我们使用的ArrayList,其实现原理是数组.而LinkedList的实现原理就是链表了.链表在进行循环遍历时效率不高,但是插入 ...

  3. java 事件分发机制_用两段代码带你看懂事件分发机制

    先来看一段精简版的源码 View.java public class View { private View.OnClickListener mOnClickListener; private OnL ...

  4. mysql运维机制_《MySQL运维内参》节选 | InnoDB日志管理机制(一)

    引 子 InnoDB 存储引擎是支持事务ACID特性的,它是以二十多年前IBM的一篇著名文章<ARIES:A Transaction Recovery Method Supporting Fin ...

  5. smtp java 抓包_[Wireshark]_003_电子邮件抓包分析

    电子邮件是我们的生活工作中经常使用的一种服务,用来联系世界各地的朋友,客户.下面我们就用Wireshark对电子邮件进行抓包. 准备工作: 邮件客户端一款(Outlook,Foxmail,KooMai ...

  6. Java酒瓶瓶盖_酒瓶与瓶盖配合常见问题分析

    酒瓶与瓶盖配合问题,直接影响酒类产品功能性,是产生漏液.拨起.打转等重大质量问题的根源.正确的分析认识酒瓶与瓶盖配合问题将有效地帮助我们识别并控制产品风险.本文将着重从常见几大类酒瓶及瓶盖配合问题进行 ...

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

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

  8. Spring5源码 - 13 Spring事件监听机制_@EventListener源码解析

    文章目录 Pre 概览 开天辟地的时候初始化的处理器 @EventListener EventListenerMethodProcessor afterSingletonsInstantiated 小 ...

  9. java开发技术心得_Java开发核心技术面试心得分析

    Java的数据结构有哪些?Map与Set的本质区别是什么? 分析:Java常见的数据结构有Collection和Map,其中Collection接口下包括List和Set接口,其下又有多个实现类如Li ...

最新文章

  1. 改进的SVN的Commit权限控制
  2. 解决yarn全局安装模块后但仍提示无法找到命令的问题
  3. php无符号整数转有符号整数,PHP中把有符号整型转换为无符号整型方法_PHP教程...
  4. 论文浅尝 | 远程监督关系抽取的生成式对抗训练
  5. 第一次来到博客园.....
  6. 能留住女友的,都是王者
  7. Linux6新建分区,centos6中添加一块新的硬盘并分区的方法介绍
  8. vuex的基础小案例(黑马教程)
  9. 计算机组成原理 全加器实验
  10. 南通全国计算机等级考试,南通大学2017年3月全国计算机等级考试报名通知
  11. table总结insertRow、deleteRow 学习
  12. 鸡得呼吸道病会易发啥病 鸡喂什么药预防打喷嚏
  13. 如何躲避红蜘蛛的控制
  14. Kotlin学习笔记(二)——函数操作符内置函数
  15. python sort是什么排序_python的sort()排序方法
  16. 委托实现信用卡用户定时还款功能
  17. 用HTML5图形拼成的画房子,canvas画小房子(补充:简陋的小房子)
  18. python gil全局锁_什么是Python全局解释器锁(GIL)?
  19. 盛世昊通2022全新布局,九大生态板块赋能产业数字化
  20. Adobe AIR迷你教程 -- 使用自定义窗口以及对窗口的移动,缩放,关闭操作

热门文章

  1. java 排序算法总结,Java排序算法总结之归并排序
  2. java awt point_100分 解决java import java.awt.Point;import java.awt.Rectangle;
  3. 使用FastReport 3.0及以上版本创建动态报表的几个技巧(转)
  4. 【机器学习】Weighted LSSVM原理与Python实现:LSSVM的稀疏化改进
  5. 深度学习 --- 受限玻尔兹曼机RBM(直接采样、接受-拒绝采样、重要性采样详解)
  6. python语言的官方网站-web2py
  7. 如何写好标题,才能轻松上简书首页?
  8. 通过用户电脑ip获取用户当前所在城市以及天气
  9. php如何定义和使用常量,PHP中如何定义和使用常量_PHP教程
  10. stm32呼吸灯程序_STM32裸机开发基础篇02点亮LED