Spring 中 Mybatis 的 Mapper 层的实例是怎么来的?

流程简单说明

  1. @MapperScan引入MapperScannerRegistrar注册一个MapperScannerConfigurerBeanDefinition对象
  2. MapperScannerConfigurer会扫描指定包的Mapper接口类,为Mapper接口类构建MapperFactoryBean
    BeanDefinition对象
  3. 当spring 初始化MapperFactoryBeanBean对象时,会调用FactoryBean # getObject() 方法,该方法会生成对应Mapper接口的org.apache.ibatis.binding.MapperProxy代理对象,spring会将该代理对象作为Bean对象存放到缓存中,所以我们才可以通过@Autowire引入。

@MapperScan

@MapperScan 注解中,引入了一个 MapperScannerRegistrar 注册器

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    
    
	...
}

MapperScannerRegistrar

MapperScannerRegistrar 注册器 是一个 ImportBeanDefinitionRegistrar 实现类,用于注册 一个 MapperScannerConfigurerBeanDefinition 对象,并将@MapperScan中的配置信息添加到该BeanDefinitionPropertyValues

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
    
	@Override
 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
   AnnotationAttributes mapperScanAttrs = AnnotationAttributes
       .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
   if (mapperScanAttrs != null) {
    
    
     registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
         generateBaseBeanName(importingClassMetadata, 0));
   }
 }
  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
     BeanDefinitionRegistry registry, String beanName) {
    
    
   BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
   //将@MapperScan中的配置信息添加到该BeanDefinition的PropertyValues中
   ...
   registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
 }
}

它就相当于以前的xml配置:

 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
       <!-- optional unless there are multiple session factories defined -->
       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
   </bean>

MapperScannerConfigurer

MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor 实现类,主要用于注册 MapperFactoryBean 的BeanDefinition对象.

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    
    
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    
    
	...
    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));
  }
}

void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法会新建一个 ClassPathMapperScanner 对象,通过 ClassPathMapperScanner 对象,除了将@MapperScan中的配置信息配置进去以外,通过 ClassPathMapperScanner # scan(String... basePackages) 方法进行扫描指定包的Mapper 层接口类 并注册 MapperFactoryBeanBeanDefinition 对象。
scan(String... basePackages)ClassPathMapperScanner 的父类ClassPathBeanDefinitionScanner方法

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
    
    
/**
	 * Perform a scan within the specified base packages.
	 * @param basePackages the packages to check for annotated classes
	 * @return number of beans registered
	 */
	public int scan(String... basePackages) {
    
    
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
    
    
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
/**
	 * 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());
				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);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
}

ClassPathMapperScanner 重写了 ClassPathBeanDefinitionScanner # doScan(String ... basePackages) 方法,再通过父级方法扫描完指定包名得到 BeanDefinitionHolder 列表后,对这个 BeanDefinitionHolder 进行处理

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    
    
	@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;
  }
  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
      definition.setBeanClass(this.mapperFactoryBeanClass);

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

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
    
    
        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.");
        }
        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(Set<BeanDefinitionHolder> beanDefinitions)中,遍历 beanDefinitions 的所有BeanDefinitionHolder 对象,取出 BeanDefinitionHolder 对象的 BeanDefinition 对象,修改 BeanDefinition 对象的 beanClass 属性为MapperFactoryBean.class,并增加了构造方法参数对象beanClassName,beanClassNameBeanDefinition 对象中原来的 beanClass的类名,原来的beanClass其实 Mapper 层的接口类。
这里其实相当于以前xml配置:

  <bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  </bean>

  <bean id="oneMapper" parent="baseMapper">
    <property name="mapperInterface" value="my.package.MyMapperInterface" />
  </bean>

  <bean id="anotherMapper" parent="baseMapper">
    <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" />
  </bean>

MapperFactoryBean

MapperFactoryBean 是一个 FactoryBean,它只有一个接收一个Class<?> 对象的构造方法。该Class<?>对象对应 MapperFactoryBeanmapperInterface 属性,该属性就是 Mapper 层的Mapper接口类。

SqlSessionDaoSupport

MapperFactoryBean 继承了 SqlSessionDaoSupport , spring 会对SqlSessionDaoSupport自动装配SqlSessionFactory,然后SqlSessionDaoSupport会使用SqlSessionFactory创建出SqlSessionTemplate对象。

public abstract class SqlSessionDaoSupport extends DaoSupport {
    
    

  private SqlSessionTemplate sqlSessionTemplate;

  /**
   * Set MyBatis SqlSessionFactory to be used by this DAO. Will automatically create SqlSessionTemplate for the given
   * SqlSessionFactory.
   *
   * @param sqlSessionFactory
   *          a factory of SqlSession
   */
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    
    
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
    
    
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  /**
   * Create a SqlSessionTemplate for the given SqlSessionFactory. Only invoked if populating the DAO with a
   * SqlSessionFactory reference!
   * <p>
   * Can be overridden in subclasses to provide a SqlSessionTemplate instance with different configuration, or a custom
   * SqlSessionTemplate subclass.
   * 
   * @param sqlSessionFactory
   *          the MyBatis SqlSessionFactory to create a SqlSessionTemplate for
   * @return the new SqlSessionTemplate instance
   * @see #setSqlSessionFactory
   */
  @SuppressWarnings("WeakerAccess")
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    
    
    return new SqlSessionTemplate(sqlSessionFactory);
  }

  /**
   * Return the MyBatis SqlSessionFactory used by this DAO.
   *
   * @return a factory of SqlSession
   */
  public final SqlSessionFactory getSqlSessionFactory() {
    
    
    return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  }

  /**
   * Set the SqlSessionTemplate for this DAO explicitly, as an alternative to specifying a SqlSessionFactory.
   *
   * @param sqlSessionTemplate
   *          a template of SqlSession
   * @see #setSqlSessionFactory
   */
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    
    
    this.sqlSessionTemplate = sqlSessionTemplate;
  }

  /**
   * Users should use this method to get a SqlSession to call its statement methods This is SqlSession is managed by
   * spring. Users should not commit/rollback/close it because it will be automatically done.
   *
   * @return Spring managed thread safe SqlSession
   */
  public SqlSession getSqlSession() {
    
    
    return this.sqlSessionTemplate;
  }

  /**
   * Return the SqlSessionTemplate for this DAO, pre-initialized with the SessionFactory or set explicitly.
   * <p>
   * <b>Note: The returned SqlSessionTemplate is a shared instance.</b> You may introspect its configuration, but not
   * modify the configuration (other than from within an {@link #initDao} implementation). Consider creating a custom
   * SqlSessionTemplate instance via {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case you're
   * allowed to customize the settings on the resulting instance.
   *
   * @return a template of SqlSession
   */
  public SqlSessionTemplate getSqlSessionTemplate() {
    
    
    return this.sqlSessionTemplate;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    
    
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

DaoSupport

除此之外, SqlSessionDaoSupport还继承了DaoSupport,而 DaoSupport 实现 InitializingBean接口,在Spring 初始化MapperFactoryBean对象,会触发 DaoSupport # afterPropertiesSet() 方法,从而回到DaoSupport # checkDaoConfig() 方法,在DaoSupport # checkDaoConfig()中:

  1. SqlSessionSupport中会检查sqlSessionTemplate对象是否为null,如果为null说明SqlSessionFactory未被装配尽量,这将会导致抛出异常。
  2. MapperFactoryBean会检查sqlSessionTemplate所维护的Configuration对象【Configuration就相当于Mybatis的注册中心,里面包含所有Mybatis的配置信息,Mapper层的配置信息/元数据等】中有没有注册过mapperInterface的Mapper层,没有就会将mapperInterface注册进去。
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);
		}
	}

	/**
	 * 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
	 */
	protected abstract void checkDaoConfig() throws IllegalArgumentException;

	/**
	 * 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
	 */
	protected void initDao() throws Exception {
    
    
	}

}

