苞米豆源码解析一: 动态注入

启动过程分析: 与绝大部分starter一样, 使用spring.factories作为入口

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration

简要说明动态SQL注入的流程:

  1. 先对XML进行解析, 基于原生的mybatis解析XML方式, 解析成statement并存入configuration中
  2. 根据第一步的解析可以获取当前XML的namespace,也即 mapper类判断当前Mapper接口是否继承 BaseMapper(只有继承了BaseMapper方法才需要动态注入SQL),
    然后动态注入BaseMapper中方法(有多个注入器, 文章最末尾代码段)
  3. 最后再对所有Mapper方法进行筛选, 判断方法是否使用注解动态注入SQL方式, 若使用了注解则覆盖前两步骤生成的statement(SelectProvider, InsertProvider, UpdateProvider)

配置构造

public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,
                                        ObjectProvider<Interceptor[]> interceptorsProvider, //spring注入 可以理解为@AutoWare
                                        ResourceLoader resourceLoader,
                                        ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                        ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ApplicationContext applicationContext) {
        this.properties = properties;
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
        this.applicationContext = applicationContext;
    }

初始化核心类SqlSessionFactory MybatisPlusAutoConfiguration

 //注入 SqlSessionFactory
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        //SqlSessionFactory生成
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        //...忽略若干行MybatisSqlSessionFactoryBean的属性设置

        //注入填充器 针对比较通用的字段 举例:插入数据是自动填充 valid gmt_create gmt_modify 修改数据时自动填充gmt_modify
        if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class, false,
            false).length > 0) {
            MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);
            globalConfig.setMetaObjectHandler(metaObjectHandler);
        }
        //注入主键生成器  做insert操作的时候 自动填充ID 不过由于大部分情况下主键需要用来承上启下, 不建议使用
        if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,
            false).length > 0) {
            IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);
            globalConfig.setKeyGenerator(keyGenerator);
        }
        //注入sql注入器  这个比较重要 可以在这里注入自定义的SQL注入器, 苞米豆自带一个逻辑处理器LogicSqlInjector,注入到Spring容器后,能在此处拿到
          (执行delete方法的时候 变成update逻辑字段)
        if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,
            false).length > 0) {
            //从容器中取
            ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);
            globalConfig.setSqlInjector(iSqlInjector);
        }

        //重点关注的是 SqlSessionFactory对象创建过程
        return factory.getObject();
    }

创建SqlSessionFactory对象 MybatisSqlSessionFactoryBean

@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");

        //重点关注 构建sqlSessionFactory
        this.sqlSessionFactory = buildSqlSessionFactory();
    }

    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

        Configuration configuration;

        //加载自定义 MybatisXmlConfigBuilder  较少使用 忽略
        MybatisXMLConfigBuilder xmlConfigBuilder = null;
        .......

        // 自定义枚举类扫描处理 类型转换器注册(jdbc和java转换) 较少使用 非关心重点 忽略
        ........

        // 自定义类别名
        if (!isEmpty(this.typeAliases)) {
            for (Class<?> typeAlias : this.typeAliases) {
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered type alias: ‘" + typeAlias + "‘");
                }
            }
        }

        // 重点关心 mybatis 拦截器注册, 几乎绝大部分mybatis插件都是使用拦截器方式实现, 将拦截器注册到configuration中
        if (!isEmpty(this.plugins)) {
            for (Interceptor plugin : this.plugins) {
                configuration.addInterceptor(plugin);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered plugin: ‘" + plugin + "‘");
                }
            }
        }

       //忽略其他自定义实现
       .....

        //设置spring事务管理工厂 其作用为新建Spring事务org.mybatis.spring.transaction.SpringManagedTransaction  非重点不关注
        if (this.transactionFactory == null) {
            this.transactionFactory = new SpringManagedTransactionFactory();
        }
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

        // 设置元数据相关 这里并非通过反射对Object对属性赋值 只是简单的将dataSource属性值赋给globalConfig 不要被名字误解
        GlobalConfigUtils.setMetaData(dataSource, globalConfig);
        SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration);

        // 各种sqlSessionFactory的属性赋值 忽略
        .......

        if (!isEmpty(this.mapperLocations)) {
            if (globalConfig.isRefresh()) {
                //TODO 设置自动刷新配置 减少配置
                new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
                    2, true);
            }
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }

                try {
                    // TODO  这里也换了噢噢噢噢  这句话是官方原话
                    // mapperLocation可以理解为一个mybatis的Xml文件 作用为创建xml解析器 XMLMapperBuilder
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                        configuration, mapperLocation.toString(), configuration.getSqlFragments());

                    //对xml进行解析 重点关注
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new NestedIOException("Failed to parse mapping resource: ‘" + mapperLocation + "‘", e);
                } finally {
                    ErrorContext.instance().reset();
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Parsed mapper file: ‘" + mapperLocation + "‘");
                }
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Property ‘mapperLocations‘ was not specified or no matching resources found");
            }
        }
        //返回此sqlSessionFactory
        return sqlSessionFactory;
    }

