Spring整合Mybatis源码解析:@MapperScan原理(二)

文章目录


前言

上篇文章讲到MapperScannerConfigurer的postProcessBeanDefinitionRegistry()方法,本文继续深入该方法。


一、Mapper注册过程

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    //省略部分代码
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		// 重点看doScan()
		doScan(basePackages);
		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  	// 调用父类的doScan()方法,根据我们在@MapperScan中配置的路径扫描所有的Mapper并封装成BeanDefinitionHolder
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
    // 处理扫描到的bd,最关键一步
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }

到这里我们已经拿到了所有的Mapper并封装成了BeanDefinitionHolder,后面就是处理BeanDefinitionHolder最关键的一步,我们来看一下具体是怎么处理的。

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

二、processBeanDefinitions()解析过程

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      // 省略部分代码
      String beanClassName = definition.getBeanClassName();
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      definition.setBeanClass(this.mapperFactoryBeanClass);
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
   	  // 省略部分代码
    }
  }

该方法内会遍历所有的Mapper,最关键的处理有两步:
1.添加一个构造函数的参数值:beanClassName,也就是当前Mapper的全限定类名。
2.设置bd的BeanClassMapperFactoryBean.class,这里来了一波偷梁换柱,为什么这么做呢?那就必须要看看MapperFactoryBean了。


三、MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  private Class<T> mapperInterface;
  private boolean addToConfig = true;
  public MapperFactoryBean() {
    // intentionally empty
  }
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  // 省略部分代码
}

1.FactoryBean

MapperFactoryBean实现了FactoryBean,我们都知道,当调用spring的getBean()方法时,如果当前对象是FactoryBean类型的话,spring会调用他的getObject()方法。可以看到MapperFactoryBeangetObject()实际就是Mybatis的处理逻辑;

2.InitializingBean

MapperFactoryBean还继承了SqlSessionDaoSupport ,看下具体继承关系:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
public abstract class SqlSessionDaoSupport extends DaoSupport
public abstract class DaoSupport implements InitializingBean

也就是说MapperFactoryBean还是InitializingBean类型。spring在初始化对象时候,如果当前对象是InitializingBean类型,那么spring会调用其afterPropertiesSet()方法。

public abstract class DaoSupport implements InitializingBean {
	/** Logger available to subclasses. */
	protected final Log logger = LogFactory.getLog(getClass());
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// 这里是模板模式,具体处理逻辑放在子类实现里面
		checkDaoConfig();
		// 省略部分代码
	}
	// 省略部分代码
}

再来看下MapperFactoryBean的实现:

 @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

可以看到MapperFactoryBean会在这里首先尝试获取Mapper(当然取不到了),获取不到则调用configuration.addMapper(this.mapperInterface),这里面就是Mybatis的处理逻辑了。

3.this.mapperInterface

this.mapperInterface是哪里来的呢?前面提到的对扫描出来的db处理就派上用场了:

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);

看下MapperFactoryBean的构造函数:

 public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

看到这里一切的清晰了。

总结

到这里对@MapperScan的解析就完毕了,其实过程还是不复杂的,首先跟我我们配置的扫描包路径去获取所有的Mapper并封装成BeanDefinitionHolder集合,然后遍历整个集合,将Mapper的类型设置为MapperFactoryBean,利用MapperFactoryBean去完成Mybatis的操作。
以上仅是我自己的理解,如有不对的地方还请指正,共同学习进步。

上一篇:北京信息科技大学第十一届程序设计竞赛(重现赛)H


下一篇:spring-api基础操作