源码梳理(3)MybatisPlus启动流程

news2024/12/26 22:12:42

文章目录

          • 1,MybatisPlus的使用示例
          • 2,BaseMapper方法的执行
            • 2,1 MybatisMapperProxy代理对象
            • 2.2 InvocationHandler接口(JDK动态代理)
            • 2.3 MapperMethodInvoker接口
            • 2.4 MybatisMapperMethod
          • 3,SqlSession的执行流程
            • 3.1 SqlSessionTemplate
            • 3.2 Proxy类中的newProxyInstance(JDK动态代理)
            • 3.3 SqlSession的创建,执行,关闭
            • 3.4 DefaultSqlSession的创建
            • 3.5 DefaultSqlSession的方法执行
        • 4,MybatisPlusAutoConfiguration自动装配
          • 4.1 SqlSessionFactory 对象的创建
            • 4.2 SqlSessionTemplate对象的创建
          • 4.3 MapperScannerConfigurer

源码版本springboot3.0.2,mybatis-plus-spring-boot3-starter3.5.5,mybatis3.5.15

1,MybatisPlus的使用示例

开始前先简单定义一个mybatisPlus的简单使用示例,为后面的分析准备
要操作表的实体类

@TableName(value = "json_type", autoResultMap = true)
@Data
@Accessors(chain = true)
public class JsonType {
    @TableId(type = IdType.AUTO)
    private Integer id;
}

mapper接口

@Mapper
public interface JsonTypeMapper extends BaseMapper<JsonType> {}

service接口

public interface JsonTypeService {
    List<JsonType> findAll();
    JsonType findOne(JsonType jsonType);
}

service实现类

@Service
public class JsonTypeServiceImpl extends ServiceImpl<JsonTypeMapper, JsonType> implements JsonTypeService {

    public List<JsonType> findAll() {
        return baseMapper.selectList(Wrappers.lambdaQuery(JsonType.class));
    }

    @Override
    public JsonType findOne(JsonType jsonType) {
        LambdaQueryWrapper<JsonType> wrapper = Wrappers.lambdaQuery(JsonType.class)
                .eq(jsonType.getId() != null, JsonType::getId, jsonType.getId())
                .eq(jsonType.getName() != null && !"".equals(jsonType.getName()), JsonType::getName, jsonType.getName());
        return this.baseMapper.selectOne(wrapper);
    }

}
2,BaseMapper方法的执行
2,1 MybatisMapperProxy代理对象

当程序启动后,在执行service方法的时候,容器中继承BaseMapper的JsonTypeMapper 实例是MybatisMapperProxy类型的代理对象
在这里插入图片描述
MybatisMapperProxy实现了InvocationHandler接口

2.2 InvocationHandler接口(JDK动态代理)

InvocationHandler 它是实现JDK动态代理的关键部分之一。它包含一个方法 invoke(),用于处理在代理对象上调用方法时的行为。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

MybatisMapperProxy在实现InvocationHandler接口后重写了invoke方法,前面的baseMapper.selectList(Wrappers.lambdaQuery(JsonType.class))就会通过invoke方法来处理

	// MybatisMapperProxy.class
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
         		// 先执行cachedInvoker方法获取到MapperMethodInvoker实例
         		// 核心环节:再执行MapperMethodInvoker实例的invoke方法
                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
2.3 MapperMethodInvoker接口

MybatisMapperProxy的invoke方法中会先执行cachedInvoker方法获取到MapperMethodInvoker实例

    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            return CollectionUtils.computeIfAbsent(methodCache, method, m -> {
            	// 判断是否是接口的default方法
                if (m.isDefault()) {
                    try {
                        if (privateLookupInMethod == null) {
                            return new DefaultMethodInvoker(getMethodHandleJava8(method));
                        } else {
                            return new DefaultMethodInvoker(getMethodHandleJava9(method));
                        }
                    } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                        | NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    return new PlainMethodInvoker(new MybatisMapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
                }
            });
        }
    }

在这里插入图片描述

在MybatisMapperProxy调用cachedInvoker方法的时候,会先获取到一个MapperMethodInvoker实例,MapperMethodInvoker接口有两种实现类型,一种是PlainMethodInvoker(用于Mybatis默认方法的调用实现),另一种是DefaultMethodInvoker(用于处理有默认实现的接口方法)