对mybatis xml进行解析

XMLMapperBuilder

//总体逻辑分为两步
  // 1 静态加载, 加载xml文件 注册xml文件中sql为 statement到 configration中
  // 2 动态加载, 判断方法是否在上一步已经注册为statement 若未注册则使用动态注册类进行 SQL动态注册statement到 configration中, 这取决于BaseMapper的基础方法数
  // 注: 由于第二步动态加载只对方法名进性判断 未对注解@Param中的参数进性容错处理 若进性自定义SQL覆盖BaseMapper中的方法,可能会导致报错
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {

      //重点关注 注册自定义xml的SQL方法
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //重点关注 动态注册xml的sql方法
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

  //解析xml
  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        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);
          //重点关注 注册mapper 此处 configuration.addMapper 被苞米豆重新实现 使用的是苞米豆的添加方法 继续往下看苞米豆的具体实现
          configuration.addMapper(boundType);
        }
      }
    }
  }

注册 mapper

MybatisPlusAutoConfiguration

@Override
    public <T> void addMapper(Class<T> type) {
        //此注册器为苞米豆的mapper注册器 原生的为MapperRegistry不需要特意关注 重点关注苞米豆注册器MybatisMapperRegistry
        mybatisMapperRegistry.addMapper(type);
    }

真正的注册类 储存mapper

MybatisMapperRegistry

