MyabtisPlus(6):启动阶段

950 阅读5分钟

MyabtisPlus在启动阶段主要完成读取配置文件,解析xml文件,生成单表CURD的SQL,实例化Mapper等操作。

本篇含有大量SpringIoC的知识,对于不明白的记住结论即可。

关于druid数据库连接池,点击此处详细说明。

DataSource

yml配置:
    datasource:
      url: jdbc:mysql://localhost:3306/bbs?useUnicode=true&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai
      username: root
      password: root
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      druid:
        # druid连接池属性
        ......
        

实例化任意mapper类的时候,实例化的是MapperFactoryBean,其BeanDefinition的autowireMode属性为AUTOWIRE_BY_TYPE,需要实例化父类SqlSessionDaoSupport下的SqlSessionTemplateSqlSessionFactory(关于为何Mapper类实例化的是MapperFactoryBean,可以点击此处:@MapperScan的作用)

查看SqlSessionFactory的BeanDefinition,得知其定义类为MybatisPlusAutoConfiguration,它有如下注解:

image.png 类MybatisPlusAutoConfiguration注解

SqlSessionFactorySqlSessionFactoryBean由Mybatis的jar包引入,@ConditionalOnSingleCandidate(DataSource.class)是条件注解,获取IoC容器中实现DataSource的bean,只有一个或多个时只有一个@Primary修饰的bean才算符合 @EnableConfigurationProperties(MybatisPlusProperties.class)将配置文件前缀为mybatis-plus的属性绑定到MybatisPlusProperties对象,@AutoConfigureAfter(DataSourceAutoConfiguration.class)说明它在DataSourceAutoConfiguration之后加载。

于是查看DataSourceAutoConfiguration,它有如下注解:

image.png 类DataSourceAutoConfiguratio注解

@EnableConfigurationProperties(DataSourceProperties.class)将配置文件前缀为spring.datasource的属性绑定到DataSourceProperties对象。

查看SqlSessionFactory,它的条件如下:

image.png MybatisPlusAutoConfiguration.SqlSessionFactory注解

@ConditionalOnMissingBean说明我们没有配置SqlSessionFactory的时候,才会实例化。它的构造方法需要实例化过的DataSource类。

因为我们引入了druid的jar包,所以会自动加载DruidDataSourceAutoConfigure类,它定义了数据源的类型为DruidDataSourceWrapper,@Bean(initMethod = "init")在初始化的时候将会执行init()方法,点开它,发现它将配置文件前缀为spring.datasource.druid的属性绑定到自身。因为实现了InitializingBean接口,所以会执行afterPropertiesSet方法,将DataSourceProperties相关属性绑定到自身。

image.png DruidDataSourceAutoConfigure.DataSource注解

DruidDataSource->init():
    createAndLogThread();
    // 创建一个守护线程,用于数据库资源连接
    createAndStartCreatorThread();
    // 创建一个守护线程,用于数据库资源回收
    createAndStartDestroyThread();
    // 等待上面两个线程执行countDown()方法
    initedLatch.await();
    // 省略处没有出错的话,将会在控制台打印{dataSource-1} inited
    ......

createAndStartCreatorThread()方法调用后,启动CreateConnectionThread线程,它用于MySQL连接,默认属性第一次进入empty.await()阻塞住,等待其他线程调用empty.signal()唤醒。主线程走完,DruidDataSourceWrapper实例化完成,实质上没有数据库连接。

DruidDataSource->CreateConnectionThread->run():
    initedLatch.countDown();
    for (;;) {
        // 必须存在线程等待,才创建连接
        if (poolingCount >= notEmptyWaitThreadCount 
                && !(keepAlive && activeCount + poolingCount < minIdle)) {
            // 走到这里,直接阻塞了
            empty.await();
        }
    }

SqlSessionFactory与SqlSessionTemplate

DataSource实例化完毕,开始实例化SqlSessionFactory

MybatisPlusAutoConfiguration->sqlSessionFactory():
    MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
    // 设置数据源、配置类、xml文件、globalConfig等信息
    // 如果使用了实现了自定义的注入填充器、主键生成器、sql注入器,则使用自定义的
    ......
    // 创建SqlSessionFactory
    return factory.getObject();
MybatisSqlSessionFactoryBean->getObject():
    if (this.sqlSessionFactory == null) {
        afterPropertiesSet();
    }
    return this.sqlSessionFactory;
MybatisSqlSessionFactoryBean->afterPropertiesSet():
    this.sqlSessionFactory = buildSqlSessionFactory();
