Mybatis源码分析(一)MapperProxy 的初始化

2020/6/12 17:26:50

本文主要是介绍Mybatis源码分析(一)MapperProxy 的初始化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、前言

我们用Spring整合mybatis的时候一定见过这两个注解

  • @Mapper 使用在mapper接口上,将接口托管给Spring管理。
  • @MapperScan 用来开启包扫描,扫描项目某路径下的Mapper接口。

1.1 @MapperScan

@MapperScan 无疑更方便,让我们来看下它做了什么事情?

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
复制代码
/**
 * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using
 * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing
 * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
 *
 * @author Michael Lanyon
 * @author Eduardo Macarron
 * @author Putthiphong Boonphong
 *
 * @see MapperFactoryBean
 * @see ClassPathMapperScanner
 * @since 1.2.0
 */
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  /**
   * {@inheritDoc}
   * 
   * @deprecated Since 2.0.2, this method not used never.
   */
  @Override
  @Deprecated
  public void setResourceLoader(ResourceLoader resourceLoader) {
    // NOP
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取@MapperScan注解上的属性
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

复制代码

  MapperScan@Import了一个类MapperScannerRegistrar.class,它实现了ImportBeanDefinitionRegistrar接口,也就是说MapperScannerRegistrar拥有了向Spring注册Bean的能力。

  Spring在执行ConfigurationClassParserdoProcessConfigurationClass()方法的时候会用getImports()扫描@Import的类。在processImports()MapperScannerRegistrar会被放到importBeanDefinitionRegistrars列表中。后面就可以被ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass()方法加载到了。

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
复制代码
……
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
						ParserStrategyUtils.invokeAwareMethods(
								registrar, this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
复制代码

来看下Spring加载MapperScannerRegistrar回调的registerBeanDefinitions方法

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }
   ……
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());


  }
复制代码

BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class) 构建了一个MapperScannerConfigurer.class类型的BeanDefinition(Spring中的bean),此外给它设置了几个属性:processPropertyPlaceHoldersannotationClassbasePackageannotationClass就是要扫描的Mapper的默认的注解类,basePackage就是要扫描的包的根路径。

MapperScannerConfigurer.class,Spring整合Mybatis的核心类 它的作用是扫描项目中的Dao类,将其创建为Mybatis的Mapper对象也就是MapperProxy。这个我们一会儿会详细讲解。

1.2 @Mapper

SpringBoot整合Mybatis的时候,官方文档里提到Springboot可以自动扫描Mapper类

  • Auto-scan your mappers, link them to the SqlSessionTemplate and register them to Spring context so they can be injected into your beans

我们对@Mapper find usage 可以发现MybatisAutoConfiguration这个类有使用到

进入MybatisAutoConfiguration可以看到MybatisAutoConfiguration实现了InitializingBean接口,那么就必然会执行afterPropertiesSet() 方法,我们在看下具体的实现

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
复制代码
 /**
   * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
   * mappers based on the same component-scanning path as Spring Boot itself.
   */
  @org.springframework.context.annotation.Configuration
  @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.");
    }

  }
复制代码

@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) 不用我多说了吧,我们在看下如果没有这两个类 Import的AutoConfiguredMapperScannerRegistrar.class,和MapperScannerRegistrar.class 有异曲同工之妙。但是我还是建议大家使用@MapperScan,省事!

 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     ……
      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      builder.addPropertyValue("annotationClass", Mapper.class);
      builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
      BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
      Stream.of(beanWrapper.getPropertyDescriptors())
          // Need to mybatis-spring 2.0.2+
          .filter(x -> x.getName().equals("lazyInitialization")).findAny()
          .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }
复制代码

二、MapperScannerConfigurer

MapperScannerConfigurer类图
从MapperScannerConfigurer的类图我们可以了解它的实现情况

  • BeanNameAware:bean在创建的时候,会将bean的名称通过setBeanName()设置在bean中,这样外部程序就可以通过getBeanName()获取到bean的名称。
  • ApplicationContextAware:任意实现ApplicationContextAware的子类在被创建的时候,Spring会将ApplicationContext对象自动注入到当前bean中。
  • InitializingBean:InitializingBean接口为bean提供了属性初始化后的处理方法,它只包括 afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
  • BeanDefinitionRegistryPostProcessor:对标准BeanFactoryPostProcessorSPI的扩展,允许在常规BeanFactoryPostProcessor检测开始之前注册更多的bean定义。通常在我们想自定义BeanDefinition用到。MapperScannerConfigurer主要实现了扫描特定包并创建Mapper对象,并交给Spring管理。
  • BeanFactoryPostProcessor:BeanFactory的后置处理器,BeanFactoryPostProcessor在spring容器加载了bean的定义文件之后,在bean实例化之前,可以在postProcessBeanFactory()中根据需要对BeanDefinition进行修改,例如可以把bean的scope从singleton改为prototype。