public interface BaseMapper<T> extends Mapper<T> {
	default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
        List<T> list = this.selectList(queryWrapper);
        int size = list.size();
        if (size == 1) {
            return list.get(0);
        } else if (size > 1) {
            if (throwEx) {
                throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + size);
            }
            return list.get(0);
        }
        return null;
    }

    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

例如上面BaseMapper接口中的selectList方法是没有默认实现的,而selectOne方法提供了默认实现。那selectList到底是怎么从数据库查到数据的呢,接下来就要看获取到的MapperMethodInvoker(也就是PlainMethodInvoker )实例的invoke方法都做了些什么

    private static class PlainMethodInvoker implements MapperMethodInvoker {
        private final MybatisMapperMethod mapperMethod;

        public PlainMethodInvoker(MybatisMapperMethod mapperMethod) {
            super();
            this.mapperMethod = mapperMethod;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
        	// 核心方法,执行MybatisMapperMethod的execute方法
            return mapperMethod.execute(sqlSession, args);
        }
    }
2.4 MybatisMapperMethod

在PlainMethodInvoker的invoke方法中,直接返回了MybatisMapperMethod实例执行execute方法的结果。
类中的两个属性:
MapperMethod.SqlCommand 用于表示 Mapper 方法对应的 SQL 语句信息。它包含了 SQL 语句的类型,ID(statement ID)、SQL 语句的字符串以及参数映射信息等。
MapperMethod.MethodSignature 用于表示 Mapper 方法的签名信息。它包含了方法的返回类型、参数类型以及其他相关信息。
在execute方法中经过一些判断,参数的转化之后,会将本次查询的操作交给SqlSession处理,也是MybatisMapperProxy中的SqlSession(这个SqlSession怎么来的后面会分析)

/**
 * 从  {@link MapperMethod} copy 过来 </br>
 * <p> 不要内部类 ParamMap </p>
 * <p> 不要内部类 SqlCommand </p>
 * <p> 不要内部类 MethodSignature </p>
 *
 * @author miemie
 * @since 2018-06-09
 */
public class MybatisMapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MybatisMapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                // 省略...
            }
            case UPDATE: {
                // 省略...
            }
            case DELETE: {
                 // 省略...
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                     // 省略...
                } else if (method.returnsMany()) {
                	// 我们前面调用的方法会继续执行到这里
                    result = executeForMany(sqlSession, args);
                } else {
                     // 省略...
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        // 省略...
        return result;
    }
	
	// 省略...

    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        // 将 Args 转换为 Sql 命令参数
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectList(command.getName(), param, rowBounds);
        } else {
        	// 核心环节,执行sqlSession的selectList的方法
            result = sqlSession.selectList(command.getName(), param);
        }
        // issue #510 Collections & arrays support
        // 判断method.getReturnType()的类型是否可以被result.getClass()的类型赋值
        if (!method.getReturnType().isAssignableFrom(result.getClass())) {
            if (method.getReturnType().isArray()) {
                return convertToArray(result);
            } else {
                return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            }
        }
        return result;
    }
    // 省略...

}
3,SqlSession的执行流程

SqlSession用于 MyBatis 的主要 Java 接口。通过此界面,您可以执行命令、获取映射器和管理事务
在这里插入图片描述
SqlSession有三个实现类,这里执行selectList的对象是从MybatisMapperProxy传递过来SqlSessionTemplate实例。

3.1 SqlSessionTemplate

SqlSessionTemplate用于提供对 Mapper 方法的调用和 SQL 语句的执行。
在SqlSessionTemplate的构造器中通过Proxy类的.new ProwyInstance方法又创建了一个SqlSession的代理对象,并赋值给属性sqlSessionProxy

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }
3.2 Proxy类中的newProxyInstance(JDK动态代理)

这个方法接收三个参数:
loader: 用于加载代理类的类加载器。
interfaces: 代理类需要实现的接口列表。
h: 代理类的调用处理程序,也就是拦截器。
当调用 newProxyInstance 方法时,它会返回一个代理对象,该对象实现了指定的接口列表,在调用代理对象的方法时,实际上会触发调用拦截器的 invoke 方法。

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);
        @SuppressWarnings("removal")
        final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass();
        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
        return newProxyInstance(caller, cons, h);
    }
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
3.3 SqlSession的创建,执行,关闭