@Override
    public <T> void addMapper(Class<T> type) {
        //若mapper类是接口则往下进行 若非接口也不报错 这点无法理解的
        if (type.isInterface()) {
            //判断是否已经注册了
            if (hasMapper(type)) {
                // TODO 如果之前注入 直接返回
                return;
                // throw new BindingException("Type " + type +
                // " is already known to the MybatisPlusMapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                //此处是为了防止后面同样mybatis xml使用用一个mapper作为namespace 可以不用重复创建 见hasMapper(type)方法
                knownMappers.put(type, new MapperProxyFactory<>(type));

                //终于到了终点代码 xml的解析实际是交给  MybatisMapperAnnotationBuilder来做的
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

苞米豆的动态statement从这里开始

MybatisMapperAnnotationBuilder

@Override
    public void parse() {
        //获取mapper全路径
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            //解析xml
            loadXmlResource();
            //设置当前mapper已经加载
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(type.getName());
            //缓存配置 忽略
            parseCache();
            parseCacheRef();
            //获取mapper所有方法 重点
            Method[] methods = type.getMethods();

            // TODO 注入 CURD 动态 SQL (应该在注解之前注入)  注入器见
            // 判断BaseMapper是否是当前mapper的接口或者父类 一个native方法
            if (BaseMapper.class.isAssignableFrom(type)) {

                //利用SQL注入器 根据方法名动态住处sql 相当于在xml写了一段sql, 然后解析成statemanet
                //最终实现在AutoSqlInjector的injectSql方法 直接看下一段代码
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
            for (Method method : methods) {
                try {
                    // 判断此方法是否为桥接方法 只处理非桥接方法 简单解释: 父类申明泛型但是不指定 而实现类指定具体的泛型 编译时确定了具体泛型
                    if (!method.isBridge()) {

                        //最后进行注解覆盖 举例org.apache.ibatis.annotations.SelectProvider
                        parseStatement(method);
                    }
                } catch (IncompleteElementException e) {
                    configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        parsePendingMethods();
    }
  

动态SQL注入器处理方法 AutoSqlInjector

// 以下每个自动注入器注入动态SQL之前会判断是否已人为实现在mybatis xml中 若不存在才使用动态注入器
    // 举例: 在分库分表时 若使用自动注入器则会连org_id一并修改, 此时需要人为实现updateById, 苞米豆检测要人为实现则不会进行动态注入
    protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
        /**
         * #148 表信息包含主键,注入主键相关方法
         */
        if (StringUtils.isNotEmpty(table.getKeyProperty())) {
            /** 删除 */
            this.injectDeleteByIdSql(false, mapperClass, modelClass, table);
            this.injectDeleteByIdSql(true, mapperClass, modelClass, table);
            /** 修改 */
            this.injectUpdateByIdSql(true, mapperClass, modelClass, table);
            this.injectUpdateByIdSql(false, mapperClass, modelClass, table);
            /** 查询 */
            this.injectSelectByIdSql(false, mapperClass, modelClass, table);
            this.injectSelectByIdSql(true, mapperClass, modelClass, table);
        } else {
            // 表不包含主键时 给予警告
            logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus ‘xxById‘ Method.",
                modelClass.toString()));
        }
        /**
         * 正常注入无需主键方法
         */
        /** 插入 */
        this.injectInsertOneSql(true, mapperClass, modelClass, table);
        this.injectInsertOneSql(false, mapperClass, modelClass, table);
        /** 删除 */
        this.injectDeleteSql(mapperClass, modelClass, table);
        this.injectDeleteByMapSql(mapperClass, table);
        /** 修改 */
        this.injectUpdateSql(mapperClass, modelClass, table);
        /** 修改 (自定义 set 属性) */
        this.injectUpdateForSetSql(mapperClass, modelClass, table);
        /** 查询 */
        this.injectSelectByMapSql(mapperClass, modelClass, table);
        this.injectSelectOneSql(mapperClass, modelClass, table);
        this.injectSelectCountSql(mapperClass, modelClass, table);
        this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
        this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
        this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
        this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
        this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
        /** 自定义方法 */
        this.inject(configuration, builderAssistant, mapperClass, modelClass, table);
    }

原文地址:https://www.cnblogs.com/xieyanke/p/12143501.html

时间: 2024-10-27 04:08:28

苞米豆源码解析一: 动态注入的相关文章

spring源码解析前瞻

IOC.AOP是spring的2个核心特性.理解这2个特性,有助于更好的解析源码. IOC:控制反转.把创建对象的权利交给框架,这有利于解耦. public class PageController { public String showPage(){ PageService page = new PageService(); return ""; } } 原先PageController中使用PageService,需要自己new创建对象,使用spring后,由容器创建PageSe

AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象

title: "AspNetCore3.1_Secutiry源码解析_2_Authentication_核心流程" date: 2020-03-18T21:19:15+08:00 draft: false --- 系列文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3.1_Secutiry源码解析_3_Authentication_Cookie

.NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入

作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9998021.html 写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着带着你一步一步的配置了.NET Core的开发环境并创建了一个ASP.NET Core的mvc项目,同时又通过一个实战教你如何在页面显示一个Content的列表.不知道你有没有跟着敲下代码,千万不要做眼高手低的人哦.这篇文章我们就会设计一些复杂的概念了,因为要对ASP.NET Core的启动及运行原

JDK1.8 动态代理机制及源码解析

动态代理 a) jdk 动态代理 Proxy, 核心思想:通过实现被代理类的所有接口,生成一个字节码文件后构造一个代理对象,通过持有反射构造被代理类的一个实例,再通过invoke反射调用被代理类实例的方法,来实现代理. 缺点:被代理类必须实现一个或多个接口 参考链接:http://rejoy.iteye.com/blog/1627405 源码解析:见第四部分 cglib 动态代理 核心思想:通过生成子类字节码实现,代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个

Spring IoC源码解析——Bean的创建和初始化

Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器,可以单独使用,也可以和Struts框架,MyBatis框架等组合使用. IoC介绍 IoC是什么 Ioc-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控

Android 热修复Nuwa的原理及Gradle插件源码解析

现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析. Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于hotpatch生成的gradle插件地址 https://github.com/jasonross/NuwaGradle 而Nuwa的具体实现是根据QQ空间的热修复方案来实现的.安卓App热补丁动态修复技术介绍.在阅读本篇文章之前,请先阅读该文章. 从QQ空间终端开发团队的文章中可以总结出要进行热更

Android 开源项目源码解析(第二期)

Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations 源码解析 SlidingMenu 源码解析 Cling 源码解析 BaseAdapterHelper 源码分析 Side Menu.Android 源码解析 DiscreteSeekBar 源码解析 CalendarListView 源码解析 PagerSlidingTabStrip 源码解析 公共

Android xUtils3源码解析之注解模块

xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块 二. Android xUtils3源码解析之图片模块 三. Android xUtils3源码解析之注解模块 四. Android xUtils3源码解析之数据库模块 初始化 public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { su

MapperScannerConfigurer源码解析

声明:源码基于mybatis-spring 1.3.2 前文 首先在阅读本文前需要明白整合后的使用方式以及熟悉MyBatis本身的工作原理,再者如果对于本文相关知识点不熟悉的可以参考下述文章. MyBatis与Spring整合 SqlSessionTemplate源码解析 Spring包扫描机制详解 前言 一般在项目中使用MyBatis时,都会和Spring整合一起使用,通过注入一个Mapper接口来操纵数据库.其中的原理就是使用了MyBatis-Spring的MapperScannerConf