SpringBoot核心知识梳理

目录

基础篇

一.SpringBoot的自动配置原理是什么?

二.Spring Boot 中如何解决跨域问题 ?

源码篇

四.源码剖析-自动配置

Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

@SpringBootApplication

@SpringBootConfiguration

@EnableAutoConfiguration

@AutoConfigurationPackage

六.在SpringBoot中Mybatis自动配置源码分析


基础篇

一.SpringBoot的自动配置原理是什么?

   主要是Spring Boot的启动类上的核心注解SpringBootApplication注解主配置类,有了这个主配置类启动时就会为SpringBoot开启一个@EnableAutoConfiguration注解自动配置功能。

有了这个EnableAutoConfiguration的话就会:

       1. 从配置文件META_INF/Spring.factories加载可能用到的自动配置类

       2. 去重,并将exclude和excludeName属性携带的类排除

       3. 过滤,将满足条件(@Conditional)的自动配置类返回

二.Spring Boot 中如何解决跨域问题 ?

     跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

@Configuration

public class CorsConfig implements WebMvcConfigurer {

  @Override

  public void addCorsMappings(CorsRegistry registry) {

    registry.addMapping("/**")

        .allowedOrigins("*")

        .allowCredentials(true)

        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")

        .maxAge(3600);

  }

}

  • Spring Boot 打成的 jar 和普通的jar 有什么区别 ?

1.SpringBoot项目打包时能打成 jar 与 war包,对比两种打包方式:

jar更加简单方便,使用 java -jar xx.jar 就可以启动。所以打成 jar 包的最多。

而 war包可以部署到tomcat的 webapps 中,随Tomcat的启动而启动。具体使用哪种方

式,应视应用场景而定。

2、打jar包时不会把src/main/webapp 下的内容打到jar包里 (你认为的打到jar包里面,路径是不行的会报404)打war包时会把src/main/webapp 下的内容打到war包里

3.打成什么文件包进行部署与项目业务有关,就像提供 rest 服务的项目需要打包成 jar文件,用命令运行很方便。。。而有大量css、js、html,且需要经常改动的项目,打成 war 包去运行比较方便,因为改动静态资源可以直接覆盖,很快看到改动后的效果,这是 jar 包不能比的(举个‘栗’子:项目打成 jar 包运行,一段时间后,前端要对其中某几个页面样式进行改动,使其更美观,那么改动几个css、html后,需要重新打成一个新的 jar 包,上传服务器并运行,这种改动频繁时很不友好,文件大时上传服务器很耗时,那么 war包就能免去这种烦恼,只要覆盖几个css与html即可).

源码篇

四.源码剖析-自动配置

自动配置:根据我们添加的jar包依赖,会自动将一些配置类的bean注册进ioc容器,我们可以需要的地方使用@autowired或者@resource等注解来使用它。

Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,

@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。

@SpringBootApplication

下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下

@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中

@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时

@Documented //表示注解可以记录在javadoc中

@Inherited  //表示可以被子类继承该注解

@SpringBootConfiguration   // 标明该类为配置类

@EnableAutoConfiguration   // 启动自动配置功能

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes =

TypeExcludeFilter.class),

@Filter(type = FilterType.CUSTOM, classes =

AutoConfigurationExcludeFilter.class) })

public @interface SpringBootApplication {

// 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。

@AliasFor(annotation = EnableAutoConfiguration.class)

Class[] exclude() default {};

// 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全

类名字符串数组。

@AliasFor(annotation = EnableAutoConfiguration.class)

String[] excludeName() default {};

// 指定扫描包,参数是包名的字符串数组。

@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")

String[] scanBasePackages() default {};

// 扫描特定的包,参数类似是Class类型数组。

@AliasFor(annotation = ComponentScan.class, attribute =

"basePackageClasses")

Class[] scanBasePackageClasses() default {};

}

从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下

@SpringBootConfiguration

@SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot的配置类。

查看@SpringBootConfiguration注解源码,核心代码具体如下。

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象

public @interface SpringBootConfiguration {

}

 从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。

@EnableAutoConfiguration

package org.springframework.boot.autoconfigure;

// 自动配置包

@AutoConfigurationPackage

// Spring的底层注解@Import,给容器中导入一个组件;

// 导入的组件是AutoConfigurationPackages.Registrar.class

@Import(AutoConfigurationImportSelector.class)

