在配置文件中,主要做了两步操作:构建 SqlSessionFactory、把 Mapper 接口注入到 Spring 容器管理。

  1. @Configuration
  2. @MapperScan(basePackages = "com.xxx.xxx.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
  3. public class AiActuaryPlatformDataSourceConfig {
  4. @Autowired
  5. private DataSource dataSource;
  6. @Bean(name = "sqlSessionFactory")
  7. @ConditionalOnMissingBean(SqlSessionFactory.class)
  8. public SqlSessionFactory getSqlSessionFactory() throws Exception {
  9. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  10. sqlSessionFactoryBean.setDataSource(dataSource);
  11. sqlSessionFactoryBean.setTypeAliasesPackage("com.xxx.xxx.domain");
  12. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
  13. .getResources("classpath:mapper/*mapper.xml"));
  14. org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
  15. configuration.setCacheEnabled(false);
  16. configuration.setMapUnderscoreToCamelCase(true);
  17. sqlSessionFactoryBean.setConfiguration(configuration);
  18. return sqlSessionFactoryBean.getObject();
  19. }
  20. }

下面我们详细说下 mybatis-spring 自动配置的实现原理:

构建 SqlSessionFactory

由配置文件可知,我们在构建 SqlSessionFactory 这个 Bean 的时候,会新建 SqlSessionFactoryBean,然后通过该对象实例的 getObject() 方法获取。
image.png
可以看到,SqlSessionFactoryBean 实现了三个接口,下面逐一分析其作用:

1. InitializingBean

该接口是 Bean 级生命周期接口,实现了该接口的 Bean 可以通过 afterPropertiesSet() 方法自定义 Bean 的初始化逻辑,当这个 Bean 初始时会调用该方法。

  1. public void afterPropertiesSet() throws Exception {
  2. ......
  3. this.sqlSessionFactory = buildSqlSessionFactory();
  4. }

buildSqlSessionFactory() 方法内部是构建 SqlSessionFactory 的具体逻辑,主要进行的操作是:整理加载配置信息 Configuration,注册各种别名信息、拦截器、TypeHandler,解析 mapper.xml 文件,最终通过这些配置信息利用 SqlSessionFactoryBuilder.build(Configuration config) 来创建 SqlSessionFactory 实例。

2. FactoryBean

FactoryBean 接口可以定制实例化 Bean 的逻辑,当 Bean 实现了 FactoryBean 接口时,之后通过 getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBean.getObject() 方法所返回的对象,相当于 FactoryBean.getObject() 代理了 getBean() 方法。

  1. public SqlSessionFactory getObject() throws Exception {
  2. if (this.sqlSessionFactory == null) {
  3. afterPropertiesSet();
  4. }
  5. return this.sqlSessionFactory;
  6. }

3. ApplicationListener

注册一个容器事件监听器,监听容器刷新事件,用于在容器初始化完成后检查配置信息。

  1. public void onApplicationEvent(ApplicationEvent event) {
  2. if (failFast && event instanceof ContextRefreshedEvent) {
  3. // fail-fast -> check all statements are completed
  4. this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
  5. }
  6. }

Mapper 注入 Spring

当配置好 SqlSessionFactory 后,我们就可以在需要 Mapper 接口的地方直接通过 @Autowired 或 @Resource 注解注入即可。这是因为在初始化过程中,会扫描通过 MapperScannerConfigurer 指定的 basePackage 路径或者通过 @MapperScan 注解指定的 basePackages 路径,为 mapper 接口生成代理 Bean,注入到 Spring 容器中。

1. MapperScannerConfigurer

image.png
BeanDefinitionRegistryPostProcessor 接口是对 BeanFactoryPostProcessor 接口的扩展,该接口允许在正常的 BeanFactoryPostProcessor 后置处理器开始之前注册更多的自定义 Bean。因此,在容器创建好 Bean 的定义信息(BeanDefinition)后,我们可以通过 BeanDefinitionRegistryPostProcessor 来向容器注册我们自定义的 Bean 信息,注册完后,才会调用 BeanFactoryPostProcessor 执行容器初始化的后置处理逻辑。

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  2. if (this.processPropertyPlaceHolders) {
  3. processPropertyPlaceHolders();
  4. }
  5. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  6. scanner.setAddToConfig(this.addToConfig);
  7. scanner.setAnnotationClass(this.annotationClass);
  8. scanner.setMarkerInterface(this.markerInterface);
  9. // 设置SqlSessionFactory,从该Scan器生成的Mapper最终都是受该SqlSessionFactory的管辖
  10. scanner.setSqlSessionFactory(this.sqlSessionFactory);
  11. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  12. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  13. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  14. scanner.setResourceLoader(this.applicationContext);
  15. scanner.setBeanNameGenerator(this.nameGenerator);
  16. scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  17. if (StringUtils.hasText(lazyInitialization)) {
  18. scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  19. }
  20. scanner.registerFilters();
  21. // 执行扫描动作
  22. scanner.scan(
  23. StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  24. }

