SpringBoot与Mybatis整合源码深度解析

问题:

       SpringBoot中没有配置Mybatis的整合类,只有Mybatis的配置文件,那么是怎么进行关联的 ?

       在Mapper接口上不加@component或者是@repository也能将Mapper注入到Spring中,是如何实现的?

       所写的Mapper接口是如何关联到xml里面所写的sql语句的?

       如果对于上述三个问题的答案不是很清楚的话,可以往下面,本文将从源码的角度分析SpringBoot与Mybatis整合的源码,对于Spring和Mybatis的整合思想都是一样的,因为现在公司中大多数使用的是SpringBoot,所以选用SpringBoot与Mybatis整合作为分析的重点。

       SpringBoot与Mybatis整合体现在代码上是很简单,只需要三步就行:

       一.引入Mybatis启动包:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis.springboot.version}</version>
</dependency>

      二.在SpringBoot的启动类上面@MapperScan注解:

@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
    public static void main(String[] args) {
        SpringApplication.run(WechatApplication.class, args);
    }
}

    三:在SpringBoot的application.properties配置文件中指定mybatis的配置文件和mapper的配置文件的路径:

mybatis.config-location = classpath:mybatis/SqlMapConfig.xml
mybatis.mapper-locations = classpath:mybatis/mapper/*.xml

   这样就指定Mapper接口、Mapper的xml文件和Mybatis配置文件的路径,这样就可以读取到文件,并进行解析了。

   下面就根据SpringBoot的启动流程进行解析

源码解析:

1.启动类

@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
    public static void main(String[] args) {
        //1.传入自身的class类,启动SpringBoot项目
        SpringApplication.run(WechatApplication.class, args);
    }
}

      首先将自身的class类传入进行然后进行启动,这里牵扯到SpringBoot的启动流程,就不展开讲解了,说一下主要内容,其会在WechatApplication 变成一个BeanDefinition,然后放入到BeanFactory的BeanDefinitionNames集合中,然后在Spring的refresh方法中执行调用bean工厂的后置处理器时,将遍历BeanDefinitionNames集合,然后会找到包裹WechatApplication类的BeanDefinition,此时会解析其类中的注解信息,然后就可以买到@MapperScan注解了。其参考:Spring中bean的生命周期(最详细),而解析的关键类ConfigurationClassPostProcessor可以自行百度,以后有时间会分析一下这个类的源码。

2.@MapperScan注解

     @MapperScan注解,会使用@Import+ImportBeanDefinitionRegistrar接口的方法向容器中注册一个bean,关于@Import的源码分析可以参考:Spring中@Import注解源码分析,关于@MapperScan注解可以查看下面代码,仅给出重要的属性。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

  String[] value() default {};

  String[] basePackages() default {};

  Class<?>[] basePackageClasses() default {};

}

3.MapperScannerRegistrar类

     MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以可以使用@Import来向Spring容器中导出BeanDefinition。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     //1.获取@MapperScan注解属性信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    //2.使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    //3.从@MapperScan注解属性中获取设置的值,然后设置给MapperScannerConfigurer的bd
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }

    //忽略其他设置属性的代码

    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
   
    //获取@MapperScan注解的属性值,然后添加到basePackages集合中
    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    //给BeanDefinition设置属性
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    //4.注册该MapperScannerConfigurer的bd
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
}

     上面可以总结为四步:

       1.获取@MapperScan注解上面的属性信息,因为@Import(MapperScannerRegistrar.class)是标注在@MapperScan上面的,所以第一步应该先获取@MapperScan注解的属性信息,这些属性信息使我们手动设置的,在WechatApplication类上使用@MapperScan,是这样使用的@MapperScan(value = "it.cast.wechat.mapper")。

       2.使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象,对于Spring整合Mybatis熟悉的同学应该都知道,需要在Spring的XML文件中配置MapperScannerConfigurer对象,对于SpringBoot来说,这些都给你屏蔽了,不过其实现都是一样的。

       3.将@MapperScan注解属性中的值,设置到MapperScannerConfigurer的BeanDefinition对象上面,这是我们就可以拿到了在@MapperScan设置的value等值

       4.使用builder创建BeanDefinition,并放入Spring容器中。

4.MapperScannerConfigurer类

        由上图可知其MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,我们知道BeanDefinitionRegistryPostProcessor接口是Spring中的一个扩展点,是的作用就是往Spring中注入BeanDefinition的,在Spring的refresh方法中执行调用bean工厂的后置处理器时进行执行。

        这里Spring在调用BeanDefinitionRegistryPostProcessor时,会调用三次,其调用先后顺序是这样的:

        1.如果类实现了BeanDefinitionRegistryPostProcessor,并且实现了PriorityOrdered

        2.如果类实现了BeanDefinitionRegistryPostProcessor,并且实现了Ordered

        3.如果类只实现了BeanDefinitionRegistryPostProcessor

        扫描WechatApplication类是在ConfigurationClassPostProcessor类中的postProcessBeanDefinitionRegistry方法进行的,其实现了BeanDefinitionRegistryPostProcessor,并且实现了PriorityOrdered,在扫描WechatApplication类是,会扫描其身上的注解信息,然后进行解析进行扫描类,此时扫描到@MapperScan注解,向Spring容器中注入MapperScannerConfigurer,然后当ConfigurationClassPostProcessor扫描完之后,接着会调用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法,进行扫描接口。

5.MapperScannerConfigurer类的postProcessBeanDefinitionRegistry方法

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	if (this.processPropertyPlaceHolders) {
	  processPropertyPlaceHolders();
	}
	//1.构建一个ClassPathMapperScanner,并填充相应属性
	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));
	}
	//2.注册Filter,因为上面构造函数我们没有使用默认的Filter,
        //有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
	scanner.registerFilters();
	// 3.扫描basePackage,basePackage可通过",; \t\n"来填写多个,
        // ClassPathMapperScanner重写了doScan方法
	scanner.scan(
		StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

        第2步,注册扫描器,就是制定扫描规则,具体看代码块6,在3步会使用ClassPathMapperScanner完成扫描,其中ClassPathMapperScanner重写了doScan方法,具体看代码块7

6.ClassPathMapperScanner类的registerFilters方法

public void registerFilters() {
	//是否允许扫描接口
	boolean acceptAllInterfaces = true;

	//1. 如果指定了注解,那么就将该注解加入到扫描集中
	if (this.annotationClass != null) {
	  addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
	  acceptAllInterfaces = false;
	}

	//2.如果指定了标记接口,则将标记接口添加到includeFilters,
        // 但这边重写了matchClassName方法,并返回了false,
        // 相当于忽略了标记接口上的匹配项,所以该参数目前相当于没有任何作用
	if (this.markerInterface != null) {
	  addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
		@Override
		protected boolean matchClassName(String className) {
		  return false;
		}
	  });
	  acceptAllInterfaces = false;
	}
        // 3.如果没有指定annotationClass和markerInterface,则添加默认的includeFilters,直接返回true,接受所有类
	if (acceptAllInterfaces) {
	  // default include filter that accepts all classes
	  addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
	}

	// 4.添加默认的excludeFilters,排除以package-info结尾的类
	addExcludeFilter((metadataReader, metadataReaderFactory) -> {
	  String className = metadataReader.getClassMetadata().getClassName();
	  return className.endsWith("package-info");
	});
}

       一般我们都不会执行 annotationClass 和 markerInterface的,也就是会添加默认的 Filter,相当于会接受除了 package-info 结尾的所有类。因此,basePackage 包下的类不需要使用 @Component 注解或 XML 中配置 bean 定义,也会被添加到 IoC 容器中。

7.ClassPathBeanDefinitionScanner类的scan和doScan方法

public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	//1.进行真正的扫描
	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
	//2.返回注册BeanDefinition的个数
	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	//3.调用父类去扫描指定包下面的类
	Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

	if (beanDefinitions.isEmpty()) {
	  LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
		  + "' package. Please check your configuration.");
	} else {
	  //4.对扫描到的beanDefinitions进行处理,在此处会将beanDefinition的beanClass进行替换
	  processBeanDefinitions(beanDefinitions);
	}

	return beanDefinitions;
}

        在第3步会调用父类去扫描制定包下面的类,然后返回扫描到的beanDefinitions集合,这里父类的扫描是调用Spring框架里面的类,这里就不进行分析,只需要知道会返回beanDefinitions集合,在第4步,会对返回的beanDefinitions进行处理,具体看代码块8.

8.ClassPathMapperScanner类的processBeanDefinitions方法

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
	GenericBeanDefinition definition;
	for (BeanDefinitionHolder holder : beanDefinitions) {
	  definition = (GenericBeanDefinition) holder.getBeanDefinition();
	  String beanClassName = definition.getBeanClassName();

	  // the mapper interface is the original class of the bean
	  // but, the actual class of the bean is MapperFactoryBean
	  //1.获取参数构造器的,并未其添加值,其值为beanClassName--类的全限定名
	  definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
	  //2.设置其BeanClass为mapperFactoryBeanClass为mapperFactoryBeanClass,在创建bean时,会根据BeanDefinition的BeanClass
	  //和构造方法进行创建
	  definition.setBeanClass(this.mapperFactoryBeanClass);

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

	  boolean explicitFactoryUsed = false;
	  //3. 因为sqlSessionFactoryBeanName、sqlSessionTemplateBeanName在@MapperScan中没有执行,
	  //所以下面的判断都不会进
	  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)) {
		definition.getPropertyValues().add("sqlSessionTemplate",
			new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
		explicitFactoryUsed = true;
	  } else if (this.sqlSessionTemplate != null) {
		definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
		explicitFactoryUsed = true;
	  }
	  
	  //4.如果前面的sqlSessionFactoryBeanName、sqlSessionTemplateBeanName没有指定的话,那么就
	  //设置其注入模型安装类型进行
	  if (!explicitFactoryUsed) {
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
	  }
	  definition.setLazyInit(lazyInitialization);
	}
}

      其中第2步和第3步就指定了在创建该bean时,需要使用的类和构造方法,其中第2步,指定了需要使用MapperFactoryBean的有参构造器创建,第3步将原来的类,替换成了MapperFactoryBean,使用MapperFactoryBean来创建bean。

      其中第4步就比较牛了,之前看源码的百思不得其姐,为什么MapperFactoryBean父类SqlSessionDaoSupport中的sqlSessionTemplate属性没有加@Autowired,创建的MapperFactoryBean对象就能够将sqlSessionTemplate类自动注入进来,而且我还看多看了几遍,没有在任何地方加@Autowired和直接的属性设置

      至于原因那么就是BeanDefinition中的注入模式了,Spring中指定了3种注入模式,如果你在BeanDefinition中指定了按照类型进行注入,即使你在属性上面不加任何Spring的注解,也是可以完成属性的依赖注入的,这一点以前没注意,此时,你可能有疑惑,为什么我在项目中不加@Autowired,属性就注入不进来,那是因为注解类默认使用不注入,这是你只有在属性上面加@Autowired才能完成依赖注入。

      到此,只是完成了mapper接口->BeanDefinition的转变,下面还需要完成BeanDefinition->bean的转变,因为你已经知道了MapperFactoryBean是一个FactoryBean,使用FactoryBean可以得到一个Mapper的代理对象。

9.Mapper接口的bean的创建

       在Mapper接口bean的创建时,会通过代码块8中的第2步和第三步创建一个MapperFactoryBean的实例,使用的是有参构造器。

        注意:Mapper接口bean在创建的时候,创建的是MapperFactoryBean的实例的bean,而不是一个正常的Mapper接口所代理的bean,这一点要记住,关于FactoryBean一定要先看一下:Spring中FactoryBean源码分析(最详细),这样你就知道此时创建的时候,仅仅是创建MapperFactoryBean的实例的bean,而使用的时候才回去调用MapperFactoryBean的getObject方法,使用的时候即有其他bean依赖了Mapper接口的bean,这时就会去创建使用MapperFactoryBean实例去创建bean,注意且重要。

         先看一下MapperFactoryBean的源码:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

    //实例化时,调用该构造器,在代码块8中已经讲到了
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
  
  @Override
  public boolean isSingleton() {
    return true;
  }
  
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  public boolean isAddToConfig() {
    return addToConfig;
  }
}

//SqlSessionDaoSupport抽象类
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  @SuppressWarnings("WeakerAccess")
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }

  public final SqlSessionFactory getSqlSessionFactory() {
    return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSessionTemplate = sqlSessionTemplate;
  }

  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }

  public SqlSessionTemplate getSqlSessionTemplate() {
    return this.sqlSessionTemplate;
  }

  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

       这里我想讲的是在创建MapperFactoryBean进行依赖注入时,会根据类型进行注入,此时,即使属性中没有加@Autowired注解也是需要进行属性注入的,只不过要满足几个条件:1.属性有set方法  2.不是基本类型  3.没有对属性进行手动赋值,其具体代码可以看Spring的AbstractAutowireCapableBeanFactory#populateBean ->AbstractAutowireCapableBeanFactory#autowireByType -> AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties,感兴趣的同学可以自己去查看源码,我这里就不介绍。

       对于MapperFactoryBean满足依赖注入的有sqlSessionTemplate和sqlSessionFactory,这一点我比较好奇,不是只有只有sqlSessionTemplate一个属性吗?这一点在写文章的时候才发现,以后再查找原因,反正现在知道是安装方法进行查找的,如果有知道的同学告诉我一下。

      如上图,在创建addressMapper时,查找到的需要进行属性依赖注入的确实是2个。

      此时,Spring容器会去查找个两个bean,并进行依赖注入。

10.sqlSessionFactory bean的创建

       sqlSessionFactory是一个方法bean,其会获取到Spring的application.properties的配置文件中关于mybatis的配置。关于怎么获取的可以自行,SpringBoot自动配置原理,现在查看一下sqlSessionFactory方法:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
	SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
	factory.setDataSource(dataSource);
	factory.setVfs(SpringBootVFS.class);
	//1.获取配置文件中设置的mybatis配置文件的路径
	if (StringUtils.hasText(this.properties.getConfigLocation())) {
	  factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
	}
	applyConfiguration(factory);
	//2.获取配置文件中设置的mybatis配置文件的路径
	if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
	  factory.setMapperLocations(this.properties.resolveMapperLocations());
	}
	
	//忽略其他配置的代码

	return factory.getObject();
}

@Override
public SqlSessionFactory getObject() throws Exception {
	//如果没有sqlSessionFactory,则进行创建
	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");
        //3.创建sqlSessionFactory工厂
	this.sqlSessionFactory = buildSqlSessionFactory();
}

      读取application.properties中关于mybatis设置的参数,然后执行第3步去创建SqlSessionFactory对象,具体代码看代码块11.

 11.SqlSessionFactoryBean类的buildSqlSessionFactory方法

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    final Configuration targetConfiguration;
    
    //省略部分代码
    
    //1.解析mapper文件,很重要的
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        //2.遍历所有的mapper文件
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            //3.解析mapper xml文件
            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.");
    }
    //4.使用targetConfiguration构建DefaultSqlSessionFactory,熟悉mybatis的同学应该知道,在不进行整合,只使用Mybatis的使用
    //会这个创建sqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

       构建SqlSessionFactory 的关键就是构建Configuration对象,该Configuration对应着mybatis的配置文件,其结构几乎相同,就是将xml转化为Java对象的过程,其中是会配置进行解析的,其可以参考一位大神写的:《深入理解mybatis原理》 Mybatis初始化机制详解

      其中第1步就是相当于解析mybatis的配置文件中的mappers节点,关于第3步就是解析每一个mapper文件,具体代码看代码块12.

12.XMLMapperBuilder类的parse方法

public void parse() {
	//1.判断是否已经解析过
	if (!configuration.isResourceLoaded(resource)) {
	  //2.解析mapper文件,从mapper节点开始
	  configurationElement(parser.evalNode("/mapper"));
	  //3.将resource添加到已加载列表
	  configuration.addLoadedResource(resource);
	  //4.绑定namespace的mapper
	  bindMapperForNamespace();
	}

	parsePendingResultMaps();
	parsePendingCacheRefs();
	parsePendingStatements();
}

      第2步解析mapper文件,具体看代码块13

      第4步绑定namespace的mapper,具体看代码块15

13.XMLMapperBuilder类的configurationElement方法

private void configurationElement(XNode context) {
  try {
    // 1.获取namespace属性
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    // 2.设置currentNamespace属性
    builderAssistant.setCurrentNamespace(namespace);
    // 3.解析parameterMap、resultMap、sql等节点
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    // 4.解析增删改查节点,封装成Statement
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}
 
private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  // 解析增删改查节点,封装成Statement
  buildStatementFromContext(list, null);
}
 
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    // 1.构建XMLStatementBuilder
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      // 2.解析节点
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }

      解析Mapper文件,逐步解析Mapper文件中的子节点,关键是解析select|insert|update|delete节点,然后封装Statement对象,然后使用statementParser.parseStatementNode();进行解析select|insert|update|delete节点,具体代码看代码块14,

      这边每个 XNode 都相当于如下的一个 SQL,下面封装的每个 MappedStatement 可以理解就是每个 SQL。

<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from address
    where address_id = #{addressId,jdbcType=VARCHAR}
  </select>

14: XMLStatementBuilder类的parseStatementNode方法

public void parseStatementNode() {
	
	//忽略解析过程,就是解析出select|insert|update|delete节点有的属性如id、parameterType、resultMap等属性

	//1.添加MapperStatement
	builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
			fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
			resultSetTypeEnum, flushCache, useCache, resultOrdered,
			keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

//MapperBuilderAssistant#addMappedStatement
public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    
    // 2.将id填充上namespace,例如:selectByPrimaryKey变成
    // it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey
    id = applyCurrentNamespace(id, false);
    //3.判断是否为select节点
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //4.使用参数构建MappedStatement.Builder
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    //5.构建MappedStatement
    MappedStatement statement = statementBuilder.build();
    //6.添加到MappedStatement集合中(重要)
    configuration.addMappedStatement(statement);
    return statement;
}

//Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {
    //7.添加到可以看到mappedStatements缓存中,可以看到是以id位key, MappedStatement为value进行存储
    //而id是it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey  namespace + select|insert|update|delete节点的id
    mappedStatements.put(ms.getId(), ms);
}

       这三个方法所完成的目标为:解析elect|insert|update|delete节点,构建出一个MappedStatement,然后以id为key,id例如it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey,MappedStatement为value保存到mappedStatements集合中。

15.XMLMapperBuilder类的bindMapperForNamespace方法

private void bindMapperForNamespace() {
	//1.获取当前解析的Namespace
	String namespace = builderAssistant.getCurrentNamespace();
	if (namespace != null) {
		Class<?> boundType = null;
		try {
			//2.根据namespace去加载类,可以看出来,如果namespace不是全限定类名就会找不到,
			//纳闷为啥不知道报错
			boundType = Resources.classForName(namespace);
		} catch (ClassNotFoundException e) {
			//ignore, bound type is not required
		}
		if (boundType != null) {
			if (!configuration.hasMapper(boundType)) {
			  // Spring may not know the real resource name so we set a flag
			  // to prevent loading again this resource from the mapper interface
			  // look at MapperAnnotationBuilder#loadXmlResource
			  configuration.addLoadedResource("namespace:" + namespace);
			  //3.如果不存在以该Class为key的mapper,则进行添加
			  configuration.addMapper(boundType);
			}
		}
	}
}
//Configuration#addMapper
public <T> void addMapper(Class<T> type) {
	mapperRegistry.addMapper(type);
}
//MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
	//4.判断是否为接口,不为接口,则直接忽略了
	if (type.isInterface()) {
		if (hasMapper(type)) {
			throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
		}
		boolean loadCompleted = false;
		try {
			// 将type和以该type为参数构建的MapperProxyFactory作为键值对,
			// 放到knownMappers缓存中去
			knownMappers.put(type, new MapperProxyFactory<>(type));
			// It's important that the type is added before the parser is run
			// otherwise the binding may automatically be attempted by the
			// mapper parser. If the type is already known, it won't try.
			MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
			parser.parse();
			loadCompleted = true;
	  } finally {
		if (!loadCompleted) {
			knownMappers.remove(type);
		}
	  }
	}
}

         主要是将刚刚解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。

         到此也就完成了对Mybatis的解析,然后生成一个Configuration类,放到sqlSessionFactory里面返回给代码块9中依赖sqlSessionFactory的bean,也就是MapperFactoryBean bean了。

文中总结:

         到此关于SpringBoot与Mybatis整合的源码分析已经进行一大半了,现在进行总结一下:

         代码块1-8是根据在@MapperScan中配置Mapper接口所在路径,根据Mybatis自定义的BeanDefinitionRegistryPostProcessor的实现类进行扫描接口,在扫描之后会返回BeanDefinition,此时将所有的BeanDefinition的beanClass都给替换成MapperFactoryBean.class,然后指定使用有参构造方法创建bean,并制定注入模型按照类型注入,因为要为MapperFactoryBean的父类属性sqlSessionFactory进行复制。

         代码块9-15主要讲的是在完成Mapper bean的创建工作时,会首先创建Mapper bean所依赖的sqlSessionFactory,其中大部分篇幅主要讲的就是sqlSessionFactory的创建,其创建完成就是mybatis的配置文件、mapper文件里面都已经解析过了,关键是在解析mapper文件时,会将每一个select、delete、insert、update节点都封装成一个MapperStatement对象然后保存到Configuration对象的一个Map,其中key为namespace + select、delete、insert、update节点的id,例如:it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey,  value就为select、delete、insert、update节点对应的MapperStatement对象,然后将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。

        代码块9-15主要是构建sqlSessionFactory的过程,在构建完成后,会将sqlSessionFactory返回给依赖它的Mapper bean,继续完成Mapper bean的创建,在构建完成Mapper bean之后,就可以使用。

                    

        当有Service层代码依赖Mapper bean的接口,如上图,就会去Spring 容器中查找这个bean,我们在1-15中已经完成了addressMapper bean的创建,根据之前知道addressMapper  bean是是以MapperFactoryBean类创建的,所以是一个FactoryBean的示例,在不加“&”符号的时候,获取的是FactoryBean所包裹的对象,关于FactoryBean可以参考:Spring中FactoryBean的使用

        下面就开始MapperFactoryBean的分析

16.MapperFactoryBean类的getObject方法

@Override
public T getObject() throws Exception {
	return getSqlSession().getMapper(this.mapperInterface);
}

//org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
public SqlSession getSqlSession() {
	return this.sqlSessionTemplate;
}

//org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
	return configuration.getMapper(type, this);
}

//org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	return mapperRegistry.getMapper(type, sqlSession);
}

//org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	// 1.从knownMappers缓存中获取
	final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
	if (mapperProxyFactory == null) {
		throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
	}
	try {
		// 2.新建实例
		return mapperProxyFactory.newInstance(sqlSession);
	} catch (Exception e) {
		throw new BindingException("Error getting mapper instance. Cause: " + e, e);
	}
}

//org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
	// 3.构造一个MapperProxy
	final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
	// 4.使用MapperProxy来构建实例对象
	return newInstance(mapperProxy);
}

//org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
	// 5.使用JDK动态代理来代理要创建的实例对象,InvocationHandler为mapperProxy,
    // 因此当我们真正调用时,会走到mapperProxy的invoke方法
	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
  

        通过 mapperInterface 从 knownMappers 缓存中获取到 MapperProxyFactory 对象,然后使用 JDK 动态代理创建 MapperProxyFactory 实例对象,InvocationHandler 为 MapperProxy。

        此时代码块16完成了从FactoryBean中获取真实的bean,此时也就完成了addressMapper的创建和依赖addressMapper的其他类的属性注入。

        当调用addressMapper的方法时,就会调用MapperProxy 的 invoke 方法,具体看代码块17.

17.MapperProxy类的invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //1.判断是否Object类的方法,如果是不进行操作
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {  //2.判断是否是JDK8中新增的默认方法
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //3.获取缓存的MapperMethod方法,如果没有则创建
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //4.执行mapperMethod的execute方法
    return mapperMethod.execute(sqlSession, args);
  }  
  
  //org.apache.ibatis.binding.MapperProxy#cachedMapperMethod
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method,
        k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

         MapperMethod这个类主要用来描述一个Mapper 文件中的select、update、delete、insert节点的信息、如id,此时的id是从MapperStateMent中取出的,所以格式为:it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey ,然后是节点的类型,是select、update、delete、insert的一种。

        在第4步中,会执行MapperMethod的execute方法,具体看代码块18.

18.MapperMethod类的execute方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //1.根据节点类型进行不同的操作
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        //2.以查询结果为List进行分析
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
  
  //org.apache.ibatis.binding.MapperMethod#executeForMany
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      //3.此时就进入了mybatis处理的逻辑了,到这里算是从Mapper接口道xml文件的映射了
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

        根据方法返回值和select、update、delete、insert节点的信息查找出对应的操作是插入、查询、更新还是删除操作,然后根据参数和MapperStatement的Id(command.getName()),通过sqlSession来完成数据路的操作,对于sqlSession是mybatis对Connection的封装,可以参考大神的博客:《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

       到此SpringBoot与Mybatis整合的源码算是分析完了,能坚持看下去真不容器,现在进行最后总结。

最后总结:

         在SpringBoot项目启动之后,在解析启动类上面的注解时,获取@MapperScan注解上面配置的信息,然后向Spring容器中导入一个mybatis自定义的BeanDefinitionRegistryPostProcessor的实现类进行扫描注定包下面的接口,在Spring调用bean工厂的后期处理器时,会调用这个mybatis自定义的BeanDefinitionRegistryPostProcessor的实现类,进行扫描指定包然后返回扫描到的BeanDefinition集合,此时,mybatis会对每一个BeanDefinition做一些特殊的处理,改变BeanDefinition的class对象为一个MapperFactoryBean的FactoryBean,然后指定使用有参构造方法创建bean,并制定注入模型按照类型注入,因为要为MapperFactoryBean的父类属性sqlSessionFactory进行复制。

         在完成Mapper bean的创建工作时,会首先创建Mapper bean所依赖的sqlSessionFactory,sqlSessionFactory创建完成就相当于mybatis的配置文件、mapper文件里面都已经解析过了,关键是在解析mapper文件时,会将每一个select、delete、insert、update节点都封装成一个MapperStatement对象然后保存到Configuration对象的一个Map,其中key为namespace + select、delete、insert、update节点的id,例如:it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey,  value就为select、delete、insert、update节点对应的MapperStatement对象,然后将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。

        在构建sqlSessionFactory完成后,会将sqlSessionFactory返回给依赖它的Mapper bean,继续完成Mapper bean的创建,在构建完成Mapper bean之后,就可以使用,在使用这个Mapper bean的时候,会调用其代理对象的invoke方法,然后根据此方法和Class类,去得到StatementId,然后最终会根据StatementId去查找MapperStatement对象并执行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章