mybatis——Spring中的接口映射器

一、接口映射器的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 自动扫描package下bean -->
    <context:component-scan base-package="org.study.*"/>

    <!-- aop代理 -->
    <aop:aspectj-autoproxy/>

    <!-- 属性文件 -->
    <context:property-placeholder location="classpath*:db.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${mysql.driver}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
        <property name="initialSize" value="${mysql.initialSize}"/>
        <property name="minIdle" value="${mysql.minIdle}"/>
        <property name="maxActive" value="${mysql.maxActive}"/>
        <property name="maxWait" value="${mysql.maxWait}"/>
     </bean>
    <!-- mybatis的SQLSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--扫描配置sql的xml文件-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!-- mybatis的mapper接口映射器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--扫描package下的接口-->
        <property name="basePackage" value="org.study.mapper"/>
    </bean>

    <!-- 事务管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 启用声明式事务管理 支持注解@Transaction-->
    <tx:annotation-driven/>

</beans>

前文说明了:sql的xml解析和注解解析,所以省去sqlSessionFactory根据mapperLocations="classpath:mapper/*.xml "匹配对应sql.xml,然后解析。直接看接口映射器实现

二、MapperScannerConfigurer

MapperScannerConfigurer是mybatis-spring.jar中的类,不是mybatis.jar中的类,

主要目的:将mapper接口放入到SpringIOC容器中,并与sql文件映射。跟踪源码前需要要先了解SpringIOC——scan,疑问:

  • 有@Component注解的class才会生成的BeanDefinition,然后初始化到SpringIOC容器中,因此接口需要生成BeanDefinition必须重写判断方法,在哪里重写的?
  • 生成了BeanDefinition后,由于mapper.class是接口,IOC容器是怎样实例化的?

1、扫描mapper接口生成BeanDefinition实例

MapperScannerConfigurer只有两个方法

  • postProcessBeanDefinitionRegistry:扫描mapper接口生成BeanDefinition的主要实现方法。
  • processPropertyPlaceHolders:根据properties文件获取MapperScannerConfigurer的属性参数。

重点跟踪postProcessBeanDefinitionRegistry方法

   /* org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry 
    * postProcessBeanDefinitionRegistry这个方法的执行时机:
    * IOC初始化-refresh()中的⑥ invokeBeanFactoryPostProcessors(beanfactory)时执行的
    * 即方法执行时。容器(registry)已经创建并且已经执行过一次scan,这里是另外附加一次scan
    */
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) { 
      //propertis中获取basePackage参数
      //应该是适配springboot无xml方式实现
      processPropertyPlaceHolders();
    }

    //mybatis-spring.jar自实现的scanner
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    //includeFilters.add(annotationClass)
    scanner.setAnnotationClass(this.annotationClass);
    //includerFilters.add(markerInterface)
    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);
    //mapper接口的实际类型MapperFactoryBean.class
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    //最重要的实现之一:解决为什么接口没有@Component也会被扫描到
    //回忆一下spring中scan阶段includeFilters的作用:默认@Component注解的class才能被IOC扫描到并生成BeanDefinition实例(注意只是生成实例,还没有加入到容器中)
    //registerFIlters:修改了includeFilters范围
    //若配置了annotationClass:includeFilter.add(annotationClass):例如mapper接口使用@Mapper注解。
    //若配置了markerInterface:includeFilter.add(markerInterface)
    //都没有配置默认所有的class都可以生成BeanDefinition
    scanner.registerFilters();
    //解析basePackage下的class生成BeanDefinition(根据上面的includeFilters)
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

scanner.registerFilters(): includeFilters修改

/* org.mybatis.spring.mapper.ClassPathMapperScanner#registerFilters */
  public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // 配置注解,includeFilters添加注解类,basePackage下使用注解的类会被扫描到
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // 配置接口,includeFilters添加接口,basePackage下实现接口的class会被扫描到
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }
    //若没有配置注解和接口,默认扫描所有的class
    if (acceptAllInterfaces) {
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // 不扫描 *package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
  }
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)):扫描basePackage下的class
/* org.mybatis.spring.mapper.ClassPathMapperScanner#doScan */
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
   //org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 
    //前面springioc-scan()中的doScan()方法
    //ClassPathMapperScanner重写了isCandidateComponent()
    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(basePackages)时: 由于重写isCandidateComponent():接口生成的BeanDefinition也会加入到容器中。
原方法:includeFilters扫描到class文件并生成BeanDefinition实例,isCandidateComponent()会判断不是接口、抽象类,才会将BeanDefinition放入到IOC容器中
重写后:接口的BeanDefinition也会放入到IOC容器中(仅限basePackage下)
/* org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent */
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    //是接口返回true,原判断是metadata.isConcrete():!(isInterface() || isAbstract())
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

processBeanDefinitions():初始化BeanDefinition的属性:

