Mybatis源码深度解析

        今天聊聊Mybatis源码,本博客是以 mybatis-3.5.6版本为例讲解。众所周知,MyBatis是一个ORM框架,用于操作数据库,给我们开发带来了很大的便利。它可以单独使用,也可以与Spring框架整合使用。目前主流的当然是和Spring框架整合,将Mybatis的bean交由Spring管理,可以很方便的使用Spring的事务。可能现在很多公司会使用Mybatis-Plus或者TK-Mybatis,在我看来,这两种框架是对Mybatis框架的增强,如果了解Mybatis的底层,再去看那两个框架的底层原理,想必也不是一件难事!话说回来,由于是跟Spring框架做整合,所以需要对Spring框架比较熟悉,Spring框架原理的讲解,请移步到这里:Spring源码深度解析(上)

        话不多说,开始今天的正题。首先,我们要引入相关依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mybatis_parent</artifactId>
        <groupId>com.szl</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>mybatis_test</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.18.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.18.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.18.2-GA</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>
    </dependencies>

</project>

        然后创建一个配置类,名为 DataSourceConfiguration,代码如下:

package com.szl.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

import javax.sql.DataSource;

/**
 * @ClassName DataSourceConfiguration.java
 * @Author lizhao
 * @Description TODO
 * @Version 1.0
 * @CreateTime 2021年09月17日 23:06:00
 */
@Configuration
public class DataSourceConfiguration {

    @Primary
    @Bean(name = "dataSource")
    public DataSource dataSource() throws Exception {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC" +
                "&characterEncoding=utf8&useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true");
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return druidDataSource;
    }

    @Bean(name = "pageInterceptor")
    public Interceptor interceptor() {
        PageInterceptor pageInterceptor = new PageInterceptor();
        return pageInterceptor;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("dataSource") DataSource dataSource,
                                                       @Qualifier("pageInterceptor") Interceptor interceptor) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setPlugins(interceptor);
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    public TransactionManagementConfigurer transactionManagementConfigurer(@Qualifier("dataSource") DataSource dataSource) {
        TransactionManagementConfigurer transactionManagementConfigurer = new TransactionManagementConfigurer() {
            @Override
            public PlatformTransactionManager annotationDrivenTransactionManager() {
                return new DataSourceTransactionManager(dataSource);
            }
        };
        return transactionManagementConfigurer;
    }

}

        该配置类中,创建了一些bean:

① DataSource,用于连接数据库(这里使用的是Druid数据库连接池);

② PageInterceptor,拦截器插件,用于分页查询;

③ SqlSessionFactoryBean,跟Spring整合的关键,属于mybatis-spring-2.0.5.jar中的类;

④ TransactionManagementConfigurer,Spring框架事务相关。

        然后创建了一个App类,可以认为它是程序入口,代码如下:

package com.szl;

import com.github.pagehelper.PageInfo;
import com.szl.pojo.PcMetaFlowStepDef;
import com.szl.service.PcMetaFlowStepDefService;
import org.apache.ibatis.logging.LogFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * @ClassName App.java
 * @Author lizhao
 * @Description TODO
 * @Version 1.0
 * @CreateTime 2021年09月19日 03:09:00
 */
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.szl"})
@MapperScan(basePackages = "com.szl.mapper")
public class App {

    private static final AnnotationConfigApplicationContext applicationContext;

    static {
        // 开启Mybatis日志控制台打印。
        LogFactory.useStdOutLogging();
        applicationContext = new AnnotationConfigApplicationContext(App.class);
    }

    public static void main(String[] args) {
        update(1L, "测试...");
    }

    private static void update(Long id, String descr) {
        TransactionSynchronizationManager.initSynchronization();
        PcMetaFlowStepDefService pcMetaFlowStepDefService = applicationContext.getBean(PcMetaFlowStepDefService.class);
        pcMetaFlowStepDefService.update(id, descr);
    }

    private static void findByPageTest() {
        PcMetaFlowStepDefService pcMetaFlowStepDefService = applicationContext.getBean(PcMetaFlowStepDefService.class);
        PageInfo<PcMetaFlowStepDef> pageInfo1 = pcMetaFlowStepDefService.findByPage(1, 10);
        System.out.println(pageInfo1);
    }
}

        项目的层级结构为:

        

        针对App类,该类被三个注解修饰,分别是:

