springboot基础
https://zhuanlan.zhihu.com/p/362984115
Spring Boot基础
概念:
约定优于配置,简单来说就是你所期待的配置与约定的配置一致,那么就可以不做任何配置,约定不符合期待时才需要对约定进行替换配置。
特征:
\1. SpringBoot Starter:他将常用的依赖分组进行了整合,将其合并到一个依赖中,这样就可以一次性添加到项目的Maven或Gradle构建中。
2,使编码变得简单,SpringBoot采用 JavaConfig的方式对Spring进行配置,并且提供了大量的注解,极大的提高了工作效率,比如@Configuration和@bean注解结合,基于@Configuration完成类扫描,基于@bean注解把返回值注入IOC容器。
3.自动配置:SpringBoot的自动配置特性利用了Spring对条件化配置的支持,合理地推测应用所需的bean并自动化配置他们。
4.使部署变得简单,SpringBoot内置了三种Servlet容器,Tomcat,Jetty,undertow.我们只需要一个Java的运行环境就可以跑SpringBoot的项目了,SpringBoot的项目可以打成一个jar包。
Spring Boot创建
Spring Boot项目结构图:
Spring Boot热部署
通过引入spring-bootdevtools插件,可以实现不重启服务器情况下,对项目进行即时编译。引入热部署插件的步骤如下:
\1. 在pom.xml添加热部署依赖
\2. IDEA热部署工具设置
在项目任意页面中使用组合快捷键“Ctrl+Shift+Alt+/”打开Maintenance选项框,选中并打开Registry页面,列表中找到“compiler.automake.allow.when.app.running”,将该选项后的Value值勾选,用于指定IDEA工具在程序运行过程中自动编译,最后单击【Close】按钮完成设置。
排除资源:
默认情况下,改变资源 /META-INF/maven , /META-INF/resources , /resources , /static , /public ,或 /templates 不触发重新启动,但确会触发现场重装。如果要自定义这些排除项,则可以使用该spring.devtools.restart.exclude 属性。例如,仅排除 /static , /public 在application.properties设置以下属性。
spring.devtools.restart.exclude=static/,public/,config/**
全局配置文件优先级
优先级:以下图顺序号代表配置文件的优先级,并且相同配置文件按顺序加载可以实现互补,但是不会被覆盖。
注意:Spring Boot 有application.properties 和 application.yaml 两种配置文件的方式,yaml是一种JSON超文本格式文件,如果是2.4.0之前版本,优先级properties>yaml;但是如果是2.4.0的版本,优先级yaml>properties。
自定义application.properties 配置文件注入IOC容器
填加相应依赖配置可以实现在自定义配置properties配置提示
@ConfigurationProperties(prefix = “person”)注解的作用是将配置文件中以person开头的属性值通过setXX()方法注入到实体类对应属性中。
@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中,只有这样才能被@ConfigurationProperties注解进行赋值。
application.yaml配置文件
YAML文件格式是Spring Boot支持的一种JSON超集文件格式,以数据为中心,比properties、xml等更适合做配置文件.
1.yml和xml相比,少了一些结构化的代码,使数据更直接,一目了然
2.相比properties文件更简洁
3.yaml文件的扩展名可以使用.yml或者.yaml。
4.application.yml文件使用 “key:(空格)value”格式配置属性,使用缩进控制层级关系。
属性注入
如果配置属性是Spring Boot已有属性,例如服务端口server.port,那么Spring Boot内部会自动扫描并读取这些配置文件中的属性值并覆盖默认属性。
@Configuration:声明一个类作为配置类。
@Bean:声明在方法上,将方法的返回值加入Bean容器。
@Value:属性注入
@ConfigurationProperties(prefix = “jdbc”):批量属性注入。
@PropertySource(“classpath:/jdbc.properties”)指定外部属性文件,在类上添加。
第三方配置:
@ConfigurationProperties 用于注释类之外,您还可以在公共 @Bean 方法上使用它。将属性绑定到控件之外的第三方组件
松散绑定:
Spring Boot使用一些宽松的规则将环境属性绑定到@ConfigurationProperties bean,因此环境属性名和bean属性名之间不需要完全匹配,比如在application.properties文件里定义一个first-name=tom,在对应bean类中使用firstName也能获取到对应的值,这就是松散绑定。
Spring Boot日志框架
SLF4J 的使用:
注意:由于每一个日志的实现框架都有自己的配置文件,所以在使用 SLF4j 之后,配置文件还是要使用实现日志框架的配置文件。
统一日志框架使用:
实现步骤
\1. 排除系统中的其他日志框架。
\2. 使用中间包替换要替换的日志框架。
\3. 导入我们选择的 SLF4J 实现。
从图中我们得到一种统一日志框架使用的方式,可以使用一种要替换的日志框架类完全一样的 jar 进行替换,这样不至于原来的第三方 jar 报错,而这个替换的 jar 其实使用了 SLF4J API. 这样项目中的日志就都可以通过 SLF4J API 结合自己选择的框架进行日志输出。
自定义日志输出:
可以在配置文件编写日志相关配置实现自定义日志输出。
替换日志框架:
spring-boot-starter-parent
Spring Boot项目的统一版本父项目依赖管理。
在底层源文件定义了工程的Java版本;工程代码的编译源文件编码格式;工程编译后的文件编码格式;Maven打包编译的版本。
接着在build节点做了资源过滤
接着从spring-boot-starter-parent找到他父依赖 spring-boot-dependencies,从里面就可以发现里面定义了各种版本声明,通过这里声明可以让部分依赖不需要写版本号,一些没有引入的第三方jar包仍然需要自己声明版本号。
spring-boot-starter-web
Spring Boot项目的所依赖jar包进行打包起步依赖管理
在spring-boot-starter-web的父依赖spring-boot-starters包中,可以发现在他的dependencies标签有着各种依赖包引入,点进去就是具体包的导入配置管理。
注意:Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器。为了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如druid-spring-boot-starter等。我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。
自动配置@SpringBootApplication
他是一个组合注解,核心代码:
自动配置@SpringBootConfiguration
通过上面可以发现我们的核心启动类注解源码中含此注解,这个注解标注在某个类上,表示这是一个 Spring Boot的配置类。他的核心代码中,内部有一个核心注解@Configuration来表明当前类是配置类,并且可以被组件扫描器扫到,所以@SpringBootConfiguration与@Configuration具有相同作用,只是前者又做了一次封装。
自动配置@ EnableAutoConfiguration
Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean ,并加载到 IOC 容器。而这个注解就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器,他的核心源码如下:
通过@AutoConfigurationPackage注解进入类别,发现他通过import引入了一个AutoConfigurationPackages.Registrar.class,在Registrar.class中就重写了一个registerBeanDefinitions方法,在方法内部调用了一个register方法来实现将注解标注的元信息传入,获取到相应的包名。通俗点就是注册bean,然后根据 @AutoConfigurationPackage找到需要注册bean的类路径,这个路径就被自动保存了下来,后面需要使用bean,就直接获取使用,比如Spring Boot整合JPA可以完成一些注解扫描。
自动配置@Import(AutoConfigurationImportSelector.class)
该注解是Spring boot的底层注解,AutoConfigurationImportSelector类可以帮助 Springboot 应用将所有符合条件的 @Configuration配置都加载到当前Spring Boot创建并使用的IOC容器( ApplicationContext )中。
该注解实现了实现了 DeferredImportSelector 接口和各种Aware 接口,在源码中截图中,通过四个接口回调,把值返回给了
定义的四个成员变量。
1.自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法。
2.自动配置的相关的绝大部分逻辑全在第一处也就是this.group.proces方法里,主要做的事就是在方法中,传入的 AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,而第二处的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入。
3.进入getAutoConfigurationEntry方法,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。AutoConfigurationEntry 方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
4.进入getCandidateConfigurations方法,里面有一个重要方法 loadFactoryNames ,这个方法是让 SpringFactoryLoader 去加载一些组件的名字。
5.进入 loadFactoryNames方法,获取到出入的键factoryClassName。
6.进入loadSpringFactories方法,加载配置文件,而这个配置文件就是spring.factories文件
由此我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。spring.factories里面保存着springboot的默认提供的自动配置类。
AutoConfigurationEntry 方法主要做的事情:
【1】从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类),获取的自动配置类如图所示。
【2】若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类排除掉;
【3】排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些不符合条件的自动配置类;
【4】经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
【5】 最后再将符合条件的自动配置类返回。
AutoConfigurationImportSelector 的 filter 方法
主要做的事情就是调用AutoConfigurationImportFilter 接口的 match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass , @ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。其实就是排除自动配置类,因为全部加载出来的类太多,不需要全部都反射成对象,而这个方法就是通过注解进行该自动配置类是否有相应匹配的类的判断,存在即加入,不存在即过滤。
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
@ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
有选择的导入自动配置类
在第一步最后的一个方法this.group.selectImports主要是针对经过排除掉 exclude 的和被AutoConfigurationImportFilter 接口过滤后的满足条件的自动配置类再进一步排除 exclude 的自动配置类,然后再排序,至此实现了自动配置。
自动配置HttpEncodingAutoConfiguration实例
\1. SpringBoot 启动会加载大量的自动配置类
2.我们看我们需要实现的功能有没有 SpringBoot 默认写好的自动配置类
3.我们再来看这个自动配置类中到底配置了哪些组件;(只要我们有我们要用的组件,我们就不需要再来配置了)
4.给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性,我们就可以在配置文件中指定这些属性的值。
xxxAutoConfiguration :自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁琐的配置。
xxxProperties : 封装了对应自动配置类的默认属性值,如果我们需要自定义属性值,只需要根据xxxProperties 寻找相关属性在配置文件设值即可。
@ComponentScan注解
主要作用是从定义的扫描路径中,找出标识了需要装配的类自动装配到spring 的bean容器中。默认扫描路径是为@ComponentScan注解的类所在的包为基本的扫描路径(也就是标注了@SpringBootApplication注解的项目启动类所在的路径),所以这里就解释了之前spring boot为什么只能扫描自己所在类的包及其子包。
常用属性:
basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径。
basePackageClasses:指定具体扫描的类。
includeFilters:指定满足Filter条件的类。
excludeFilters:指定排除Filter条件的类。
SpringApplication() 构造方法
第二步getSpringFactoriesInstances方法解析
主要就是 loadFactoryNames()方法,这个方法是spring-core中提供的从META-INF/spring.factories中获取指定的类(key)的同一入口方法,获取的是key为 org.springframework.context.ApplicationContextInitializer 的类,是Spring框架的类, 这个类的主要目的就是在ConfigurableApplicationContext 调用refresh()方法之前,回调这个类的initialize方法。通过 ConfigurableApplicationContext 的实例获取容器的环境Environment,从而实现对配置文件的修改完善等工作。
源码剖析Run方法整体流程
重要六步:
第一步:获取并启动监听器
第二步:构造应用上下文环境
第三步:初始化应用上下文
第四步:刷新应用上下文前的准备阶段
第五步:刷新应用上下文
第六步:刷新应用上下文后的扩展接口
Run方法第一步:获取并启动监听器
事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。
通过getRunListeners方法来获取监听器,在getRunListeners方法内部调用了一个getSpringFactoriesInstances方法,返回值是一个SpringApplicationRunListeners有参构造的监听器类,这个方法加载SpringApplicationRunListener类,把这个类当做key,这个类的作用就是负责在SpringBoot启动的不同阶段,广播出不同的消息,传递给ApplicationListener监听器实现类。
getSpringFactoriesInstances方法被重复使用。
总结:如何获取到监听器并进行启动开启监听。
Run方法第二步:构造应用上下文环境
应用上下文环境包括什么呢?包括计算机的环境,Java环境,Spring的运行环境,Spring项目的配置(在SpringBoot中就是那个熟悉的application.properties/yml)等等。
通过prepareEnvironment方法创建并按照相应的应用类型配置相应的环境,然后根据用户的配置,配置系统环境,然后启动监听器,并加载系统配置文件。
主要步骤方法:
getOrCreateEnvironment方法
configureEnvironment方法
listeners.environmentPrepared方法
总结:最终目的就是把环境信息封装到environment对象中,方便后面使用。
Run方法第三步:初始化应用上下文
通过createApplicationContext方法构建应用上下文对象context,而context中有一个属性beanFactory他是一个DefaultListableBeanFactory类,这就是我们所说的IoC容器。应用上下文对象初始化的同时IOC容器也被创建了。
通过createApplicationContext方法构建应用上下文对象context,而context中有一个属性beanFactory他是一个DefaultListableBeanFactory类,这就是我们所说的IoC容器。应用上下文对象初始化的同时IOC容器也被创建了。
在SpringBoot工程中,应用类型分为三种
通过反射拿到配置类的字节码对象并通过BeanUtils.instantiateClass方法进行实例化并返回。
总结:就是创建应用上下文对象同时创建了IOC容器。
Run方法第四步:刷新应用上下文前的准备阶段
主要的目的就是为前面的上下文对象context进行一些属性值的设置,在执行过程中还要完成一些Bean对象的创建,其中就包含核心启动类的创建。
属性设置
Bean对象创建
Spring容器在启动的时候,会将类解析成Spring内部的beanDefintion结构,并将beanDefintion存储到DefaultListableBeanFactory的Map中。BeanDefinitionLoader方法就是完成赋值。
总结:就是应用上下文属性的设置并把核心启动类生成实例化对象存储到容器中。
Run方法第五步:刷新应用上下文
Spring Boot的自动配置原理:通常来说主要就是依赖核心启动类上面的@SpringBootApplication注解,这个注解是一个组合注解,他组合了@EnableAutoConfiguration这个注解,在run方法启动会执行getImport方法,最终找到process方法,进行注解的扫描,通过注解组合关系,在底层借助@Import注解向容器导入AutoConfigurationImportSelector.class组件类,这个类在执行过程中他会去加载WEB-INF下名称为spring.factories的文件,从这个文件中根据EnableAutoConfiguration这个key来加载pom.xml引入的所有对应自动配置工厂类的全部路径配置,在经过过滤,选出真正生效的自动配置工厂类去生成实例存到容器中,从而完成自动装配。如果从Main方法的Run方法出发,了解实际实现的原理,就能知道他是怎么通过Main方法找到主类,然后再扫描主类注解,完成一系列操作。而在刷新应用上下文这步就是根据找到的主类来执行解析注解,完成自动装配的一系列过程。
通过refreshContext()方法一路跟下去,最终来到AbstractApplicationContext类的refresh()方法,其中最重要的方法就是invokeBeanFactoryPostProcessors方法,他就是在上下文中完成Bean的注册。
运行步骤:
1.prepareRefresh()刷新上下文
2.obtainFreshBeanFactory()在第三步初始化应用上下文中我们创建了应用的上下文,并触发了GenericApplicationContext类的构造方法如下所示,创建了beanFactory,也就是创建了DefaultListableBeanFactory类,这里就是拿到之前创建的beanFactory。
3.prepareBeanFactory()对上面获取的beanFactory,准备bean工厂,以便在此上下文中使用。
4.postProcessBeanFactory()向上下文中添加了一系列的Bean的后置处理器。
接着就进入到我们最重要的invokeBeanFactoryPostProcessors()方法,完成了IoC容器初始化过程的三个步骤:
1) 第一步:Resource定位
2) 第二步:BeanDefinition的载入
3) 第三个过程:注册BeanDefinition
总结:spring启动过程中,就是通过各种扫描,获取到对应的类,然后将类解析成spring内部的BeanDefition结构,存到容器中(注入到ConCurrentHashMap中),也就是最后的beanDefinitionMap中。
第一步分析:
主要方法,从invokeBeanFactoryPostProcessors方法一直往下跟,直到ConfigurationClassPostProcessor类的parse方法,会发现他把核心启动类传入了这个方法中。
在这个方法内部,他判断这个类上知否存在注解,如果存在继续进入下一个方法,直到真正做事的doProcessConfigurationClass方法,在这个方法类,他就开始处理@ComponentScan注解,获取到componentScans对象,然后调用this.componentScanParser.parse方法对他进行解析。
在方法内部根据basePackages获取对应类全限定集合,如果集合为空,就把当前的核心启动类全限定名的包名即com.lg加入,设置为basePackages(扫描的包范围),这里就完成了第一步,获取扫描路径。
第二步分析
接着再跳到doScan方法,开始把他转成BeanDefition并注入IOC容器。
在doScan方法中第一个关键点findCandidateComponents方法,根据传入的初始路径地址扫描该包及其子包所有的class,并封装成BeanDefinition并存入一个Set集合中,完成第二步。
第三步分析
有了BeanDefinition集合之后,对他进行遍历,在遍历的最后调用了一个registerBeanDefinition方法进行注册BeanDefinition。
在方法内部,执行到他的实现类DefaultListableBeanFactory中的registerBeanDefinition方法,就直接通过put方式把BeanDefinition注册进了beanDefinitionMap中。
@Import注解指定类解析
解析完主类扫描包之后,接着又开始解析@import注解指定类。
首先参数里面有一个getImports方法,他作用就是根据@import注解来获取到要导入到容器中的组件类。他从核心启动类中找到对应的@Import注解。在内部最终要的collectImports方法中,进行递归调用一直找到有@import注解的全类名,最后返回所有有@Import注解的组件类。
获取到注解组件类之后,就需要去执行组件类了,回到ConfigurationClassParser类的parse方法,执行this.deferredImportSelectorHandler.process方法。
接着往下走最后到processGroupImports方法内,里面有非常重要的一步grouping.getImports()。
先通过grouping.getImports()方法里面调用了process方法,加载spring.factories文件配置所有类,一步步过滤,最后封装成AutoConfigurationEntry对象返回,把这些对象放入Map<String, AnnotationMetadata> entries集合中。
最后通过this.group.selectImports()方法再进行过滤排序,返回要生效的自动装配对象全路径集合,最后通过this.reader.loadBeanDefinitions(configClasses)方法使这些自动装配类全部生效。
Run方法第六步:刷新应用上下文后的扩展接口
afterRefresh方法,他其实就是一个扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。