所以SqlSessionTemplate中的属性sqlSessionProxy 代理对象,它的加载器使用SqlSessionFactory的类加载器,并且需要实现SqlSession接口的方法,在对象方法被调用时,通过SqlSessionInterceptor的invoke方法处理
SqlSessionInterceptor的invoke方法处理了SqlSession的创建,执行,关闭

  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 获取SqlSession对象
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 通过反射执行sqlSession的方法获取结果
        Object result = method.invoke(sqlSession, args);
        // 判断会话是不是开启事务的
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // 如果没有开启事务则强制提交
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        // 判断是否设置了异常转换器(exceptionTranslator)且捕获到的异常是PersistenceException类型
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          // 转化异常后再尝试赋值给unwrapped
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          // 关闭sqlSession
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
3.4 DefaultSqlSession的创建

这里又通过getSqlSession获取到了一个SqlSession实例,也是真正来执行接口方法(本次示例中是SqlSession的selectList方法)的是实例,先去getSqlSession方法里看看这次获取到的sqlSession又是什么类型,传入的三个参数sqlSessionFactory,executorType,exceptionTranslator都是在构建SqlSessionTemplate时赋值的,后面会分析到的。
Mybatis在同一个同一个事务中,只有存在一个sqlSession,所以这里通过sessionHolder尝试获取当前事务的sqlSession,如果没有获取到,则通过sessionFactory的openSession开启一个sqlSession

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    // 从事务同步管理器中获取一个 SqlSessionHolder 对象。SqlSessionHolder是一个包含SqlSession 实例的持有者对象,可能在事务刚开始时就已经存放在事务上下文中
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
	// 从 SqlSessionHolder 对象中取出 SqlSession 实例。如果能够取到 SqlSession 实例,则直接返回
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    // 开启一个sql会话
    session = sessionFactory.openSession(executorType);
	// 调用 registerSessionHolder() 方法将新创建的 SqlSession 实例和其他相关信息注册到事务同步管理器中。这样,在同一个事务中的其他操作就可以通过事务上下文获取到这个 SqlSession 实例。
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

DefaultSqlSessionFactory的openSession获取到DefaultSqlSession

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
      this.configuration = configuration;
    }
    // 省略...
    @Override
    public SqlSession openSession(TransactionIsolationLevel level) {
      return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
    }
    // 省略...
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
      boolean autoCommit) {
      Transaction tx = null;
      try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 通过mybatis的配置类创建一个执行器(默认是开启缓存的,所以是CachingExecutor)
        final Executor executor = configuration.newExecutor(tx, execType);
        // 将mybatis的配置,执行器,是否提交作为参数创建一个DefaultSqlSession
        return new DefaultSqlSession(configuration, executor, autoCommit);
      } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
	// 省略...
}
3.5 DefaultSqlSession的方法执行

DefaultSqlSession 是 MyBatis 框架中的一个核心类,实现了 SqlSession 接口。它用于执行 SQL 语句并与持久层交互,提供了对数据库的增删改查操作.。
在前面SqlSessionInterceptor的invoke方法里,也将具体的执行SQL语句交给了前面创建的defalutSqlSession
在这里插入图片描述

    public class DefaultSqlSession implements SqlSession {
	  @Override
      public <E> List<E> selectList(String statement, Object parameter) {
          return this.selectList(statement, parameter, RowBounds.DEFAULT);
      }
      
      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
          return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
      }
	  
	  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
      	try {
          MappedStatement ms = configuration.getMappedStatement(statement);
          dirty |= ms.isDirtySelect();
          return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
      	} catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     	} finally {
          ErrorContext.instance().reset();
      	}
      }
    }

具体的执行逻辑会交给executor,在CachingExecutor的query方法执行过程中,先尝试从mybatis的一级缓存中获取数据,如果没有获取到,再进行数据库查询,拿到结果

public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  // 省略...

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)
      throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler,
      CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

}