① @EnableTransactionManagement,用于开启事务,它的作用是往Spring容器中注册了一个后置处理器,用于处理 @Transactional注解;

② @ComponentScan注解,用于包扫描;

③ @MapperScan注解,用于Mybatis的包扫描,因为涉及到接口的注册,对于Spring框架而言,接口是不会被放入Spring的IOC容器的。 

        这里,重点聊聊@MapperScan注解,代码如下:

该注解可以设置很多参数,用到再讲,用得最多的是,String[] value()、String[] basePackages(),这两个参数是等价的,都是用于设置要扫描的包。当然,最核心的还是 @MapperScan注解上的@Import注解,对Spring熟悉的话,就知道这个注解会将MapperScannerRegistrar注册到Spring容器中。看看这个类,代码如下:

        由于该类实现了ImportBeanDefinitionRegistrar,最终Spring会调用ImportBeanDefinitionRegistrar#registerBeanDefinitions()方法,在该方法中可以获取到@MapperScan注解上的相关信息,然后调用MapperScannerRegistrar#registerBeanDefinitions()方法,很明显该方法就是处理@MapperScan注解,看看该方法,代码如下:

 void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
                               BeanDefinitionRegistry registry, String beanName) {
    // 创建一个MapperScannerConfigurer的BeanDefinitionBuilder对象。
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // 属性填充(主要是将@MapperScan注解上的一些配置解析出来,最后Spring在初始化MapperScannerConfigurer对象的时候的,
    // 会将之前配置的属性值将会填充到该对象中)。
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    Class<? extends Annotation> 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<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> 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<String> basePackages = new ArrayList<>();
    basePackages.addAll(
      Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    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()));

    if (basePackages.isEmpty()) {
      // 如果basePackages为空,则加载默认包路径,即App所在包 => "com.szl"。
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
      builder.addPropertyValue("defaultScope", defaultScope);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    // 属性设置完毕,将BeanDefinition对象注册到IOC容器中。
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

        可以知道,在该方法中,会先创建一个BeanDefinitionBuilder,设置beanClass属性为 MapperScannerConfigurer.class,并且获取配置的包信息,如"com.szl",通过BeanDefinitionBuilder#addPropertyValue()方法,进行属性的设置。最终,调用BeanDefinitionRegistry#registerBeanDefinition()方法,将BeanDefinitionBuilder注册到Spring容器中。再看看,当然,最终在Spring容器中存在的是 MapperScannerConfigurer对象,看看该类,代码如下:

/**
 * Copyright 2010-2020 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mybatis.spring.mapper;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Optional;

import static org.springframework.util.Assert.notNull;

/**
 * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
 * registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
 * concrete classes will be ignored.
 * <p>
 * This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
 * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
 * details.
 * <p>
 * The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
 * <p>
 * This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
 * {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
 * specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
 * match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given
 * {@code basePackage} are added as mappers.
 * <p>
 * This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
 * proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
 * in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
 * {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names
 * are used rather than actual objects because Spring does not initialize property placeholders until after this class
 * is processed.
 * <p>
 * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
 * actual object creation until later in the startup process, after all placeholder substitution is completed. However,
 * note that this configurer does support property placeholders of its <em>own</em> properties. The
 * <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.
 * <p>
 * Configuration sample:
 *
 * <pre class="code">
 * {@code
 *   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 *       <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
 *       <!-- optional unless there are multiple session factories defined -->
 *       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
 *   </bean>
 * }
 * </pre>
 *
 * @author Hunter Presnall
 * @author Eduardo Macarron
 * @see MapperFactoryBean
 * @see ClassPathMapperScanner
 */
public class MapperScannerConfigurer
  implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

  private String basePackage;

  private boolean addToConfig = true;

  private String lazyInitialization;

  private SqlSessionFactory sqlSessionFactory;

  private SqlSessionTemplate sqlSessionTemplate;

  private String sqlSessionFactoryBeanName;

  private String sqlSessionTemplateBeanName;

  private Class<? extends Annotation> annotationClass;

  private Class<?> markerInterface;

  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;

  private ApplicationContext applicationContext;

  private String beanName;

  private boolean processPropertyPlaceHolders;

  private BeanNameGenerator nameGenerator;

  private String defaultScope;

  /**
   * This property lets you set the base package for your mapper interface files.
   * <p>
   * You can set more than one package by using a semicolon or comma as a separator.
   * <p>
   * Mappers will be searched for recursively starting in the specified package(s).
   *
   * @param basePackage base package name
   */
  public void setBasePackage(String basePackage) {
    this.basePackage = basePackage;
  }

  /**
   * Same as {@code MapperFactoryBean#setAddToConfig(boolean)}.
   *
   * @param addToConfig a flag that whether add mapper to MyBatis or not
   * @see MapperFactoryBean#setAddToConfig(boolean)
   */
  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  /**
   * Set whether enable lazy initialization for mapper bean.
   * <p>
   * Default is {@code false}.
   * </p>
   *
   * @param lazyInitialization Set the @{code true} to enable
   * @since 2.0.2
   */
  public void setLazyInitialization(String lazyInitialization) {
    this.lazyInitialization = lazyInitialization;
  }

  /**
   * This property specifies the annotation that the scanner will search for.
   * <p>
   * The scanner will register all interfaces in the base package that also have the specified annotation.
   * <p>
   * Note this can be combined with markerInterface.
   *
   * @param annotationClass annotation class
   */
  public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
    this.annotationClass = annotationClass;
  }

  /**
   * This property specifies the parent that the scanner will search for.
   * <p>
   * The scanner will register all interfaces in the base package that also have the specified interface class as a
   * parent.
   * <p>
   * Note this can be combined with annotationClass.
   *
   * @param superClass parent class
   */
  public void setMarkerInterface(Class<?> superClass) {
    this.markerInterface = superClass;
  }

  /**
   * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
   * Usually this is only needed when you have more than one datasource.
   * <p>
   *
   * @param sqlSessionTemplate a template of SqlSession
   * @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead
   */
  @Deprecated
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSessionTemplate = sqlSessionTemplate;
  }

  /**
   * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
   * Usually this is only needed when you have more than one datasource.
   * <p>
   * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
   * it is too early to build mybatis object instances.
   *
   * @param sqlSessionTemplateName Bean name of the {@code SqlSessionTemplate}
   * @since 1.1.0
   */
  public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) {
    this.sqlSessionTemplateBeanName = sqlSessionTemplateName;
  }

  /**
   * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
   * Usually this is only needed when you have more than one datasource.
   * <p>
   *
   * @param sqlSessionFactory a factory of SqlSession
   * @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
   */
  @Deprecated
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
  }

  /**
   * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context.
   * Usually this is only needed when you have more than one datasource.
   * <p>
   * Note bean names are used, not bean references. This is because the scanner loads early during the start process and
   * it is too early to build mybatis object instances.
   *
   * @param sqlSessionFactoryName Bean name of the {@code SqlSessionFactory}
   * @since 1.1.0
   */
  public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
    this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
  }

  /**
   * Specifies a flag that whether execute a property placeholder processing or not.
   * <p>
   * The default is {@literal false}. This means that a property placeholder processing does not execute.
   *
   * @param processPropertyPlaceHolders a flag that whether execute a property placeholder processing or not
   * @since 1.1.1
   */
  public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
    this.processPropertyPlaceHolders = processPropertyPlaceHolders;
  }

  /**
   * The class of the {@link MapperFactoryBean} to return a mybatis proxy as spring bean.
   *
   * @param mapperFactoryBeanClass The class of the MapperFactoryBean
   * @since 2.0.1
   */
  public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
    this.mapperFactoryBeanClass = mapperFactoryBeanClass;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setBeanName(String name) {
    this.beanName = name;
  }

  /**
   * Gets beanNameGenerator to be used while running the scanner.
   *
   * @return the beanNameGenerator BeanNameGenerator that has been configured
   * @since 1.2.0
   */
  public BeanNameGenerator getNameGenerator() {
    return nameGenerator;
  }

  /**
   * Sets beanNameGenerator to be used while running the scanner.
   *
   * @param nameGenerator the beanNameGenerator to set
   * @since 1.2.0
   */
  public void setNameGenerator(BeanNameGenerator nameGenerator) {
    this.nameGenerator = nameGenerator;
  }

  /**
   * Sets the default scope of scanned mappers.
   * <p>
   * Default is {@code null} (equiv to singleton).
   * </p>
   *
   * @param defaultScope the default scope
   * @since 2.0.6
   */
  public void setDefaultScope(String defaultScope) {
    this.defaultScope = defaultScope;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    // 检查basePackage属性,不能为空。
    notNull(this.basePackage, "Property 'basePackage' is required");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // left intentionally blank
  }

  /**
   * {@inheritDoc}
   * 由于当前类实现了BeanDefinitionRegistryPostProcessor接口,Spring会自动调用此方法。
   *
   * @since 1.0.2
   */
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    // 创建ClassPathMapperScanner对象,并进行属性填充。
    // ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner类,
    // 这是Spring使用的包扫描类,他重写了扫描过滤规则。
    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));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    // 注册扫描包的过滤规则。
    scanner.registerFilters();
    // 开始包扫描。
    scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

  /*
   * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
   * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
   * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
   * definition. Then update the values.
   */
  private void processPropertyPlaceHolders() {
    Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
      false, false);

    if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
      BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
        .getBeanDefinition(beanName);

      // PropertyResourceConfigurer does not expose any methods to explicitly perform
      // property placeholder substitution. Instead, create a BeanFactory that just
      // contains this mapper scanner and post process the factory.
      DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      factory.registerBeanDefinition(beanName, mapperScannerBean);

      for (PropertyResourceConfigurer prc : prcs.values()) {
        prc.postProcessBeanFactory(factory);
      }

      PropertyValues values = mapperScannerBean.getPropertyValues();

      this.basePackage = getPropertyValue("basePackage", values);
      this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values);
      this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values);
      this.lazyInitialization = getPropertyValue("lazyInitialization", values);
      this.defaultScope = getPropertyValue("defaultScope", values);
    }
    this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
      .map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
      .map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
      .orElse(null);
    this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null);
  }

  private Environment getEnvironment() {
    return this.applicationContext.getEnvironment();
  }

  private String getPropertyValue(String propertyName, PropertyValues values) {
    PropertyValue property = values.getPropertyValue(propertyName);

    if (property == null) {
      return null;
    }

    Object value = property.getValue();

    if (value == null) {
      return null;
    } else if (value instanceof String) {
      return value.toString();
    } else if (value instanceof TypedStringValue) {
      return ((TypedStringValue) value).getValue();
    } else {
      return null;
    }
  }

}

        可以知道,该类实现了BeanDefinitionRegistryPostProcessor接口,最终会调用BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()方法,也就是MapperScannerConfigurer的该方法,看看该方法,代码如下:

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    // 创建ClassPathMapperScanner对象,并进行属性填充。
    // ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner类,
    // 这是Spring使用的包扫描类,他重写了扫描过滤规则。
    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));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    // 注册扫描包的过滤规则。
    scanner.registerFilters();
    // 开始包扫描。
    scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

        在该方法中,会创建ClassPathMapperScanner类,调用有参构造,传入BeanDefinitionRegistry对象,并且,该类继承自Spring的ClassPathBeanDefinitionScanner类,作用当然就是包扫描了。然后就是进行属性设置,这些属性值都是来源于@MapperScan注解上的属性值了。这里有一点最重要,就是调用了ClassPathMapperScanner#registerFilters()方法,该方法就是设置包过滤的规则,看看过滤规则是怎样的,代码如下:

 public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    if (this.annotationClass != null) {
      // 添加注解的过滤规则,即mapper接口添加了 annotationClass 注解,才会被扫描到。
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // override AssignableTypeFilter to ignore matches on the actual marker interface
    if (this.markerInterface != null) {
      // 添加接口的过滤规则,即mapper接口继承了 markerInterface 接口,才会被扫描到。
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }

    if (acceptAllInterfaces) {
      // 如果以上两种都没有设置,默认只要是接口就行。
      // default include filter that accepts all classes
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
  }

        可知,如果设置了@MapperScan注解的Class<? extends Annotation> annotationClass()、Class<?> markerInterface(),都会添加AnnotationTypeFilter对象(用于包扫描的过滤),由于我只设置了basePackages,也就是包名,因此前面两个if代码块都不会进去,那两个属性是干嘛的,上面代码中的注解解释很详细,这里不多少了。然后就会进入最后一个if代码块,传入的λ表达式直接返回的是true,因此接口也可以被扫描到。回到ClassPathMapperScanner#registerFilters()方法,下一行就是调用ClassPathMapperScanner#scan()方法,传入包名,即"com.szl",代码如下:

        最终调用ClassPathMapperScanner#processBeanDefinitions()方法,代码如下:

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      boolean scopedProxy = false;
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
        definition = (AbstractBeanDefinition) Optional
          .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
          .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
            "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
        scopedProxy = true;
      }
      // 获取原始接口的类的全限定名。
      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
      // 设置有参构造的值,在创建bean的时候,就会调用有参构造,传入设置的 beanClassName 作为值创建bean。
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      // 给definition设置新的beanClass。
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // 创建mapperFactoryBeanClass的时候,进行属性值填充,值就是现在设置的这些...
      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      // Attribute for MockitoPostProcessor
      // https://github.com/mybatis/spring-boot-starter/issues/475
      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

      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) {
        // 如果 sqlSessionFactory、sqlSessionFactoryBeanName、sqlSessionTemplate、sqlSessionTemplateBeanName
        // 都不存在,则设置注入类型为 ByType,在实例化该对象的时候,Spring会通过类型自动装配,注入属性。
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }

      definition.setLazyInit(lazyInitialization);

      if (scopedProxy) {
        continue;
      }

      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }

      if (!definition.isSingleton()) {
        BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
          registry.removeBeanDefinition(proxyHolder.getBeanName());
        }
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
      }

    }
  }

        这里就是处理扫描到的所有Mapper接口,该项目只有一个Mapper,即PcMetaFlowStepDefMapper,就被扫锚到了,因此接下来就是怎么处理这个接口了,也就是上面的代码。这里,我只说关键的一点:获取到 BeanDefinition,获取beanClassName,即获取原始接口的类的全限定名,调用 BeanDefinition#getConstructorArgumentValues()#addGenericArgumentValue(beanClassName),后面,会通过构造方法的形式,将beanClassName赋值给某个属性,然后给BeanDefinition设置新的beanClass属性,为 MapperFactoryBean.class,因为接口是无法实例化的,即便接口可以被扫描到,如果不做处理,在实例化的时候还是会报错。其他的属性设置,比较重要的就是设置BeanDefinition的autowireMode属性为 AbstractBeanDefinition.AUTOWIRE_BY_TYPE,也就是使用Spring自己的依赖注入。即Spring会找到所有的Set方法,通过类型进行属性的依赖注入。最后还是调用BeanDefinitionRegistry#registerBeanDefinition()方法,将该BeanDefinition注册到Spring容器中,只不过在实例化的时候,创建的不是Mapper接口,而是MapperFactoryBean对象。再看看 MapperFactoryBean类,很明显它实现了FactoryBean接口,在获取MapperFactoryBean对象的时候,获取的实际上是 MapperFactoryBean#getObject()方法返回的对象。由于MapperFactoryBean还实现了InitializingBean,因此需要先看看 MapperFactoryBean#afterPropertiesSet()方法。类的层级为:

