苞米豆源码解析一: 动态注入
Posted xieyanke
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了苞米豆源码解析一: 动态注入相关的知识,希望对你有一定的参考价值。
启动过程分析: 与绝大部分starter一样, 使用spring.factories作为入口
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration
简要说明动态SQL注入的流程:
- 先对XML进行解析, 基于原生的mybatis解析XML方式, 解析成statement并存入configuration中
- 根据第一步的解析可以获取当前XML的namespace,也即 mapper类判断当前Mapper接口是否继承 BaseMapper(只有继承了BaseMapper方法才需要动态注入SQL),
然后动态注入BaseMapper中方法(有多个注入器, 文章最末尾代码段) - 最后再对所有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); }
以上是关于苞米豆源码解析一: 动态注入的主要内容,如果未能解决你的问题,请参考以下文章
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段