最后执行SqlSessionInterceptor 拦截器invoke 方法中的closeSqlSession方法,如果不存在事务则会直接关闭会话,如果存在事务不会立马关闭。
在事务结束后,在Mybatis提供SqlSessionSynchronization类的afterCompletion方法中,会关闭sqlSession。
SqlSessionSynchronization实现了TransactionSynchronization事务同步接口,重写了事务的回调方法。

  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
 		// 省略...
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
  private static final class SqlSessionSynchronization implements TransactionSynchronization {

    private final SqlSessionHolder holder;

    private final SqlSessionFactory sessionFactory;

    private boolean holderActive = true;
	// 省略...
    /**
     * {@inheritDoc}
     */
    @Override
    public void afterCompletion(int status) {
      if (this.holderActive) {
        // afterCompletion may have been called from a different thread
        // so avoid failing if there is nothing in this one
        LOGGER
            .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
        TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
        this.holderActive = false;
        LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
        this.holder.getSqlSession().close();
      }
      this.holder.reset();
    }
  }
4,MybatisPlusAutoConfiguration自动装配

前面在执行我们定义的mapper方法时,涉及到了MybatisMapperProxy,SqlSession等一系列组件。
比如在使用MybatisPlus的时候我们都会定义一些添加Mapper注解的接口(如下面的代码示例),这些接口我们不会自己去实现,它们都将在应用启动后被注册为MybatisMapperProxy代理对象并存放在容器中。当这些实例是怎么创建的呢,现在我们去MybatisPlusAutoConfiguration类里看看

@Mapper
public interface JsonTypeMapper extends BaseMapper<JsonType> {}
4.1 SqlSessionFactory 对象的创建
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
       // 省略...
    }
4.2 SqlSessionTemplate对象的创建
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
4.3 MapperScannerConfigurer

在MybatisPlusAutoConfiguration类中配置了这样一个类,它表示如果不存在MapperFactoryBean或者MapperScannerConfigurer对象,我就要把AutoConfiguredMapperScannerRegistrar导入到容器里了,并且在属性注入之后打个debug日志

    @org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
    @Import(AutoConfiguredMapperScannerRegistrar.class)
    @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
    public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

        @Override
        public void afterPropertiesSet() {
            logger.debug(
                "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
        }
    }

要被导入的AutoConfiguredMapperScannerRegistrar是MybatisPlusAutoConfiguration的内部类,它实现了ImportBeanDefinitionRegistrar接口,并且重写了registerBeanDefinitions方法。
这里要先说一下registerBeanDefinitions方法会在Spring容器初始化的早期执行,用于注册BeanDefinition,而在这个我们AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions方法中,它注册了一个类型为MapperScannerConfigurer的BeanDefinition

    public static class AutoConfiguredMapperScannerRegistrar
        implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {

        private BeanFactory beanFactory;
        private Environment environment;

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

            if (!AutoConfigurationPackages.has(this.beanFactory)) {
                logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
                return;
            }

            logger.debug("Searching for mappers annotated with @Mapper");

            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
            if (logger.isDebugEnabled()) {
                packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
            }

            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
            builder.addPropertyValue("processPropertyPlaceHolders", true);
            // 这些addPropertyValue方法都是给要注册的BeanDefinition的属性赋值,瞧,这里给annotationClass属性赋了Mapper.class
            builder.addPropertyValue("annotationClass", Mapper.class);
            builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
            BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
            Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
                .collect(Collectors.toSet());
            if (propertyNames.contains("lazyInitialization")) {
                // Need to mybatis-spring 2.0.2+
                builder.addPropertyValue("lazyInitialization", "${mybatis-plus.lazy-initialization:${mybatis.lazy-initialization:false}}");
            }
            if (propertyNames.contains("defaultScope")) {
                // Need to mybatis-spring 2.0.6+
                builder.addPropertyValue("defaultScope", "${mybatis-plus.mapper-default-scope:}");
            }

            // for spring-native
            Boolean injectSqlSession = environment.getProperty("mybatis-plus.inject-sql-session-on-mapper-scan", Boolean.class);
            if (injectSqlSession == null) {
                injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class, Boolean.TRUE);
            }
            if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
                ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
                Optional<String> sqlSessionTemplateBeanName = Optional
                    .ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
                Optional<String> sqlSessionFactoryBeanName = Optional
                    .ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
                if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) {
                    builder.addPropertyValue("sqlSessionTemplateBeanName",
                        sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));
                } else {
                    builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());
                }
            }
            builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

            registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) {
            this.beanFactory = beanFactory;
        }

        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }

        private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) {
            String[] beanNames = factory.getBeanNamesForType(type);
            return beanNames.length > 0 ? beanNames[0] : null;
        }
    }