1.1 ClassPathMapperScanner

ClassPathMapperScanner 执行具体的扫描逻辑,根据扫描路径得到 Bean 定义信息,scan() 方法内部会调用 doScan() 方法,doScan() 方法先调用了父类(ClassPathBeanDefinitionScanner)的方法,根据扫描的文件构建对应的 BeanDefinitionHolder 对象,然后通过 processBeanDefinitions() 方法对 BeanDefinitions 进行加工处理,加入 Mybatis 特性。

  1. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  2. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  3. if (beanDefinitions.isEmpty()) {
  4. LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
  5. + "' package. Please check your configuration.");
  6. } else {
  7. processBeanDefinitions(beanDefinitions);
  8. }
  9. return beanDefinitions;
  10. }
  11. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  12. GenericBeanDefinition definition;
  13. for (BeanDefinitionHolder holder : beanDefinitions) {
  14. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  15. ......
  16. definition.setBeanClass(this.mapperFactoryBeanClass);
  17. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  18. definition.getPropertyValues().add("sqlSessionTemplate", this.SqlSessionTemplate);
  19. }
  20. }

由上述逻辑可知,BeanDefinition 的 beanClass 设置的类为 MapperFactoryBean,即该 BeanDefinition 初始化的实例为 MapperFactoryBean,这是一个 FactoryBean 接口的实现类,它会在 getObject() 内定义实例构建的具体逻辑。同时为 MapperFactoryBean 设置了 SqlSessionFactory,以便在实例化时可以自动获取到 SqlSessionFactory。

这一步的扫描过程结束后,此时还并没有初始化 Mapper,只是创建了很多的 BeanDefinition,并且设置他们的类为 MapperFactoryBean,那我们将目光转向 MapperFactoryBean。

1.2 MapperFactoryBean

image.png
SqlSessionDaoSupport 内部维护了一个 SqlSessionTemplate 的属性,该属性是在上一步创建 MapperFactoryBean 的时候设置的,这样在实例化 Bean 时,Spring 会自动注入实例,即在实例化 Bean 时,下面方法中的一个或多个会被调用。

  1. public abstract class SqlSessionDaoSupport extends DaoSupport {
  2. private SqlSessionTemplate sqlSessionTemplate;
  3. public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  4. if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
  5. this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
  6. }
  7. }
  8. public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
  9. this.sqlSessionTemplate = sqlSessionTemplate;
  10. }
  11. }

DaoSupport 实现了 InitializingBean,在 afterPropertiesSet() 方法中会执行 checkDaoConfig() 方法,而 MapperFactoryBean 重写了该方法,因此在 MapperFactoryBean 初始化时会执行该方法。

  1. protected void checkDaoConfig() {
  2. super.checkDaoConfig();
  3. notNull(this.mapperInterface, "Property 'mapperInterface' is required");
  4. Configuration configuration = getSqlSession().getConfiguration();
  5. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
  6. configuration.addMapper(this.mapperInterface);
  7. ......
  8. }
  9. }
  10. // Configuration的addMapper方法
  11. public <T> void addMapper(Class<T> type) {
  12. mapperRegistry.addMapper(type);
  13. }

该方法首先先调用了父类的 checkDaoConfig() 方法,然后检查 mapperInterface 是否已经注册,如果已注册则抛出异常,否则调用 Configuration 来增加 Mapper。添加 Mapper 的核心类为 MapperRegistry。

1.3 MapperRegistry

image.png
addMapper

  1. public <T> void addMapper(Class<T> type) {
  2. if (type.isInterface()) {
  3. if (hasMapper(type)) {
  4. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  5. }
  6. boolean loadCompleted = false;
  7. try {
  8. knownMappers.put(type, new MapperProxyFactory<>(type));
  9. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  10. parser.parse();
  11. loadCompleted = true;
  12. } finally {
  13. if (!loadCompleted) {
  14. knownMappers.remove(type);
  15. }
  16. }
  17. }
  18. }