MybatisSqlSessionFactoryBean->buildSqlSessionFactory():
    // 遍历xml文件
    for (Resource mapperLocation : this.mapperLocations) {
        try {
            // 读取文件流
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            // 首先解析xml里<mapper></mapper>标签里的内容,比如<select></select>、<update></update>等标签内容
            // 然后解析mapper.java里使用四大注解标记的sql语句
            // 最后使用sql注入器注入默认语句
            xmlMapperBuilder.parse();   // --1
        }
    }
    ......
    // 发现sqlSessionFactory就是DefaultSqlSessionFactory,它封装了configuration对象
    final SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(targetConfiguration);
    if (globalConfig.isBanner()) {
        System.out.println(" _ _   |_  _ _|_. ___ _ |    _ ");
        System.out.println("| | |\\/|_)(_| | |_\\  |_)||_|_\\ ");
        System.out.println("     /               |         ");
        System.out.println("                        " + MybatisPlusVersion.getVersion() + " ");
    }
    return sqlSessionFactory;
XMLMapperBuilder->parse():  // --1
    if (!configuration.isResourceLoaded(resource)) {
        // 解析xml中<mapper></mapper>里的内容,将结果封装成MappedStatement赋到configuration的mappedStatements变量
        configurationElement(parser.evalNode("/mapper")); 
        // 将resource放进configuration的loadedResources中
        configuration.addLoadedResource(resource);
        // 解析mapper 
        bindMapperForNamespace();   
    }
XMLMapperBuilder->bindMapperForNamespace():
    // xml文件中<mapper>标签的namespace属性
    String namespace = builderAssistant.getCurrentNamespace();
    Class<?> boundType = null;
    try {
        // 使用反射加载
        boundType = Resources.classForName(namespace);
    }
    // boundType是xml对应的mapper类
    // configuration的mybatisMapperRegistry的knownMappers是否有mapper类,没有的话,会加入并且解析
    if (!configuration.hasMapper(boundType)) {
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
    }
MybatisConfiguration->addMapper():
    mybatisMapperRegistry.addMapper(type);
MybatisMapperRegistry->addMapper():
    // knownMappers再一次判断,如果之前注入,直接返回
    ......
    parser.parse();
MybatisMapperAnnotationBuilder->parse():
    // 解析xml,因为之前解析过了,这里跳过
    loadXmlResource();
    ......
    Method[] methods = type.getMethods();
    for (Method method : methods) {
        // 遍历mapper类里的方法,如果有注解,将解析的信息封装成MappedStatement,并赋到configuration的mappedStatements变量
        if (!method.isBridge()) {
            // 只有@Select、@Insert、@Update、@Delete才会解析
            parseStatement(method);
            SqlParserHelper.initSqlParserInfoCache(typeName, method);
        }
    }
   // 如果这个类是Mapper类的子类的话,会使用Sql注入器注入,这里选择的是默认的Sql注入器
   if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
       GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
   }

AbstractSqlInjector->inspectInject():
    Class<?> modelClass = extractModelClass(mapperClass);
    // DefaultSqlInjector注入器加入了selectById,insert,deleteById等方法
    List<AbstractMethod> methodList = this.getMethodList(mapperClass);
    if (CollectionUtils.isNotEmpty(methodList)) {
        // tableInfo记录了bean的反射类、表名、主键填充类型、主键字段、字段与数据库对应字段等信息
        // fieldList是除了主键字段的其他字段
        // 使用了相当多的注解,如@TableName、@TableId、@TableField、@TableLogic等
        TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);  
        // 循环注入自定义方法,主要是将已经定义好的sql语句模板根据TableInfo拼接成sql语句
        methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
    }

SqlSessionFactory实例化完毕,开始实例化SqlSessionTemplate。

定义方法位于MybatisPlusAutoConfiguration->sqlSessionTemplate(),最终执行构造方法。

SqlSessionTemplate->SqlSessionTemplate():
    // 可见,当执行SqlSession的方法时,会进入到SqlSessionInterceptor.invoke()方法中
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());

MapperFactoryBean

依赖注入完毕后,在初始化的过程中,它的父类SqlSessionDaoSupport的父类DaoSupport实现了InitializingBean接口。

DaoSupport->afterPropertiesSet():
    checkDaoConfig();
MapperFactoryBean->checkDaoConfig():
    // 实例化SqlSessionFactory的时候,如果有xml,会顺便添加java类和自动注入的SQL语句
    // 如果java类没有对应的.xml文件,在这边仍然会注入
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        configuration.addMapper(this.mapperInterface);
    }

MapperFactoryBean是FactoryBean的实现类,所以实例化完毕后,会调用getObject()方法。

MapperFactoryBean->getObject():
    return getSqlSession().getMapper(this.mapperInterface);
SqlSessionTemplate->getMapper():
    return getConfiguration().getMapper(type, this);
MybatisConfiguration->getMapper():
    return mybatisMapperRegistry.getMapper(type, sqlSession);
MybatisMapperRegistry->getMapper():
    return mapperProxyFactory.newInstance(sqlSession);
MybatisMapperProxyFactory->newInstance():
    final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
MybatisMapperProxyFactory->newInstance():
    // mapperProxy固定为MybatisMapperProxy
    // 可见,实例化Mapper方法,返回的是动态代理,执行mapper方法时,会进入MybatisMapperProxy.invoke()方法中
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);