每周总结springboot源码分析

Spring Boot源码分析

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容器也被创建了。

 

在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,或者一些其它后置处理。

 

自定义Starter

Spring Boot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,Spring Boot就能自动扫描到要加载的信息并启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。Spring Boot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。Spring Boot提

供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。简而言之,starter就是一个外部的项目,我们需要使用它的时候就可以在当前Spring Boot项目中引入它,Spring Boot会自动完成装配。

使用场景

比如动态数据源、登陆模块、AOP日志切面等等就可以将这些功能封装成一个starter,复用的时候只需要在pom.xml引入即可,比如阿里的Druid数据源,就是阿里自己实现了一个第三方starter,因此Spring Boot就可以直接引入并使用这个数据库。命令规则SpringBoot提供的starter以 spring-boot-starter-xxx 的方式命名的。官方建议自定义的starter使用 xxx-spring-boot-starter 命名规则。以区分SpringBoot生态提供的starter,比如阿里的druid-spring-boot-starter。

自定义starter代码实现:

1. 新建maven jar工程,工程名为zdy-spring-boot-starter,导入依赖

 

2. 编写JavaBean

 

3. 编写配置类MyAutoConfiguration

 

4. resources下创建/META-INF/spring.factories

 

使用自定义starter

1. 对应项目导入自定义starter的依赖

 

2. 在全局配置文件中配置属性值

 

自定义Starter热插拔技术

@Enablexxx注解就是一种热拔插技术,加了这个注解就可以启动对应的starter,当不需要对应的starter的时候只需要把这个注解注释掉就行。

1. 新增标记类ConfigMarker

 

2. 新增EnableRegisterServer注解,将@Import引入的组件类生成实例,添加进容器

 

3. 改造 MyAutoConfiguration 新增条件注解 @ConditionalOnBean(ConfigMarker.class) ,@ConditionalOnBean 这个是条件注解,前面的意思代表只有当期上下文中含有 ConfigMarker对象,被标注的类才会被实例化,才能让自动配置类生效。

 

4. 在启动类上新增上面自定义@EnableRegisterServer注解,根据之前的源码分析可以知道他在执行过程中会解析这个注解,再去解析里面的@Import注解,从而拿到组件类生成实例化对象存入容器,满足自动装配条件。

 

关于条件注解的讲解:

@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时触发实例化。

内嵌tomcat原理

Spring Boot默认支持Tomcat,Jetty,和Undertow作为底层容器。而Spring Boot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。

通过前面的源码分析我们可以知道核心启动类在启动的时候,进入AutoConfigurationImportSelector类中的getAutoConfigurationEntry方法去各个模块WEB-INF下的spring.factories配置文件中加载相关配置类,获取到ServletWebServerFactoryAutoConfiguration自动配置类,也就是tomcat自动配置。

 

 

通过spring.factories配置文件找到ServletWebServerFactoryAutoConfiguration注解类分析上面的注解。

 

通过@Import发现他将EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow等嵌入式容器类加载进来了,进入EmbeddedTomcat.class类,通过TomcatServletWebServerFactory类的getWebServer方法,实现了tomcat的实例化,最后调用 getTomcatWebServer方法进入下一步操作。

 

根据上面方法继续往下走,到了initialize,就开始启动了tomcat。

 

总结:@Import引入的EmbeddedTomcat类里面TomcatServletWebServerFactory工厂类的getWebServer方法一旦被启动他就会创建并启动内嵌tomcat。

getWebServer方法被调用点分析

调用的地方其实就是之前分析过的refresh方法中的onRefresh方法

 

在方法里面进入到ServletWebServerApplicationContext类的createWebServer方法,他会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器,当获取到了容器工厂之后就通过工厂来调用getWebServer方法,也就是上面那个,来完成内嵌tomcat的创建和启动。

 

 

自动配置Spring MVC源码分析

SpringBoot项目里面是可以直接使用诸如 @RequestMapping 这类的SpringMVC的注解,在以前的项目中除了引入包之外还需要在web.xml配置一个前端控制器org.springframework.web.servlet.DispatcherServlet。spring Boot通过自动装配就实现了相同的效果, IOC容器的注入后,最重要的一点就是需要把DispatcherServlet再注册进servletContext中,也就是servlet容器中。

自动配置DispatcherServlet加载

和前面tomcat分析一样,我们还是要先去找到DispatcherServlet的自动配置类

 

接着到META-INF/spring.factories下,找到我们对应的DispatcherServletAutoConfiguration全限定名路径,进入对应注解类查看源码。

 

最下面那个类点进去发现他就是tomcat的那个注解类,也就是先需要tomcat完成自动装配再来解析DispatcherServletAutoConfiguration类。

 

在DispatcherServletAutoConfiguration类里面有两个内部类DispatcherServletConfiguration类和DispatcherServletRegistrationCondition类。

在第一个内部类中创建了DispatcherServlet实例化对象设值存到IOC容器,但是还未添加到servletContext。

 

在第二个内部类中就判断存在DispatcherServlet类对象再执行,而这个类对象已经在第一个内部类已经创建存在于上下文中。接着他构建了DispatcherServletRegistrationBean对象,并进行了返回。由此我们得到了两个对象,第一个前端控制器DispatcherServlet,第二个DispatcherServletRegistrationBean是DispatcherServlet的注册类,他的作用就是把他注册进servletContext中。

自动配置DispatcherServlet注册

 