// 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。

public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

// 返回不会被导入到 Spring 容器中的类

Class[] exclude() default {};

// 返回不会被导入到 Spring 容器中的类名

String[] excludeName() default {};

}

Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean ,并加载到 IOC 容器。

@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。

@AutoConfigurationPackage

package org.springframework.boot.autoconfigure;

@Import(AutoConfigurationPackages.Registrar.class) // 导入Registrar中注册的组件

public @interface AutoConfigurationPackage {

}

 @AutoConfigurationPackage :自动配置包,它也是一个组合注解,其中最重要的注解是

@Import(AutoConfigurationPackages.Registrar.class) ,它是 Spring 框架的底层注解,它的作

用就是给容器中导入某个组件类,例如

@Import(AutoConfigurationPackages.Registrar.class) ,它就是将 Registrar 这个组件类导入

到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法:

@Override

public void registerBeanDefinitions(AnnotationMetadata metadata,

BeanDefinitionRegistry registry) {

// 将注解标注的元信息传入,获取到相应的包名

register(registry, new PackageImport(metadata).getPackageName());

}

我们对 new PackageImport(metadata).getPackageName() 进行检索,看看其结果是什么?

SpringBoot核心知识梳理_第1张图片

再看register方法

public static void register(BeanDefinitionRegistry registry, String...

packageNames) {

// 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解

// @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包

if (registry.containsBeanDefinition(BEAN)) {

// 如果该bean已经注册,则将要注册包名称添加进去

BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);

ConstructorArgumentValues constructorArguments = beanDefinition

.getConstructorArgumentValues();

constructorArguments.addIndexedArgumentValue(0,

addBasePackages(constructorArguments, packageNames));

}

else {

//如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

beanDefinition.setBeanClass(BasePackages.class);

beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames);

beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

registry.registerBeanDefinition(BEAN, beanDefinition);

}

}

AutoConfigurationPackages.Registrar这个类就干一个事,注册一个 Bean ,这个 Bean 就是

org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有

一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。

@Import(AutoConfigurationImportSelector.class)

@Import({AutoConfigurationImportSelector.class}) :将

AutoConfigurationImportSelector 这个类导入到 Spring 容器中,

AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration

配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。

SpringBoot核心知识梳理_第2张图片

可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。

其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。

确定自动配置实现逻辑的入口方法:

  跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处,因此我们就从 DeferredImportSelectorGrouping 类的 getImports 方法来开始分析SpringBoot的自动配置源码好了。

先看一下 getImports 方法代码:

// ConfigurationClassParser.java

public Iterable getImports() {

  // 遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合

装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector

  for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {

    // 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定

导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了)

    this.group.process(deferredImport.getConfigurationClass().getMetadata(),

        deferredImport.getImportSelector());

 }

  // 【2】,经过上面的处理后,然后再进行选择导入哪些配置类

  return this.group.selectImports();

}

标 【1】 处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(),

deferredImport.getImportSelector()) ;主要做的事情就是在 this.group 即AutoConfigurationGroup 对象的 process 方法中,传入的 AutoConfigurationImportSelector

对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。

注:

AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配

置相关的逻辑,拥有process和selectImports方法,然后拥有entries和

autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道

这些就足够了;

AutoConfigurationImportSelector:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配

置类;

metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据

标【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一

步有选择的选择导

再进入到AutoConfigurationImportSelector$AutoConfigurationGroup的pross方法:

SpringBoot核心知识梳理_第3张图片

通过图中我们可以看到,跟自动配置逻辑相关的入口方法在process方法中

分析自动配置的主要逻辑

  • 源码剖析-Run方法执行流程

SpringBoot项目的mian函数

@SpringBootApplication //来标注一个主程序类,说明这是一个Spring Boot应用

public class MyTestMVCApplication {

public static void main(String[] args) {

SpringApplication.run(MyTestMVCApplication.class, args);

}

}

点进run方法

public static ConfigurableApplicationContext run(Class primarySource,

String... args) {

// 调用重载方法

return run(new Class[] { primarySource }, args);

}

public static ConfigurableApplicationContext run(Class[] primarySources,

String[] args) {

// 两件事:1.初始化SpringApplication 2.执行run方法

return new SpringApplication(primarySources).run(args);

}

SpringApplication() 构造方法