2.1 BeanDefinitionRegistryPostProcessor

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,因此Spring容器会在invokeBeanDefinitionRegistryPostProcessors()回调postProcessBeanDefinitionRegistry()方法。

  @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));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
复制代码
  • 这里设置sqlSessionFactorysqlSessionTemplateBeanName,后续生成的Mapper自然会受到他们的管理
  • 调用了ClassPathMapperScanner的scan()方法

2.2 ClassPathMapperScanner

public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

复制代码
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
复制代码
  • super.doScan()调用父类的doScan方法,解析配置文件并构建对应的BeanDefinitionHolder对象。
  • processBeanDefinitions() 增加BeanDefinitions定义,加入Mybatis特性

2.2.1 doScan

/**
	 * Perform a scan within the specified base packages,
	 * returning the registered bean definitions.
	 * <p>This method does <i>not</i> register an annotation config processor
	 * but rather leaves this up to the caller.
	 * @param basePackages the packages to check for annotated classes
	 * @return set of beans registered if any for tooling registration purposes (never {@code null})
	 */
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
		//       扫描候选组件的类路径
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				            //生成bean名称
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					//注册bean定义
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
复制代码

在指定的基础包中执行扫描,返回已注册的bean定义。MapperScanner执行之前,doscan在processConfigBeanDefinitions()也会被执行到用来做Config的扫描。

可以参考spring 扫描BeanDefinition详解

2.2.2 processBeanDefinitions

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
     // 这里就设置了生成的Mappe的类是mapperFactoryBeanClass = MapperFactoryBean.class;

      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      //将SqlSessionFactory放入MapperFactoryBean属性中,在实例化时可以自动获取到该SqlSessionFactory。
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        //如果sqlSessionTemplate不为空,则放入到属性中,以便Spring在实例化MapperFactoryBean时可以得到对应的SqlSessionTemplate。
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }
复制代码

processBeanDefinitions()方法设置了BeanDefinition类为MapperFactoryBean,在Spring中我们可以通过FactoryBean对象的getObject()方法获得构建的实例。另外在MapperFactoryBean属性中还设置了SqlSessionFactorySqlSessionTemplate(sqlSessionTemplate不为空)。

注意: 这里只是修改了BeanDefinition,Mapper还并未初始化。

三、 MapperFactoryBean

MapperFactoryBean

3.1 DaoSupport

/**
 * Generic base class for DAOs, defining template methods for DAO initialization.
 *
 * <p>Extended by Spring's specific DAO support classes, such as:
 * JdbcDaoSupport, JdoDaoSupport, etc.
 *
 * @author Juergen Hoeller
 * @since 1.2.2
 * @see org.springframework.jdbc.core.support.JdbcDaoSupport
 */
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 {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}
复制代码

通过文档注释可以知道,DaoSupport定义了初始化的模版方法

  • checkDaoConfig()检查或构建dao的配置信息
/**
	 * Abstract subclasses must override this to check their configuration.
	 * <p>Implementors should be marked as {@code final} if concrete subclasses
	 * are not supposed to override this template method themselves.
	 * @throws IllegalArgumentException in case of illegal configuration
	 */
复制代码
  • initDao()初始化Dao相关的方法
/**
	 * Concrete subclasses can override this for custom initialization behavior.
	 * Gets called after population of this instance's bean properties.
	 * @throws Exception if DAO initialization fails
	 * (will be rethrown as a BeanInitializationException)
	 * @see org.springframework.beans.factory.BeanInitializationException
	 */
复制代码

3.2 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();
      }
    }
  }
复制代码
  • mapperInterface 就只刚才扫描到的mapper接口
  • 如果已经没有注册过,就调用configuration.addMapper()把它加在configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
……
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
复制代码

我们来看下核心类MapperRegistry

3.3 MapperRegistry

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
复制代码
  • 这里的Configuration是Mybatis的全局配置对象。包含各种xml或者注解解析的配置。
  • knownMappers放的是MapperMapperProxyFactory表示Mapper是否已经被添加。

MapperRegistry中两个重要的方法addMappergetMapper。addMapper为*Mapper创建对应的MapperProxyFactory映射关系,getMapper顾名思义就是获取MapperProxyFactory对象

3.3.1 addMapper

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
复制代码
  • 已注册的Mapper会抛出异常
  • Mapper创建MapperProxyFactory对象并放在map里
  • 如果Mapper对应的xml文件未加载,触发xml的绑定操作,将xml中的sql语句与Mapper建立关系。在后边会详细介绍。

3.3.2 getMapper

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
复制代码
  • 根据类型从knownMappers获取相对应的MapperProxyFactory对象
  • newInstance(sqlSession)创建MapperProxy对象 观察getMapper的堆栈调用情况不难发现,在扫描Mapper注入的时候回去调用MapperFactoryBeangetObject()方法生成Mapper的代理对象。MapperProxy的创建到这里就结束了。MapperProxy与XMl文件或注解中的sql语句建立关联,我们后面继续讨论。


这篇关于Mybatis源码分析(一)MapperProxy 的初始化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程