让SpringBoot自动化配置不再神秘
本文若有任何纰漏、错误,还请不吝指出!
注:本文提到的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应用。需要做的仅仅是引入SpringBoot的maven或者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
@Import是Annotation时代的<import/>,作用是向BeanDefinitionRegistry注册Bean的。
所以@EnableAutoConfiguration这个注解一共注册了两个Bean,分别是:AutoConfigurationPackages.Registrar.class和AutoConfigurationImportSelector.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中的BeanFactory,ApplicationContext等等所有的一些框架内的组件,用于后面使用。
之后完成自己框架的一些初始化工作,主要就是将原先和Spring整合时,需要手动配置的那些,在这里通过编程式的方式,给做了。
这样,就完成了所谓的自动化配置,全程不需要我们的任何参与。
PS: 这个仅仅是做了一个通用的配置,让用户可以在不做任何配置的情况下能直接使用。但是一些个性化的配置,还是需要通过配置文件的方式,写入配置。对于这部分配置的处理,
SpringBoot也都给揽下了
总结
整体看下来,SpringBoot干的这些,更像是一个体力活,将于Spring集成的那么多三方库的配置,使用代码全部实现了一遍,其使用的核心功能,依然是Spring Framework的那些东西。
但是这个体力活是为使用者省下的,也让Spring Framework更加的具有活力了。
同时微服务的兴起,也是Spring为了顺势而必须作出的一个改变,也可以说为Spring在微服务领域立下了汗马功劳!
让SpringBoot自动化配置不再神秘的更多相关文章
- SpringBoot自动化配置之一:SpringBoot内部的一些自动化配置原理
springboot用来简化Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发. 比如不使用springboot而使用SpringMVC作为web框架进行开发 ...
- SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition
自动配置绝对算得上是Spring Boot的最大亮点,完美的展示了CoC约定优于配置: Spring Boot能自动配置Spring各种子项目(Spring MVC, Spring Security, ...
- SpringBoot自动化配置的注解开关原理
我们以一个最简单的例子来完成这个需求:定义一个注解EnableContentService,使用了这个注解的程序会自动注入ContentService这个bean. @Retention(Retent ...
- SpringBoot自动化配置之四:SpringBoot 之Starter(自动配置)、Command-line runners
Spring Boot Starter是在SpringBoot组件中被提出来的一种概念,stackoverflow上面已经有人概括了这个starter是什么东西,想看完整的回答戳这里 Starter ...
- SpringBoot自动化配置之三:深入SpringBoot:自定义EnableAutoConfiguration
前言 上面几篇文章介绍了SpringFramework的一些原理,这里开始介绍一下SpringBoot,并通过自定义一些功能来介绍SpringBoot的原理.SpringBoot在SpringFram ...
- SpringBoot自动化配置之四:@Conditional注解详解
前言 之前在分析spring boot 源码时导出可见@ConditionalOnBean 之类的注解,那么它到底是如何使用的以及其工作流程如何,我们这里就围绕以下几点来分析: @Conditiona ...
- springboot笔记-1.自动化配置的关键
最近发现看过的东西容易忘,但是写一遍之后印象倒是会深刻的多. 总所周知springboot极大的简化了java开发繁琐性,而其最大的优势应该就是自动化配置了.比如要使用redis,我们直接引入相关的包 ...
- 徒手撸一个 Spring Boot 中的 Starter ,解密自动化配置黑魔法!
我们使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中.Starter 为我们带来了众多的自动化配置,有了这些自动化配置,我们可以不费吹灰之力就能搭建一个生产级开发环境,有的小 ...
- 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二) 上一篇分析了SpringBoo ...
随机推荐
- Blazor WebAssembly 3.2.0 正式起飞,blazor 适合你吗?
最近blazor更新很快,今天在官方博客上发布了Blazor WebAssembly 3.2.0 RC:https://devblogs.microsoft.com/aspnet/blazor-web ...
- 【Linux常见命令】uname命令
uname命令用于显示系统信息. uname可显示电脑以及操作系统的相关信息. 语法 uname [-amnrsv][--help][--version] 参数说明: -a或--all 显示全部的信息 ...
- 2.Python是什么?使用Python的好处是什么?
Python是什么?使用Python的好处是什么? 答: Python is a programming language with objects, modules, threads, except ...
- 交换机上的MAC地址表
拓扑图: 1.首先在R1上的配置: R1(config)#int R1(config)#interface g R1(config)#interface gigabitEthernet 0/0 R1( ...
- Android APP 性能测试之 GT 工具
一.介绍: GT(随身调)是 APP 的随身调测平台,它是直接运行在手机上的"集成调测环境"(IDTE, Integrated Debug Environment).利用 GT,仅 ...
- 题解 CF545A 【Toy Cars】
题目传送门 太弱了,只能写写A题的题解 题意 给你一个 $n·n$ 的矩阵,翻车分三种情况: 如果 $a_i,_j=1$ ,记录第 $i$ 辆车 如果 $a_i,_j=2$ ,记录第 $j$ 辆车 如 ...
- 请不要浪费你的生命,一文多发推广就用它(OpenWrite)
你我的共同困惑 你是否跟我一样?刚开始尝试写作,自己没有名气,有以下几个困惑: 想要推广自己,想在多个平台发文? 多平台发文之后,想要看各个平台的流量如何? 有了流量,想要引流用户到自己的网站? 网站 ...
- 数位dp H - F(x) HDU - 4734
http://acm.hdu.edu.cn/showproblem.php?pid=4734 一般数位dp表示的是数的性质,这个题目也是一样,但是我们求出来的是一个函数的值,怎么把这个值转化成一类数, ...
- 龙贝格算法 MATLAB实现
龙贝格算法主要是不断递推和加速,直到满足精度要求 递推: 加速: 得到T表: MATLAB代码: function I = Romberg(f, a, b, epsilon) I = 0; h = b ...
- C# 中 枚举Enum 一些转换的方法整理
工作中 经常遇到枚举 的一些转换 特别是获取枚举备注等 特地整理下 方法以后使用 public void TestMethod1() { TestEnumOne colorEnum = TestE ...