继续查看源码, SpringApplication 实例化过程,首先是进入带参数的构造方法,最终回来到两个参数的构造方法。

public SpringApplication(Class... primarySources) {

 this(null, primarySources);

}

@SuppressWarnings({"unchecked", "rawtypes"})

public SpringApplication(ResourceLoader resourceLoader, Class...

primarySources) {

//设置资源加载器为null

this.resourceLoader = resourceLoader;

//断言加载资源类不能为null

Assert.notNull(primarySources, "PrimarySources must not be null");

//将primarySources数组转换为List,最后放到LinkedHashSet集合中

this.primarySources = new LinkedHashSet<>

(Arrays.asList(primarySources));

//【1.1 推断应用类型,后面会根据类型初始化对应的环境。常用的一般都是servlet环境 】

this.webApplicationType = WebApplicationType.deduceFromClasspath();

//【1.2 初始化classpath下 META-INF/spring.factories中已配置的

ApplicationContextInitializer 】

setInitializers((Collection)

getSpringFactoriesInstances(ApplicationContextInitializer.class));

//【1.3 初始化classpath下所有已配置的 ApplicationListener 】

setListeners((Collection)

getSpringFactoriesInstances(ApplicationListener.class));

//【1.4 根据调用栈,推断出 main 方法的类名 】

this.mainApplicationClass = deduceMainApplicationClass();

}

deduceWebApplicationType();

private static final String[] WEB_ENVIRONMENT_CLASSES =

{"javax.servlet.Servlet",

    

"org.springframework.web.context.ConfigurableWebApplicationContext"};

private static final String REACTIVE_WEB_ENVIRONMENT_CLASS =

"org.springframework."

   + "web.reactive.DispatcherHandler";

private static final String MVC_WEB_ENVIRONMENT_CLASS =

"org.springframework."

   + "web.servlet.DispatcherServlet";

private static final String JERSEY_WEB_ENVIRONMENT_CLASS =

"org.glassfish.jersey.server.ResourceConfig";

/**

* 判断 应用的类型

* NONE: 应用程序不是web应用,也不应该用web服务器去启动

* SERVLET: 应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet

web(tomcat)服务器。

* REACTIVE: 应用程序应作为 reactive web应用程序运行,并应启动嵌入式 reactive web服

务器。

* @return

*/

private WebApplicationType deduceWebApplicationType() {

 //classpath下必须存在org.springframework.web.reactive.DispatcherHandler

 if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)

     && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)

      && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null))

{

   return WebApplicationType.REACTIVE;

}

 for (String className : WEB_ENVIRONMENT_CLASSES) {

   if (!ClassUtils.isPresent(className, null)) {

     return WebApplicationType.NONE;

   }

}

  //classpath环境下存在javax.servlet.Servlet或者

org.springframework.web.context.ConfigurableWebApplicationContext

 return WebApplicationType.SERVLET;

}

返回类型是WebApplicationType的枚举类型, WebApplicationType 有三个枚举,三个枚举的解释如其中注释

具体的判断逻辑如下:

WebApplicationType.REACTIVE classpath下存在

org.springframework.web.reactive.DispatcherHandler

WebApplicationType.SERVLET classpath下存在javax.servlet.Servlet或者

org.springframework.web.context.ConfigurableWebApplicationContext

WebApplicationType.NONE 不满足以上条件。

setInitializers((Collection)

getSpringFactoriesInstances(ApplicationContextInitializer.class));

初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer

private  Collection getSpringFactoriesInstances(Class type) {

  return getSpringFactoriesInstances(type, new Class[]{});

}

/**

* 通过指定的classloader 从META-INF/spring.factories获取指定的Spring的工厂实例

* @param type

* @param parameterTypes

* @param args

* @param 

* @return

*/