对于实例化方法,需要优先看看,代码如下:

        先看看MapperFactoryBean#getSqlSession()方法:

        可知是它父类的方法,而父类中sqlSessionTemplate又是拿来的呢?看看是在哪里赋值的就知道了,代码如下:

这里注释讲的很清楚,由于前面给MapperFactoryBean所对应的BeanDefinition对象的autowireMode属性为 AbstractBeanDefinition.AUTOWIRE_BY_TYPE,因此会自动调用SqlSessionDaoSupport#setSqlSessionFactory()方法,进行属性注入。而入参的SqlSessionFactory对象又是哪里来的呢??还记得前面在DataSourceConfiguration配置类中,定义了一个SqlSessionFactoryBean对象吗?实际上就是它,由于它也实现了FactoryBean接口,因此准确的说,应该是SqlSessionFactoryBean#getobject()方法返回的对象,看看该方法,代码如下:

        看看SqlSessionFactoryBean#buildSqlSessionFactory()方法,代码如下(截取部分):

可以知道,最终返回的SqlSessionFactory就是DefaultSqlSessionFactory对象。回到SqlSessionDaoSupport#setSqlSessionFactory()方法,其实传入的SqlSessionFactory,也就是DefaultSqlSessionFactory,由于该方法中,调用了SqlSessionDaoSupport#createSqlSessionTemplate(sqlSessionFactory),代码如下:

        创建的对象是SqlSessionTemplate,调用的是有参构造,传入的是DefaultSqlSessionFactory对象。看看该有参构造,代码如下:

        这里最核心的就是,通过JDK的动态代理,给SqlSessionTemplate的sqlSessionProxy进行了赋值。其他的不用看,直接看SqlSessionInterceptor#invoke()方法就好了,代码如下:

        可知,在该方法中,就是先获取了SqlSession对象,通过反射,调用SqlSession中的某些方法,先记住这里,后面会说到。ok,回到MapperFactoryBean#checkDaoConfig()方法,代码如下:

        就是获取SqlSession对象,也就是SqlSessionTemplate对象,最终就是从DefaultSqlSessionFactory对象中,获取到Configuration对象,然后调用Configuration#addMapper()方法,传入Mqpper接口,即项目中的PcMetaFlowStepDefMapper.class。看看该方法,代码如下:

        可知,会创建 MapperProxyFactory对象,调用有参构造,传入Mapper接口,并且将其放入