如果 Mapper 接口已经注册,则抛出已经绑定的异常。没有注册则为该接口注册 MapperProxyFactory,但这里只是注册其创建 MapperProxy 的工厂,并不是创建 MapperProxy。之后如果 Mapper 对应的 xml 资源未加载,则触发 xml 的绑定操作,将 xml 中的 SQL 语句与 Mapper 建立关系。该方法只是为 Mapper 创建对应的 MapperProxyFactory。

getMapper

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  3. if (mapperProxyFactory == null) {
  4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5. }
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession);
  8. } catch (Exception e) {
  9. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  10. }
  11. }

根据 Mapper 接口与 SqlSession 创建 MapperProxy 对象,一个 Mapper 接口对应一个 MapperProxy。内部通过动态代理为接口创建默认实现类。

  1. public T newInstance(SqlSession sqlSession) {
  2. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  3. return newInstance(mapperProxy);
  4. }
  5. protected T newInstance(MapperProxy<T> mapperProxy) {
  6. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  7. }

1.4 MapperProxy

image.png
MapperMethod 代表了一个个的 Mapper 方法,从 SqlCommand 可以看出,每一个 MapperMethod 都会对应一条 SQL 语句。最终我们调用代理对象的方法,会执行到 MapperProxy 的 invoke 方法:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. if (Object.class.equals(method.getDeclaringClass())) {
  3. try {
  4. return method.invoke(this, args);
  5. } catch (Throwable t) {
  6. throw ExceptionUtil.unwrapThrowable(t);
  7. }
  8. }
  9. final MapperMethod mapperMethod = cachedMapperMethod(method);
  10. return mapperMethod.execute(sqlSession, args);
  11. }

MapperMethod 的 execute 方法就是我们真正执行 SQL 语句的地方了。

2. @MapperScan 配置 mapper

@MapperScan 注解所做的工作与 MapperScannerConfigurer 是一样的,但更为简便。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(MapperScannerRegistrar.class)
  5. @Repeatable(MapperScans.class)
  6. public @interface MapperScan {
  7. ......
  8. String[] basePackages() default {};
  9. String sqlSessionFactoryRef() default "";
  10. }

@Import 注解指示要导入的一个或多个组件类,它允许导入 @Configuration 类,ImportSelector 和 ImportBeanDefinitionRegistrar 实现以及常规组件类。

2.1 Spring 动态注册 Bean

MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,该接口通常会与 @Import 注解配合使用。在 Spring 容器启动加载配置类阶段,会执行配置类注解 @Import 的逻辑,获取需要导入的 Bean,然后通过 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitions() 方法来获取所有的 BeanDefinition,内部会调用实现类的 registerBeanDefinitions() 方法完成自定义 Bean 的注册逻辑。

  1. class ConfigurationClassBeanDefinitionReader {
  2. public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
  3. ......
  4. loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
  5. }
  6. private void loadBeanDefinitionsForConfigurationClass(
  7. ......
  8. loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
  9. }
  10. // 调用实现类的registerBeanDefinitions方法完成注册逻辑
  11. private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
  12. registrars.forEach((registrar, metadata) ->
  13. registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
  14. }
  15. }

loadBeanDefinitions() 方法被 ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry 方法调用。因为 ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,所以自定义注册 bean 的逻辑会在容器初始化完成后执行。

  1. public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
  2. PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
  3. private ConfigurationClassBeanDefinitionReader reader;
  4. @Override
  5. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  6. ......
  7. processConfigBeanDefinitions(registry);
  8. }
  9. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  10. ......
  11. // 执行自定义注册bean的逻辑
  12. this.reader.loadBeanDefinitions(configClasses);
  13. }
  14. }

2.2 MapperScannerRegistrar

@MapperScan 注解通过 @Import 导入了 MapperScannerRegistrar,MapperScannerRegistrar 又实现了 ImportBeanDefinitionRegistrar,因此在 Spring 容器初始化完成后,会执行 registerBeanDefinitions 方法,实现自定义注册 bean 的逻辑。

  1. public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  2. @Override
  3. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  4. AnnotationAttributes mapperScanAttrs = AnnotationAttributes
  5. .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  6. if (mapperScanAttrs != null) {
  7. registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
  8. }
  9. }
  10. }

之后的过程就和 MapperScannerConfigurer 的逻辑一致了。