private  Collection getSpringFactoriesInstances(Class type,

                           Class[] parameterTypes,

Object... args) {

  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

  // Use names and ensure unique to protect against duplicates

  //通过指定的classLoader从 META-INF/spring.factories 的资源文件中,

  //读取 key 为 type.getName() 的 value

  Set names = new LinkedHashSet<>

(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

  //创建Spring工厂实例

  List instances = createSpringFactoriesInstances(type, parameterTypes,

      classLoader, args, names);

  //对Spring工厂实例排序(org.springframework.core.annotation.Order注解指定的顺序)

  AnnotationAwareOrderComparator.sort(instances);

  return instances;

}

看看 getSpringFactoriesInstances 都干了什么,看源码,有一个方法很重要 loadFactoryNames()这个方法很重要,这个方法是spring-core中提供的从META-INF/spring.factories中获取指定的类(key)的同一入口方法。在这里,获取的是key为 org.springframework.context.ApplicationContextInitializer 的类。

debug看看都获取到了哪些

SpringBoot核心知识梳理_第4张图片

上面说了,是从classpath下 META-INF/spring.factories中获取,我们验证一下:

发现在SpringBoot核心知识梳理_第5张图片

SpringBoot核心知识梳理_第6张图片

上图所示的两个工程中找到了debug中看到的结果。

ApplicationContextInitializer 是Spring框架的类, 这个类的主要目的就是在

ConfigurableApplicationContext 调用refresh()方法之前,回调这个类的initialize方法。

通过 ConfigurableApplicationContext 的实例获取容器的环境Environment,从而实现对配置文件的修改完善等工作。

etListeners((Collection)

getSpringFactoriesInstances(ApplicationListener.class));

初始化classpath下 META-INF/spring.factories中已配置的 ApplicationListener。

ApplicationListener 的加载过程和上面的 ApplicationContextInitializer 类的加载过程是一样的。

不多说了,至于 ApplicationListener 是spring的事件监听器,典型的观察者模式,通过

ApplicationEvent 类和 ApplicationListener 接口,可以实现对spring容器全生命周期的监听,当然也可以自定义监听事件

总结

关于 SpringApplication 类的构造过程,到这里我们就梳理完了。纵观 SpringApplication 类的实例化过程,我们可以看到,合理的利用该类,我们能在spring容器创建之前做一些预备工作,和定制化的需求。

比如,自定义SpringBoot的Banner,比如自定义事件监听器,再比如在容器refresh之前通过自定义ApplicationContextInitializer 修改配置一些配置或者获取指定的bean都是可以的

六.在SpringBoot中Mybatis自动配置源码分析

1、springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解

@SpringBootApplication中的@EnableAutoConfiguration。

2、EnableAutoConfiguration主要是通过AutoConfigurationImportSelector类来加载

以mybatis为例,*selector通过反射加载spring.factories中指定的java类,也就是加载

MybatisAutoConfiguration类(该类有Configuration注解,属于配置类。

SpringBoot核心知识梳理_第7张图片

SpringBoot核心知识梳理_第8张图片

/**

* {@link EnableAutoConfiguration Auto-Configuration} for Mybatis.

Contributes a 重点:SqlSessionFactory 和 SqlSessionTemplate 两个类

* {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.

*

* If {@link org.mybatis.spring.annotation.MapperScan} is used, or a

* configuration file is specified as a property, those will be considered,

* otherwise this auto-configuration will attempt to register mappers based

on

* the interface definitions in or under the root auto-configuration

package.

*

* @author Eddú Meléndez

* @author Josh Long

* @author Kazuki Shimizu

* @author Eduardo Macarrón

*/

@org.springframework.context.annotation.Configuration

@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class

})

@ConditionalOnBean(DataSource.class)

@EnableConfigurationProperties(MybatisProperties.class)

@AutoConfigureAfter(DataSourceAutoConfiguration.class)

public class MybatisAutoConfiguration {

 private static final Logger logger =

LoggerFactory.getLogger(MybatisAutoConfiguration.class);

  // //与mybatis配置文件对应

 private final MybatisProperties properties;

 private final Interceptor[] interceptors;

 private final ResourceLoader resourceLoader;

 private final DatabaseIdProvider databaseIdProvider;

 private final List configurationCustomizers;

 public MybatisAutoConfiguration(MybatisProperties properties,

                 ObjectProvider

interceptorsProvider,

                 ResourceLoader resourceLoader,

                 ObjectProvider

databaseIdProvider,

               

 ObjectProvider>

configurationCustomizersProvider) {

  this.properties = properties;

  this.interceptors = interceptorsProvider.getIfAvailable();

 this.resourceLoader = resourceLoader;

  this.databaseIdProvider = databaseIdProvider.getIfAvailable();

  this.configurationCustomizers =

configurationCustomizersProvider.getIfAvailable();

}

 //postConstruct作用是在创建类的时候先调用, 校验配置文件是否存在