这里的MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口重写了postProcessBeanDefinitionRegistry方法,postProcessBeanDefinitionRegistry方法也是在BeanDefinition注册到容器之前调用的,这里在postProcessBeanDefinitionRegistry方法中创建了一个ClassPathMapperScanner扫描器,这类又继承了ClassPathBeanDefinitionScanner 用于用于扫描指定路径下的类,并将其转化为BeanDefinition。
ClassPathMapperScanner对象会在执行扫描前设置annotationClass的值为Mapper.class用于对扫描结果过滤,如果在@MapperScan注解中配置扫描路径,则会扫描该路径,否则扫描springboot默认的包扫描路径。
在执行完doScan方法之后,那些带Mapper接口就已经注册为BeanDefinition了

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;
  // 省略...
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    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里有一个setMapperFactoryBeanClass的set方法,确保mapperFactoryBeanClass的属性为MapperFactoryBean

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
	  public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
           this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
      }
}

MapperFactoryBean是一个FactoryBean并且继承了SqlSessionDaoSupport ,这个类的作用是通过getObject创建代理对象,
MybatisMapperProxy

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

  private Class<T> mapperInterface;
  // 省略...
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
  // 省略...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1434579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AUTOSAR内存篇 -EEPROM Abstraction(EA)

文章目录 功能介绍一般行为寻址机制和分段地址计算擦/写次数限制“立即” 数据的处理管理块一致性信息总结本文介绍关于EEPROM Abstraction相关的内容。下图所示为内存硬件抽象层的模块架构图。 EEPROM抽象(EA)从器件特定的寻址方案和分段中抽象出来,并为上层提供虚拟寻址方…

100000行级别数据的 Excel 导入优化之路

项目中有一个 Excel 导入的需求&#xff1a;缴费记录导入 由实施 / 用户 将别的系统的数据填入我们系统中的 Excel 模板&#xff0c;应用将文件内容读取、校对、转换之后产生欠费数据、票据、票据详情并存储到数据库中。 在接手之前可能由于之前导入的数据量并不多没有对效率…

冀蒙辽三地共同推进北斗卫星导航定位基准站资源共享

冀蒙辽三地共同推进北斗卫星导航定位基准站资源共享 近期&#xff0c;冀蒙辽三地共同举办了“北斗卫星导航定位基准站资源共享推进会”&#xff0c;旨在推动北斗卫星导航定位系统的规模化应用&#xff0c;加强区域北斗卫星导航定位基准站网络的协同服务能力&#xff0c;为经济…

QT 槽函数的五种写法

前三种写法&#xff1a; 方法五&#xff1a;

clr的执行模型-笔记

学习来源&#xff1a;《CLR via C by Jeffrey Richter 》第四版&#xff0c;第1章 clr的执行模型 1.C#编译生成执行程序集文件 编译文件的组成&#xff1a;pe32/pe32头&#xff0c;clr头&#xff0c;元数据&#xff0c;IL pe32/pe32头&#xff1a;windows标准执行文件头 cl…

FPGA平台以太网学习:涉及1G/2.5G Ethernet 和Tri Mode Ethernet MAC两个IP核的学习记录(二)——IP学习使用

文章目录 一、传输速率二、网口标准选择三、核功能选择四、共享逻辑五、总结&#xff08;重点&#xff09; 学习不能稀里糊涂&#xff0c;要学会多思考&#xff0c;发散式学习以及总结&#xff1a; FPGA作为一种器件&#xff0c;只是实现目的的一种方法&#xff0c;过度追求实现…

第二十四回 王婆计啜西门庆 淫妇药鸩武大郎-Numpy索引和切片操作示例

郓哥被王婆打了&#xff0c;就去找武大郎。将情况一说&#xff0c;两人商定去抓奸。一天武大郎只做了两三扇炊饼&#xff0c;约好了时间&#xff0c;郓哥进去顶住大门不让王婆关&#xff0c;武大郎直接跑进去&#xff0c;西门庆刚开始躲到床底下&#xff0c;后被潘金莲提醒&…

Uibot (RPA设计软件)智能识别信息+微信群发助手(升级版)———课后练习1