knownMappers中,key为Mapper接口,value为 MapperProxyFactory对象。再构建MapperAnnotationBuilder对象,调用MapperAnnotationBuilder#parse()方法,解析Mapper接口中是否有以下注解:@SelectProvider、@ResultMap、@Select、@Update、@Insert、@Delete,有则处理,代码如下:

        到这里为止,扫描到的Mapper接口被成功加入到了Spring容器,只不过真正的生成的对象是MapperFactoryBean罢了,并且对mapper接口中的注解扫描也完成的,被上述注解修饰的方法,会生成一个MappedStatement,缓存起来,想要获取MappedStatement,通过Mapper接口的全限定名+"."+方法名就可以获取到,如果Mapper接口中有两个重载方法会报错!

        这时候,我们可以想一个场景,假设在一个Service类中,通过@Autowired依赖了这个Mapper接口,由于这个接口在Spring容器中是对应的bean是MapperFactoryBean,因此在真正进行注入的时候,注入的Mapper对象是 MapperFactoryBean#getobject()方法返回的对象,代码如下:

        可知,最终生成的Mapper就是通过JDK生成的代理对象,而这个代理对象那个,传入的InvocationHandler,就是MapperProxy,MapperProxy#invoke()方法即可,代码如下:

public Object execute(SqlSession sqlSession, Object[] args) {
    // 判断 command 的类型。
    Object result;
    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;
        } 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;
  }

        这里以查询为例,查询的方法当然是被@Select注解所修饰了,执行的应该是这里的代码:

真正调用具体方法的实惠,会先判断查询方法的返回值,返回的是返回的是Map呢?还是List呢?这里以List为例,则调用的是MapperMethod#executeForMany()方法了,代码如下:

        核心就是调用SqlSession#selectList()方法,而这里的SqlSession当然就是SqlSessionTemplate了,该方法的代码如下:

        这里的sqlSessionProxy对象我在前面讲过了,他是通过JDK的动态代理生成的代理对象,因此只需要看,SqlSessionInterceptor#invoke()方法即可,代码如下:

        这里实际上是通过反射,调用 DefaultSqlSession#selectList(),方法如下:

        到这里为止,其实Mybatis的核心调用逻辑就讲完了。剩下的内容,请移步到这里:Mybatis整合Spring事务原理&数据库连接池原理讲解
        

        

  • 31
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值