 @PostConstruct

 public void checkConfigFileExists() {

  if (this.properties.isCheckConfigLocation() &&

StringUtils.hasText(this.properties.getConfigLocation())) {

   Resource resource =

this.resourceLoader.getResource(this.properties.getConfigLocation());

   Assert.state(resource.exists(), "Cannot find config location: " +

resource

     + " (please add config file or check your Mybatis

configuration)");

 }

}

  //conditionalOnMissingBean作用:在没有类的时候调用,创建sqlsessionFactory

sqlsessionfactory最主要的是创建并保存了Configuration类

 @Bean

 @ConditionalOnMissingBean

 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws

Exception {

  SqlSessionFactoryBean factory = new SqlSessionFactoryBean();

  factory.setDataSource(dataSource);

  factory.setVfs(SpringBootVFS.class);

  if (StringUtils.hasText(this.properties.getConfigLocation())) {

 factory.setConfigLocation(this.resourceLoader.getResource(this.properties.g

etConfigLocation()));

 }

  Configuration configuration = this.properties.getConfiguration();

  if (configuration == null &&

!StringUtils.hasText(this.properties.getConfigLocation())) {

   configuration = new Configuration();

 }

  if (configuration != null &&

!CollectionUtils.isEmpty(this.configurationCustomizers)) {

   for (ConfigurationCustomizer customizer :

this.configurationCustomizers) {

    customizer.customize(configuration);

  }

 }

  factory.setConfiguration(configuration);

  if (this.properties.getConfigurationProperties() != null) {

 factory.setConfigurationProperties(this.properties.getConfigurationProperti

es());

 }

  if (!ObjectUtils.isEmpty(this.interceptors)) {

   factory.setPlugins(this.interceptors);

 }

  if (this.databaseIdProvider != null) {

   factory.setDatabaseIdProvider(this.databaseIdProvider);

 }

if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {

 factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());

 }

  if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {

 factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());

 }

  if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {

   factory.setMapperLocations(this.properties.resolveMapperLocations());

 }

   // //获取SqlSessionFactoryBean的getObject()中的对象注入Spring容器,也就是

SqlSessionFactory对象

  return factory.getObject();

}

 @Bean

 @ConditionalOnMissingBean

  // 往Spring容器中注入SqlSessionTemplate对象

 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory

sqlSessionFactory) {

  ExecutorType executorType = this.properties.getExecutorType();

  if (executorType != null) {

   return new SqlSessionTemplate(sqlSessionFactory, executorType);

 } else {

   return new SqlSessionTemplate(sqlSessionFactory);

 }

}

3、MybatisAutoConfiguration:

①类中有个MybatisProperties类,该类对应的是mybatis的配置文件

②类中有个sqlSessionFactory方法,作用是创建SqlSessionFactory类、Configuration类

(mybatis最主要的类,保存着与mybatis相关的东西)

③SelSessionTemplate,作用是与mapperProoxy代理类有关

sqlSessionFactory主要是通过创建了一个SqlSessionFactoryBean,这个类实现了FactoryBean接口,所以在Spring容器就会注入这个类中定义的getObject方法返回的对象。

看一下getObject()方法做了什么?