通过类图分析最上面是一个ServletContextInitializer接口。我们可以知道,实现该接口意味着是用来初始化ServletContext的。

 

进入onStartup方法内部,根据类图一直往下直到ServletRegistrationBean类中的addRegistration方法,发现他把DispatcherServlet给add进了servletContext完成了注册。

 

而这个addRegistration方法的触发跟上面tomcat的触发是相同地方,通过getWebServer方法里面的getSelfInitializer方法在方法内部继续调用selfInitialize方法,通过getServletContextInitializerBeans拿到所有的ServletContextInitializer 集合,而集合中就包含了我们需要的DispatcherServlet控制器,接着遍历这个集合,使用遍历结果集调用onStartup方法,就完成了DispatcherServlet的注册。

 

三、Spring Boot高级进阶

Spring Boot数据源自动配置

application.properties文件中数据源指定的命名规则是因为底层源码设定了需要这样才能获取,注入DataSourceProperties类对象中。

 

 

数据源自动配置连接池默认指定Hikari,通过指定TYPE就可以实现更换连接池。

 

在引入spring-boot-starter-jdbc包后就把HikariCP也一并引入进来了,满足条件,而其他连接池未引入,不满足因此默认是HikariCP连接池。

 

 

如果多个连接池都满足的情况下,按照配置的数组顺序取值,第一个仍然是HikariCP,所以在不指定的情况下,默认永远是他。

 

Mybatis自动配置源码分析

与前面的tomcat自动配置,DispatcherServlet自动配置是一样的,都是通过run方法解析EnableAutoConfiguration注解,进入AutoConfigurationImportSelector类,然后去WEB-INF下的spring.factories配置文件中加载相关配置类,完成自动配置类组装。

 

1.通过application.properties文件设置mybatis属性可以注解注入到对应的MybatisProperties类使用。

 

2..这个类里面第一个方法sqlSessionFactory,通过他实现了创建sqlSessionFactory类、 Configuration类并添加容器,内部实现其实就是先解析配置,封装Configuration对象,完成准备工作,然后调用MyBatis的初始化流程。

 

3. 这个类第二个方法sqlSessionTemplate,作用是与mapperProoxy代理类有关。SqlSessionTemplate是线程安全的,可以被多个Dao持有。

 

4. 得到了SqlSessionFactory了,接下来就是如何扫描到相关的Mapper接口了,通过注解@MapperScan(basePackages = “com.mybatis.mapper”)实现,在内部@Import引入MapperScannerRegistrar类调用registerBeanDefinitions方法进行注册。

 

通俗点就是:@MapperScan(basePackages = “com.mybatis.mapper”)这个定义,扫描指定包下的mapper接口,通过动态代理生成了实现类,然后设置每个mapper接口的beanClass属性为MapperFactoryBean类型并加入到spring的bean容器中。使用者就可以通过@Autowired或者getBean等方式,从spring容器中获取。

SpringBoot + Mybatis实现动态数据源切换

业务背景

电商订单项目分正向和逆向两个部分:其中正向数据库记录了订单的基本信息,包括订单基本信息、订单商品信息、优惠卷信息、发票信息、账期信息、结算信息、订单备注信息、收货人信息等;逆向数据库主要包含了商品的退货信息和维修信息。数据量超过500万行就要考虑分库分表和读写分离,那么我们在正向操作和逆向操作的时候,就需要动态的切换到相应的数据库,进行相关的操作。

解决思路

现在项目的结构设计基本上是基于MVC的,那么数据库的操作集中在dao层完成,主要业务逻辑在service层处理,controller层处理请求。假设在执行dao层代码之前能够将数据源(DataSource)换成我们想要执行操作的数据源,那么这个问题就解决了。

 

实现原理

Spring内置了一个AbstractRoutingDataSource抽象类,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的数据源。因为AbstractRoutingDataSource也是一个DataSource接口,因此,应用程序可以先设置好key, 访问数据库的代码就可以从AbstractRoutingDataSource拿到对应的一个真实的数据源,从而访问指定的数据库。

通过内部的getConnection方法获取数据源,在连接数据库之前会执行determineCurrentLookupKey()方法,这个方法返回的数据作为key去targetDataSources中查找相应的值,如果查到相对应的DataSource,那么就使用此DataSource获取数据库连接。

 

 

环境准备工作

实体类:

 

Mapper类:

 

Service类

 

Controller类:

 

具体实现步骤

多数据源配置:

 

读取初始数据源类:

 

创造一个RoutingDataSourceContext类指定数据源

 

创建一个继承AbstractRoutingDataSource类的RoutingDataSource类,重写determineCurrentLookupKey方法

 

创建一个primaryDataSource方法,引入两个数据源bean对象,获取数据源,设置key存入继承类RoutingDataSource中。

 

改造Controller类,在具体业务逻辑执行前,进行数据源绑定确认。

 

测试结果,分别调用两个方法,返回了不同数据库的结果

 

动态数据源优化

需要读数据库的地方,在对应controller类方法里面就需要加上一大段RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);代码,这里可以通过自定义注解@RoutingWith("slaveDataSource"),来实现这个效果,取消大量重复代码编写。

新增RoutingWith注解类:

 

借助AOP的动态代理对方法拦截实现横切逻辑增强,添加Maven依赖

 

切面类RoutingAspect

 

自定义注解定义完毕,把controller类改造成注解方式就完成了优化改造。

posted @ 2023-09-08 00:16  橘子味芬达水  阅读(8)  评论(0编辑  收藏  举报