本文若有任何纰漏、错误,还请不吝指出!

注:本文提到的Spring容器或者Bean容器,或者Spring Bean容器,都是指同一个事情,那就是代指BeanFactory。关于BeanFactory,后面有机会会再说下。

花絮

几年前接触过SpringBoot,跑过Demo,当时刚入行,连Spring都没搞明白,更别说SpringBoot了,就是觉得,哇塞,好厉害,然后一脸懵逼。

工作中没有用到,又没有去主动学习它。觉得很恐惧,这么厉害的东西,肯定是很深奥,很复杂吧!。

这种心理也造成了一定程度上,对某些事物的望而却步,其实只要向前迈出了步子,一步步慢慢来,才发现,以前的那种恐惧心理是多么的幼稚、胆怯、可笑!

序言

SpringBoot本身并没有多大的花样,所有的知识点其实还都是Spring Framework的。

SpringBoot之前,使用Spring可以说,并不是那么的方便,其实也主要是在搭建一个基于Spring Framework的项目时这个困扰。Spring本身的配置,整合SpringMVC,整合Struts2,整合mybatis,整合Hibernate,整合SpringSecurity等等,如果是Web应用还有个web.xml需要配置。什么都要你去配置一下,第一步就是去找怎么配置,记住这么配置是如何配的,其实并没有切实的意义,毕竟又不是经常需要去搭建一个项目。正因为不常这么配置,不值得记住如何配置,导致每次实际用到时,很麻烦,到处去找如何配置的XML配置文件。

SpringBoot的出现,正是为了解决这个问题,让你可以不去做任何配置的情况下,运行一个Spring应用,或者Web应用。需要做的仅仅是引入SpringBootmaven或者gradle依赖即可。

SpringBoot要做的就是,让你开箱即用!

将使用Spring的成本降到尽可能低,为用户带来了极大的便利。

当然SpringBoot做的也不仅仅只有这些,不过这里仅讨论下它的自动化配置,不讨论其他的。

如果了解Spring@Configuration这个注解的处理过程,会更加容易理解SpringBoot的自动化配置。

如果没有,可以参考这篇解释

穷其林

这第一件事,就是找门,门都找不到,那不是没门吗!

既然想找门,就得从程序的启动入口去找,任何SpringBoot程序都会用到这么两个

@SpringBootApplication
public class Application{
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}

看到这个后,如果好奇其实现,应该会首先查看SpringApplication#run方法,实际调用的是这个重载的静态方法。

// org.springframework.boot.SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
} public ConfigurableApplicationContext run(String... args) {
···省略···
try {
···省略···
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
// 真正启动应用程序上下文前的一些准备动作
// 这里会去将Application.class,注册到org.springframework.context.annotation.AnnotatedBeanDefinitionReader
// 也就是去把Application.class注册成一个BeanDefinition实例
// 不过Application必须要是一个@Component
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文,这个过程中主要就是Bean的实例化和属性赋值绑定
// 如果是Web环境,涉及到Web相关的一些东西,但是本质上还是各种Bean的实例化
// 和Bean之间依赖关系的处理,代理Bean的生成(涉及到AspectJ的Advice处理)等等
refreshContext(context); }
return context;
}

BeanDefinition实例有了,就能去启动上下文,处理Bean容器了,容器启动完成后,整个SpringBoot程序基本启动完成!

等等! 是不是少了什么?

这里就注册了一个BeanDefinition,那么多@Component@Configuration@Service@Controller怎么办?

先留着疑问,且待后面解答!

遇山口

林尽水源,便得一山,山有小口,仿佛若有光。

注意到上面的准备阶段,被注册的Bean必须要被@Component注解,现在Application.class仅有一个注解@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 {
···省略···
}

挨个查看几个注解的定义后,会发现@SpringBootConfiguration@Component所注解,这就解释了为什么被@SpringBootApplication所注解的Application.class类可以被作为一个Bean注册到BeanDefinitionRegistry

除此之外,还有个令人惊喜的名称:@EnableAutoConfiguration,看名字就看出来它是做啥的了。