@Override

 public SqlSessionFactory getObject() throws Exception {

  if (this.sqlSessionFactory == null) {

   afterPropertiesSet();

 }

  return this.sqlSessionFactory;

}

 @Override

 public void afterPropertiesSet() throws Exception {

  notNull(dataSource, "Property 'dataSource' is required");

  notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder'

is required");

 state((configuration == null && configLocation == null) || !

(configuration != null && configLocation != null),

    "Property 'configuration' and 'configLocation' can not specified

with together");

  this.sqlSessionFactory = buildSqlSessionFactory();

}

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

  final Configuration targetConfiguration;

  XMLConfigBuilder xmlConfigBuilder = null;

  if (this.configuration != null) {

   targetConfiguration = this.configuration;

   if (targetConfiguration.getVariables() == null) {

    targetConfiguration.setVariables(this.configurationProperties);

  } else if (this.configurationProperties != null) {

  

 targetConfiguration.getVariables().putAll(this.configurationProperties);

  }

 } else if (this.configLocation != null) {

   xmlConfigBuilder = new

XMLConfigBuilder(this.configLocation.getInputStream(), null,

this.configurationProperties);

   targetConfiguration = xmlConfigBuilder.getConfiguration();

 } else {

   LOGGER.debug(

    () -> "Property 'configuration' or 'configLocation' not specified,

using default MyBatis Configuration");

   targetConfiguration = new Configuration();

 Optional.ofNullable(this.configurationProperties).ifPresent(targetConfigura

tion::setVariables);

 }

 Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setO

bjectFactory);

 Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguratio

n::setObjectWrapperFactory);

 Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

  if (hasLength(this.typeAliasesPackage)) {

   scanClasses(this.typeAliasesPackage,

this.typeAliasesSuperType).stream()

    .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz ->

!clazz.isInterface())

    .filter(clazz ->

!clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::

registerAlias);

 }

  if (!isEmpty(this.typeAliases)) {

   Stream.of(this.typeAliases).forEach(typeAlias -> {

 targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);

    LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");

  });

 }

  if (!isEmpty(this.plugins)) {

   Stream.of(this.plugins).forEach(plugin -> {

    targetConfiguration.addInterceptor(plugin);

    LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");

  });

 }

  if (hasLength(this.typeHandlersPackage)) {

   scanClasses(this.typeHandlersPackage,

TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())

    .filter(clazz -> !clazz.isInterface()).filter(clazz ->

!Modifier.isAbstract(clazz.getModifiers()))

    .forEach(targetConfiguration.getTypeHandlerRegistry()::register);

 }

  if (!isEmpty(this.typeHandlers)) {

   Stream.of(this.typeHandlers).forEach(typeHandler -> {

    targetConfiguration.getTypeHandlerRegistry().register(typeHandler);

    LOGGER.debug(() -> "Registered type handler: '" + typeHandler +

"'");

  });

 }

  targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

  if (!isEmpty(this.scriptingLanguageDrivers)) {

   Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {

    targetConfiguration.getLanguageRegistry().register(languageDriver);

    LOGGER.debug(() -> "Registered scripting language driver: '" +

languageDriver + "'");

  });

 }

  Optional.ofNullable(this.defaultScriptingLanguageDriver)

   .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

  if (this.databaseIdProvider != null) {// fix #64 set databaseId before

parse mapper xmls

   try {

  

 targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(thi

s.dataSource));

  } catch (SQLException e) {

    throw new NestedIOException("Failed getting a databaseId", e);

  }

 }

 Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

  if (xmlConfigBuilder != null) {

   try {

xmlConfigBuilder.parse();

 LOGGER.debug(() -> "Parsed configuration file: '" +

this.configLocation + "'");

  } catch (Exception ex) {

    throw new NestedIOException("Failed to parse config resource: " +

this.configLocation, ex);

  } finally {

    ErrorContext.instance().reset();

  }

 }

  targetConfiguration.setEnvironment(new Environment(this.environment,

    this.transactionFactory == null ? new

SpringManagedTransactionFactory() : this.transactionFactory,

    this.dataSource));

  if (this.mapperLocations != null) {

   if (this.mapperLocations.length == 0) {

    LOGGER.warn(() -> "Property 'mapperLocations' was specified but

matching resources are not found.");

  } else {

    for (Resource mapperLocation : this.mapperLocations) {

     if (mapperLocation == null) {

      continue;

    }

     try {

      XMLMapperBuilder xmlMapperBuilder = new

XMLMapperBuilder(mapperLocation.getInputStream(),

        targetConfiguration, mapperLocation.toString(),

targetConfiguration.getSqlFragments());

      //这个方法已经是mybaits的源码,初始化流程

      xmlMapperBuilder.parse();

    } catch (Exception e) {

      throw new NestedIOException("Failed to parse mapping resource:

'" + mapperLocation + "'", e);

    } finally {

      ErrorContext.instance().reset();

    }

     LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation +

"'");

   }

  }

 } else {

   LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");

 }

//这个方法已经是mybaits的源码,初始化流程

  return this.sqlSessionFactoryBuilder.build(targetConfiguration);

}

这个已经很明显了,实际上就是调用了MyBatis的初始化流程

现在已经得到了SqlSessionFactory了,接下来就是如何扫描到相关的Mapper接口了。

这个需要看这个注解

@MapperScan(basePackages = “com.mybatis.mapper”)

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Import(MapperScannerRegistrar.class)

@Repeatable(MapperScans.class)