/* org.mybatis.spring.mapper.ClassPathMapperScanner#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");

      //解决:接口为什么能实例化成一个Bean
      //原beanClassName设置为构造方法的参数
      //beanClassName = MapperFactoryBean.class
      //经过上面配置,SpringIOC容器生成接口的实例实际是MapperFactoryBean类型的 
      //最后beanName(userMapper) - singletonObject(new MapperFactoryBean(UserMapper.class))     
      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.sqlSessionFactory根据BeanName注入
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        //新增属性:definition.sqlSessionFactory直接ref注入
        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.sqlSessionTemplate根据BeanName注入
        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.sqlSessionTemplate直接ref注入
        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);
      }
      //延迟实例化  默认false
      definition.setLazyInit(lazyInitialization);
    }
  }

到这里mapper接口的BeanDefinition实例已经全部初始化完成了,接下来就是Bean实例化了。Bean实例前先猜想一下这个实例是什么样的。

    // 例如一个UserMapper.class,
    MapperFactoryBean factoryBean = new MapperFactoryBean(UserMapper.class);
    factoryBean.setSqlSessionFactory(sqlSessionFactory);
    factoryBean.setSqlSessionTemplate(sqlSessionTemplate);

2、MapperFactoryBean初始化

 SpringIOC——refresh初始化中漏掉的实现FactoryBean接口的Bean的特殊处理,除beanName= "&" + beanName,其他初始化逻辑与Bean一模一样。

这里结合MapperFactoryBean实现,发现了FactoryBean作用:提供接口实例化的机会,接口绑定技术。接口是肯定不能实例化的,但是可以实例化一个FactoryBean实例代替接口。

例如:UserMapper接口的BeanDefinition实例化出来就是一个MapperFactoryBean实例,

断点发现,确实是一个MapperFactoryBean实例,其中记录了UserMapper.class信息。

 

 接口映射器实例化已经完成了,但是实际代码一般是下面这样的

@Autowired
private UserMapper userMapper;//MapperFactoryBean没有实现UserMaper,所以依赖注入时,注入的肯定不是MapperFactoryBean实例。
...
userMapper.seletUserById(1L);

断点发现依赖注入时,userMapper确实不是MapperFactoryBean实例,而是一个代理实例

 根据FactoryBean接口,猜想可能是在依赖注入时,依赖注入的是FactoryBean.getObject()实例

public interface FactoryBean<T> {

    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    T getObject() throws Exception;
    @Nullable
    Class<?> getObjectType();
    default boolean isSingleton() {
        return true;
    }

}
最终发现Bean初始化时有一个了解不够全面的地方:org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance,
也是实现FactoryBean接口的Bean与其他类型Bean的区别所在。
① !(Bean instanceof FactoryBean) 直接返回Bean实例
②(Bean instanceof FactoryBean) 初始化时返回FactoryBean实例,其他情况下(例如DI)时返回的是FactoryBean.getObject()实例
    protected Object getObjectForBeanInstance(
            Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

        //UserMapper初始化时由于MapperFactoryBean instanceof FactoryBean,传入的name = "&userMapper"
        //依赖注入时传入的是name = "userMapper"
        if (BeanFactoryUtils.isFactoryDereference(name)) {
            //Bean instanceof FactoryBean的Bean初始化时,name = "&userMapper"直接返回FactoryBean实例
            if (beanInstance instanceof NullBean) {
                return beanInstance;
            }
            if (!(beanInstance instanceof FactoryBean)) {
                throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
            }
            if (mbd != null) {
                mbd.isFactoryBean = true;
            }
            return beanInstance;
        }

        // 没有实现FactoryBean的Bean直接返回Bean实例
        if (!(beanInstance instanceof FactoryBean)) {
            return beanInstance;
        }
        //依赖注入时name = "userMapper"
        //返回的是FactoryBean.getObject()实例,
        Object object = null;
        if (mbd != null) {
            mbd.isFactoryBean = true;
        }
        else {
            object = getCachedObjectForFactoryBean(beanName);
        }
        if (object == null) {
            // Return bean instance from factory.
            FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
            // Caches object obtained from FactoryBean if it is a singleton.
            if (mbd == null && containsBeanDefinition(beanName)) {
                mbd = getMergedLocalBeanDefinition(beanName);
            }
            boolean synthetic = (mbd != null && mbd.isSynthetic());
            object = getObjectFromFactoryBean(factory, beanName, !synthetic);
        }
        return object;
    }

到这里初始化就剩下一个MapperFactoryBean.getObject():先小结一下:

① SpringIOC容器中userMapper是MapperFactoryBean类型的

② SpringIOC容器中userService中userMapper是MapperFactoryBean.getObject()类型的(UserMapper.class)

3、MapperFactoryBean.getObject()

/* org.mybatis.spring.mapper.MapperFactoryBean#getObject */
  public T getObject() throws Exception {
    //这里sqlSession是SqlSessionTemplate类型的
    return getSqlSession().getMapper(this.mapperInterface);
  }

/* org.mybatis.spring.SqlSessionTemplate#getMapper */
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

/* 
 * 下面就是mybatis.jar中的逻辑了,也就回到了前文的逻辑了
 */

/* org.apache.ibatis.session.Configuration#getMapper */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

/* org.apache.ibatis.binding.MapperRegistry#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);
    }
  }

/* org.apache.ibatis.binding.MapperProxyFactory*/
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

 

posted on 2020-03-08 23:22  FFStayF  阅读(572)  评论(0编辑  收藏  举报