微信群发助手机器人的小项目友友们可以参考小北的课前材料二博客~ (本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北的前两篇博客&#xff0c;友友们我们即将开展新课的学习~RPA 培训前期准备指南——安装Uibot(RPA设计软件&#x…

微信小程序(三十五)双向绑定警告去除方法

该警告的出现原因是开发者工具自身的不足&#xff0c;但在调试过程中容易刷屏&#xff0c;这里讲一下解决方法 1. 在双向绑定后面绑定一个空函数&#xff08;bind:input"emptyfn"&#xff09; <input type"text" model:value"{{keyword}}" b…

物业公司数字档案室建设要求

物业公司数字档案室建设的要求可以包括以下几个方面&#xff1a; 1. 硬件设备&#xff1a;需要配置足够的计算机、服务器、网络设备等硬件设备&#xff0c;以支持档案的数字化存储和管理。 2. 软件系统&#xff1a;需要选择专久智能档案管理软件系统&#xff0c;确保可以方便地…

基于微信小程序的校园水电费管理小程序的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

不关电脑因为懒?你们不懂程序员!

笔者作为一只老程序猿&#xff0c;确实没有关电脑的习惯。有人说不关电脑因为懒&#xff0c;那只能说这个想法too young~ 这个问题的答案可能因人而异&#xff0c;但一般来说&#xff0c;程序员不喜欢关电脑的原因可能包括以下几个方面&#xff1a; 工作需要&#xff1a;对于…

用握力器玩谷歌小恐龙游戏(三)

往期回顾 用握力器玩谷歌小恐龙游戏&#xff08;一&#xff09; 用握力器玩谷歌小恐龙游戏&#xff08;二&#xff09; GS-GAME-PC 前言 这次更新主要是&#xff0c;将原来的使用Wifi Mesh串口接收上位机的方法&#xff0c;改成了蓝牙直连电脑的方式&#xff0c;这种方式的…

Google Chrome Close AutoUpdate

DOMException: play() failed because the user didn‘t interact with the document first.-CSDN博客 html5 audio video-CSDN博客 Google Chrome Close AutoUpdate 关闭google浏览器自动更新 1&#xff1a;检查是否已安装google浏览器&#xff0c;并卸载&#xff1a; 2&…

【JS】基于node-media-server搭建流媒体服务器示例

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍基于node-media-server搭建流媒体服务器示例。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…

【Shell的运行原理以及Linux当中的权限问题】

Shell的运行原理以及Linux当中的权限问题 Shell的运行原理Linux当中的权限问题Linux权限的概念如何实现用户账号之间的切换如何仅提升当前指令的权限如何将普通用户添加到信任列表 Linux权限管理文件访问者的分类 (人)文件类型和访问权限 (事物属性)文件权限值的表示方法文件访…

少儿编程考级:智慧启迪还是智商税?

在当前科技日新月异的时代背景下&#xff0c;少儿编程教育日益受到家长和社会的广泛关注。与此同时&#xff0c;各类少儿编程考级应运而生&#xff0c;引发了公众对于其价值和意义的深度探讨。一部分人认为这是对孩子逻辑思维与创新能力的有效锻炼&#xff0c;是智慧启迪的重要…

业务拓展利器!跨境电商如何选对代理IP?IPIDEA 一键连接全球商机!

文章目录 一、跨境电商发展与海外代理IP的重要性1.1 跨境电商的发展现状1.2 海外代理IP在跨境电商中的重要性 二、选对代理IP品牌的关键因素三、IPIDEA海外IP代理的优势3.1 IPIDEA的优势3.2 IPIDEA提供的代理类型 四、使用IPIDEA爬虫实战五、总结 一、跨境电商发展与海外代理IP…

算法——二分查找算法

1. 二分算法是什么&#xff1f; 简单来说&#xff0c;"二分"指的是将查找的区间一分为二&#xff0c;通过比较目标值与中间元素的大小关系&#xff0c;确定目标值可能在哪一半区间内&#xff0c;从而缩小查找范围。这个过程不断重复&#xff0c;每次都将当前区间二分…

五、Redis之发布订阅及事务管理

5.1 发布订阅 5.1.1 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。下图展示了频道 channel1 &#xff0c;以及订阅这个频道的三个客户端 —— client1 、client2 …