public @interface MapperScan

通过@Import的方式会扫描到MapperScannerRegistrar类。

MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,那么在spring实例化之前就会调用到registerBeanDefinitions方法

public class MapperScannerRegistrar implements

ImportBeanDefinitionRegistrar, ResourceLoaderAware

@Override

 public void registerBeanDefinitions(AnnotationMetadata

importingClassMetadata, BeanDefinitionRegistry registry) {

//拿到MapperScan注解,并解析注解中定义的属性封装成AnnotationAttributes对象

  AnnotationAttributes mapperScanAttrs = AnnotationAttributes

  

.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.get

Name()));

  if (mapperScanAttrs != null) {

   registerBeanDefinitions(importingClassMetadata, mapperScanAttrs,

registry,

     generateBaseBeanName(importingClassMetadata, 0));

 }

}

 void registerBeanDefinitions(AnnotationMetadata annoMeta,

AnnotationAttributes annoAttrs,

   BeanDefinitionRegistry registry, String beanName) {

  BeanDefinitionBuilder builder =

BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

  builder.addPropertyValue("processPropertyPlaceHolders", true);

  Class annotationClass =

annoAttrs.getClass("annotationClass");

  if (!Annotation.class.equals(annotationClass)) {

   builder.addPropertyValue("annotationClass", annotationClass);

 }

  Class markerInterface = annoAttrs.getClass("markerInterface");

  if (!Class.class.equals(markerInterface)) {

   builder.addPropertyValue("markerInterface", markerInterface);

 }

  Class generatorClass =

annoAttrs.getClass("nameGenerator");

  if (!BeanNameGenerator.class.equals(generatorClass)) {

   builder.addPropertyValue("nameGenerator",

BeanUtils.instantiateClass(generatorClass));

 }

Class mapperFactoryBeanClass =

annoAttrs.getClass("factoryBean");

  if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {

   builder.addPropertyValue("mapperFactoryBeanClass",

mapperFactoryBeanClass);

 }

  String sqlSessionTemplateRef =

annoAttrs.getString("sqlSessionTemplateRef");

  if (StringUtils.hasText(sqlSessionTemplateRef)) {

   builder.addPropertyValue("sqlSessionTemplateBeanName",

annoAttrs.getString("sqlSessionTemplateRef"));

 }

  String sqlSessionFactoryRef =

annoAttrs.getString("sqlSessionFactoryRef");

  if (StringUtils.hasText(sqlSessionFactoryRef)) {

   builder.addPropertyValue("sqlSessionFactoryBeanName",

annoAttrs.getString("sqlSessionFactoryRef"));

 }

  List basePackages = new ArrayList<>();

  basePackages.addAll(

  

 Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasTex

t).collect(Collectors.toList()));

 basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages"))

.filter(StringUtils::hasText)

   .collect(Collectors.toList()));

 basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClass

es")).map(ClassUtils::getPackageName)

   .collect(Collectors.toList()));

  if (basePackages.isEmpty()) {

   basePackages.add(getDefaultBasePackage(annoMeta));

 }

  String lazyInitialization = annoAttrs.getString("lazyInitialization");

  if (StringUtils.hasText(lazyInitialization)) {

   builder.addPropertyValue("lazyInitialization", lazyInitialization);

 }

  builder.addPropertyValue("basePackage",

StringUtils.collectionToCommaDelimitedString(basePackages));

//把类型为MapperScannerConfigurer的注册到spring容器中

  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,所以接着又会扫

描并调用到postProcessBeanDefinitionRegistry方法。

public class MapperScannerConfigurer

  implements BeanDefinitionRegistryPostProcessor, InitializingBean,

ApplicationContextAware, BeanNameAware

@Override

 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry

registry) {

  if (this.processPropertyPlaceHolders) {

   processPropertyPlaceHolders();

 }

  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

  scanner.setAddToConfig(this.addToConfig);

  scanner.setAnnotationClass(this.annotationClass);

  scanner.setMarkerInterface(this.markerInterface);

  scanner.setSqlSessionFactory(this.sqlSessionFactory);

  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);

  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);

  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);

  scanner.setResourceLoader(this.applicationContext);

  scanner.setBeanNameGenerator(this.nameGenerator);

  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);

  if (StringUtils.hasText(lazyInitialization)) {

   scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));

 }

  scanner.registerFilters();

  scanner.scan(

    StringUtils.tokenizeToStringArray(this.basePackage,

ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

}

public int scan(String... basePackages) {

int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

doScan(basePackages);

// Register annotation config processors, if necessary.

if (this.includeAnnotationConfig) {

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);

}

return (this.registry.getBeanDefinitionCount() -

beanCountAtScanStart);

}