没错,SpringBoot的所谓自动配置,就是它在起作用。

这里暂时不讨论@ComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
···省略···
}

这个注解又使用了两个注解,分别是@AutoConfigurationPackage@Import

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage { }

可以发现,这两个注解最终都指向了同一个注解@Import

@ImportAnnotation时代的<import/>,作用是向BeanDefinitionRegistry注册Bean的。

所以@EnableAutoConfiguration这个注解一共注册了两个Bean,分别是:AutoConfigurationPackages.Registrar.classAutoConfigurationImportSelector.class

先说说AutoConfigurationPackages.Registrar的用处

这个类就干一个事,注册一个Bean,这个Bean就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages,它有一个参数,这个参数是使用了@AutoConfigurationPackage这个注解的类所在的包路径。有了这个包路径后,就会扫描这个包下的所有class文件,然后将需要注册到Bean容器的类,给注册进去。

具体可以参见这里 org.springframework.boot.autoconfigure.AutoConfigurationPackages#register

这里就解释了为什么有时候主配置类放的位置不对,导致有些类没被Spring容器纳入管理

桃花源

经历了一番折腾,就要进入桃花源了

AutoConfigurationImportSelector就是那最后一层窗户纸

// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 为了加载spring-boot-autoconfiguration包下的配置文件META-INF/spring-autoconfigure-metadata.properties
// 这里配置的主要是一些SpringBoot启动时用到的一些@ConditionOnClass的配置
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 这里的AutoConfigurationEntry,就包含了所有的导入的需要被实例化的Bean
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
// 返回这些被导入Bean的类全限定名数组
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
} protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
··· 省略 ···
// 获取所有的需要导入的Bean,这些被导入的Bean就是各个组件需要自动化配置的启动点
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
··· 省略 ···
return new AutoConfigurationEntry(configurations, exclusions);
} protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 使用SpringFactoriesLoader#loadFactoryNames方法,从所有的包及classpath目录下,
// 查找META-INF/spring.factories文件,且名称为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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.");
return configurations;
}
}

最终这些配置在META-INF/spring.factories中需要自动配置的类,就会被注册到Spring Bean容器中,然后被实例化,调用初始化方法等!

这些做自动配置的类,基本都会通过实现各种Aware接口,获取到Spring Framework中的BeanFactoryApplicationContext等等所有的一些框架内的组件,用于后面使用。

之后完成自己框架的一些初始化工作,主要就是将原先和Spring整合时,需要手动配置的那些,在这里通过编程式的方式,给做了。

这样,就完成了所谓的自动化配置,全程不需要我们的任何参与。

PS: 这个仅仅是做了一个通用的配置,让用户可以在不做任何配置的情况下能直接使用。但是一些个性化的配置,还是需要通过配置文件的方式,写入配置。对于这部分配置的处理,SpringBoot也都给揽下了

总结

整体看下来,SpringBoot干的这些,更像是一个体力活,将于Spring集成的那么多三方库的配置,使用代码全部实现了一遍,其使用的核心功能,依然是Spring Framework的那些东西。

但是这个体力活是为使用者省下的,也让Spring Framework更加的具有活力了。

同时微服务的兴起,也是Spring为了顺势而必须作出的一个改变,也可以说为Spring在微服务领域立下了汗马功劳!