MapperFactoryBean # getObject()

MapperFactoryBean被Spring初始化后,就会调用MapperFactoryBean # getObject() 方法,该方法会使用sqlSessionTemplate构建出mapperInterface的代理对象。Spring会FactoryBean # getObject()返回的对象封装成Bean对象,所以我们可以通过 @Autowire自动装配进来。
该代理对象就是 org.apache.ibatis.binding.MapperProxy对象,当调用接口方法时,MapperProxy会从Configuration中获取与之对应的MapperStatement对象进行执行。

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;
 }

 /**
  * {@inheritDoc}
  */
 @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();
     }
   }
 }

 /**
  * {@inheritDoc}
  */
 @Override
 public T getObject() throws Exception {
    
    
   return getSqlSession().getMapper(this.mapperInterface);
 }

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

 /**
  * {@inheritDoc}
  */
 @Override
 public boolean isSingleton() {
    
    
   return true;
 }

 // ------------- mutators --------------

 /**
  * Sets the mapper interface of the MyBatis mapper
  *
  * @param mapperInterface
  *          class of the interface
  */
 public void setMapperInterface(Class<T> mapperInterface) {
    
    
   this.mapperInterface = mapperInterface;
 }

 /**
  * Return the mapper interface of the MyBatis mapper
  *
  * @return class of the interface
  */
 public Class<T> getMapperInterface() {
    
    
   return mapperInterface;
 }

 /**
  * If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in
  * mybatis-config.xml.
  * <p>
  * If it is true, the mapper will be added to MyBatis in the case it is not already registered.
  * <p>
  * By default addToConfig is true.
  *
  * @param addToConfig
  *          a flag that whether add mapper to MyBatis or not
  */
 public void setAddToConfig(boolean addToConfig) {
    
    
   this.addToConfig = addToConfig;
 }

 /**
  * Return the flag for addition into MyBatis config.
  *
  * @return true if the mapper will be added to MyBatis in the case it is not already registered.
  */
 public boolean isAddToConfig() {
    
    
   return addToConfig;
 }
}

可能会有读者疑问,为什么在 MapperScannerConfigurer 中所生成的 MapperFactoryBeanBeanDefnition 对象所配置的构造函数参数是一个String类型的 Mapper 层接口类名。但是 MapperFactoryBean 是只接受Class<?>对象,为啥还能成功构建出 MapperFactoryBean 对象 呢?
因为 Spring 可以自动匹配对应依赖类型,尽可能对能转换成依赖类型的对象进行转换。具体代码情况 AbstractAutowireCapableBeanFactory # createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) 方法,该方法会使用ConstructResolver找到最匹配的构造函数方法,然后通过TypeConverter尽可能地转换构造函数参数为依赖类型。温馨提示:MapperFactoryBeanBeanDefinition对象的构造函数参数值通过TypeConverter进行转换成Class<?>其实是通过 PropertyEditorRegistry的默认内置PropertyEditorClassEditor进行转换的。

猜你喜欢

转载自blog.csdn.net/qq_30321211/article/details/108605484