@Override

 public Set doScan(String... basePackages) {

//这个方法主要就注册扫描basePackages路径下的mapper接口,然后封装成一个

BeanDefinition后加入到spring容器中

  Set beanDefinitions = super.doScan(basePackages);

  if (beanDefinitions.isEmpty()) {

   LOGGER.warn(() -> "No MyBatis mapper was found in '" +

Arrays.toString(basePackages)

   + "' package. Please check your configuration.");

 } else {

   //这个方法主要会把原BeanDefinition的beanClass类型,修改为MapperFactoryBean

   processBeanDefinitions(beanDefinitions);

 }

  return beanDefinitions;

}

修改了mapper的beanClass类型为MapperFactoryBean

private void processBeanDefinitions(Set

beanDefinitions) {

  GenericBeanDefinition definition;

  for (BeanDefinitionHolder holder : beanDefinitions) {

   definition = (GenericBeanDefinition) holder.getBeanDefinition();

   String beanClassName = definition.getBeanClassName();

   LOGGER.debug(() -> "Creating MapperFactoryBean with name '" +

holder.getBeanName() + "' and '" + beanClassName

     + "' mapperInterface");

   // the mapper interface is the original class of the bean

   // but, the actual class of the bean is MapperFactoryBean

 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClass

Name); // issue #59

   //修改beanClass类型

   definition.setBeanClass(this.mapperFactoryBeanClass);

   definition.getPropertyValues().add("addToConfig", this.addToConfig);

   boolean explicitFactoryUsed = false;

   if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {

    definition.getPropertyValues().add("sqlSessionFactory",

      new RuntimeBeanReference(this.sqlSessionFactoryBeanName));

    explicitFactoryUsed = true;

  } else if (this.sqlSessionFactory != null) {

    definition.getPropertyValues().add("sqlSessionFactory",

this.sqlSessionFactory);

    explicitFactoryUsed = true;

  }

   if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {

    if (explicitFactoryUsed) {

     LOGGER.warn(

      () -> "Cannot use both: sqlSessionTemplate and

sqlSessionFactory together. sqlSessionFactory is ignored.");

   }

    definition.getPropertyValues().add("sqlSessionTemplate",

      new RuntimeBeanReference(this.sqlSessionTemplateBeanName));

    explicitFactoryUsed = true;

  } else if (this.sqlSessionTemplate != null) {

    if (explicitFactoryUsed) {

     LOGGER.warn(

      () -> "Cannot use both: sqlSessionTemplate and

sqlSessionFactory together. sqlSessionFactory is ignored.");

   }

definition.getPropertyValues().add("sqlSessionTemplate",

this.sqlSessionTemplate);

    explicitFactoryUsed = true;

  }

   if (!explicitFactoryUsed) {

    LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean

with name '" + holder.getBeanName() + "'.");

    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

  }

   definition.setLazyInit(lazyInitialization);

 }

}

上述几步主要是完成通过

@MapperScan(basePackages = “com.mybatis.mapper”)这个定义,扫描指定包下的

mapper接口,然后设置每个mapper接口的beanClass属性为MapperFactoryBean类型并加入

到spring的bean容器中。

MapperFactoryBean实现了FactoryBean接口,所以当spring从待实例化的bean容器中遍历到这个bean并开始执行实例化时返回的对象实际上是getObject方法中返回的对象。

public class MapperFactoryBean extends SqlSessionDaoSupport implements

FactoryBean

最后看一下MapperFactoryBean的getObject方法,实际上返回的就是mybatis中通过getMapper拿到的对象,熟悉mybatis源码的就应该清楚,这个就是mybatis通过动态代理生成的mapper接口实现类'

 @Override

 public T getObject() throws Exception {

  return getSqlSession().getMapper(this.mapperInterface);

}

到此,mapper接口现在也通过动态代理生成了实现类,并且注入到spring的bean容器中了,之后使用者就可以通过@Autowired或者getBean等方式,从spring容器中获取到了。

你可能感兴趣的