让SpringBoot自动化配置不再神秘的更多相关文章

  1. SpringBoot自动化配置之一:SpringBoot内部的一些自动化配置原理

    springboot用来简化Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发. 比如不使用springboot而使用SpringMVC作为web框架进行开发 ...

  2. SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition

    自动配置绝对算得上是Spring Boot的最大亮点,完美的展示了CoC约定优于配置: Spring Boot能自动配置Spring各种子项目(Spring MVC, Spring Security, ...

  3. SpringBoot自动化配置的注解开关原理

    我们以一个最简单的例子来完成这个需求:定义一个注解EnableContentService,使用了这个注解的程序会自动注入ContentService这个bean. @Retention(Retent ...

  4. SpringBoot自动化配置之四:SpringBoot 之Starter(自动配置)、Command-line runners

    Spring Boot Starter是在SpringBoot组件中被提出来的一种概念,stackoverflow上面已经有人概括了这个starter是什么东西,想看完整的回答戳这里 Starter ...

  5. SpringBoot自动化配置之三:深入SpringBoot:自定义EnableAutoConfiguration

    前言 上面几篇文章介绍了SpringFramework的一些原理,这里开始介绍一下SpringBoot,并通过自定义一些功能来介绍SpringBoot的原理.SpringBoot在SpringFram ...

  6. SpringBoot自动化配置之四:@Conditional注解详解

    前言 之前在分析spring boot 源码时导出可见@ConditionalOnBean 之类的注解,那么它到底是如何使用的以及其工作流程如何,我们这里就围绕以下几点来分析: @Conditiona ...

  7. springboot笔记-1.自动化配置的关键

    最近发现看过的东西容易忘,但是写一遍之后印象倒是会深刻的多. 总所周知springboot极大的简化了java开发繁琐性,而其最大的优势应该就是自动化配置了.比如要使用redis,我们直接引入相关的包 ...

  8. 徒手撸一个 Spring Boot 中的 Starter ,解密自动化配置黑魔法!

    我们使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中.Starter 为我们带来了众多的自动化配置,有了这些自动化配置,我们可以不费吹灰之力就能搭建一个生产级开发环境,有的小 ...

  9. 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二) 上一篇分析了SpringBoo ...

随机推荐

  1. Codeforces Round #635 (Div. 2) 题解

    渭城朝雨浥轻尘,客舍青青柳色新. 劝君更尽一杯酒,西出阳关无故人.--王维 A. Ichihime and Triangle 网址:https://codeforces.com/contest/133 ...

  2. Mozilla开始推送Firefox Preview 5.0版 支持画中画特性

    Mozilla 发布了 5.0 版本的 Firefox Preview 浏览器,根据 GitHub 上的发布说明,这次更新带来了一系列新的改进.其中包含对五个新的附加组件的支持,引入了对 Progre ...

  3. 【集群实战】inotify

    1. inotify简介 Inotify是一种强大的,细粒度的,异步的文件系统事件监控机制(软件). linux内核从2.6.13起,加入了Inotify支持,通过Inotify可以监控文件系统中添加 ...

  4. 面向对象第四单元(UML)总结

    OO第四单元 一.总结本单元两次作业的架构设计 第一次作业 架构 第一次作业只有类图,所以全部的UmlElement都可以放在MyUmlInteraction中进行存储.计算和查找.对于类图来说,可以 ...

  5. 图论-网络流-最大流--POJ1273Drainage Ditches(Dinic)

    Drainage Ditches Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 91585   Accepted: 3549 ...

  6. python(安装)

    1.下载安装包 https://www.python.org/downloads/ 2.安装 默认安装路径:C:\python3(建议自定义安装路径) 3.配置环境变量 [右键计算机]-->[属 ...

  7. Jmeter系列(9)- jmeter插件入门篇

    如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html 前言 jmeter4.0以上,如现在最 ...

  8. C# 基础知识系列- 17 实战篇 编写一个小工具(1)

    0. 前言 这是对C# 基础系列的一个总结,现在我们利用之前学到的知识做一个小小的工具来给我们使用. 如果有看过IO篇的小伙伴,应该有印象.当时我提过一个场景描述,我们在平时使用系统的时候,经常会为了 ...

  9. SpringBoot:Demo

    目录 Demo 准备工作 登录+拦截器 Restful CRUD Restful架构 查询所有员工 添加员工 员工修改功能 HiddenHttpMethodFilter 删除员工 定制错误页面 注销功 ...

  10. C++中的隐式转换和explicit

    隐式转换 c++中的数据类型转换分为隐式转换和显示转换: 显示转换即使用static_cast等方法进行转换,相关内容请参考 <C++数据类型转换>: 隐式转换则是编译器完成的,如,boo ...