spring、mybatis整合源码简单分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

配置

    <bean id="localDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <property name="url" value="jdbc:mysql://192.168.31.14:3366/lios?characterEncoding=utf8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
            ...
        </bean>
        <!-- 创建SqlSessionFactory,同时指定数据源-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="localDataSource"/>
            <property name="configLocation" value="classpath:sqlmap-config.xml"/>
            <property name="mapperLocations">
                <list>
                   <value>classpath*:com/lios/mybatis/mapper/*.xml</value>
                </list>
            </property>
        </bean>
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.lios.mybatis.dao"/>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>

MapperScannerConfigurer这个bean有什么作用呢,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,该接口可以让我们实现自定义并注册bean,具体可以参考关于BeanDefinitionRegistryPostProcessor接口使用的文章,无疑分析入口还是从org.springframework.context.support.AbstractApplicationContext#refresh方法开始.

分析

扫描basePackages,封装MapperFactoryBean,注册到spring容器

AbstractApplicationContext类的refresh方法里,会调用:

    invokeBeanFactoryPostProcessors(beanFactory);

调用BeanFactory的后置处理器,向容器中注册自定义Bean,一直跟到PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法中这段代码:

    // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
    boolean reiterate = true;
    while (reiterate) {
        reiterate = false;
        postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        for (String ppName : postProcessorNames) {
            if (!processedBeans.contains(ppName)) {
                //getBean方法会初始化MapperScannerConfigurer
                BeanDefinitionRegistryPostProcessor pp = beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class);
                registryPostProcessors.add(pp);
                processedBeans.add(ppName);
                // 调用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法
                pp.postProcessBeanDefinitionRegistry(registry);
                reiterate = true;
            }
        }
    }

跟到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法中,关键代码:

    ...
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

这段代码用于扫描MapperScannerConfigurer中配置的basePackage路径下的文件.继续根进ClassPathBeanDefinitionScanner类的scan方法:

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

doScan方法会调用ClassPathMapperScanner#doScan类中的doScan方法:

    // 调用父类doScan方法,扫描basePackage下的mapper的接口文件,封装成Set<BeanDefinitionHolder>
    doScan(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;

processBeanDefinitions方法很重要:

    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      // 上面的注释其实说的很清楚了,mapper接口实际的实体为MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      // 设置MapperFactoryBean属性addToConfig元素
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      ...
      // 设置MapperFactoryBean属性sqlSessionTemplate元素
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
    }

经过上面的流程,basePackages下的mapper接口已经注册到容器中.

实例化MapperFactoryBean中SqlSessionFactory,解析xml配置文件

继续回到AbstractApplicationContext类中的refresh中,会在该方法中初始化所有单例且是懒加载的bean,如果在应用中注入使用mapper接口时:

    @Autowired
    UserInfoDao userInfoDao;

就会初始化该mapper实例,其实就是初始化MapperFactoryBean,spring会检查该bean的属性是否为对象,依次初始化,由于
MapperFactoryBean中的属性SqlSessionTemplate、addToConfig,由于SqlSessionTemplate已经在配置文件配置,继而又会去初始化SqlSessionTemplate的属性org.mybatis.spring.SqlSessionFactoryBean,因为SqlSessionFactoryBean实现了InitializingBean接口,所以在初始化时会调用其afterPropertiesSet方法:

    @Override
    public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");
    this.sqlSessionFactory = buildSqlSessionFactory();
    }

buildSqlSessionFactory方法非常关键,用来解析mppaer xml文件,关键代码:

    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
    configuration, mapperLocation.toString(), configuration.getSqlFragments());
    xmlMapperBuilder.parse();

这里不作具体分析。

生成mapper接口动态代理类

当MapperFactoryBean中的属性初始化完后,则继续执行MapperFactoryBean的初始化流程,在AbstractBeanFactory类的doGetBean方法中:

    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

调用了AbstractBeanFactory类的getObjectForBeanInstance方法:

    object = getObjectFromFactoryBean(factory, beanName, !synthetic);

因为MapperFactoryBean实现了FactoryBean接口,所以才可以向下执行代码,
继续调用了FactoryBeanRegistrySupport类的getObjectFromFactoryBean方法:

    Object object = doGetObjectFromFactoryBean(factory, beanName);

继续调用FactoryBeanRegistrySupport类中的doGetObjectFromFactoryBean方法:

    ...
    object = factory.getObject();
    ...

原来调用了FactoryBean的getObject方法,这时则断点执行到了
MapperFactoryBean的getObject方法中:

    return getSqlSession().getMapper(this.mapperInterface);

继续执行到org.apache.ibatis.session.Configuration的getMapper方法:

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

上面代码是不是很熟悉,原来为mapper 接口创建了代理类MapperProxy<T>,当调用mapper接口中具体的方法操作数据库时,其实执行的的是MapperProxy<T>中的invoke方法:

    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);

上面还有一个关键点就是,xml中解析的配置如何与spring容器中mapper bean相关联呢,其实通过DaoSupport类中的checkDaoConfig方法,在DaoSupport类的afterPropertiesSet方法中调用,具体看MapperFactoryBean中的checkDaoConfig实现:

    @Override
      protected void checkDaoConfig() {
        super.checkDaoConfig();
        notNull(this.mapperInterface, "Property 'mapperInterface' is required");
        // mybatis中配置类
        Configuration configuration = getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
          try {
            // 添加mapper关联
            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();
          }
        }
      }

到此为止,已经分析完了mybatis与spring结合的源码简单说明,省略了大量的细节,以及mapper xml文件解析、sql执行流程没有分析,后续文章会做分析。由于作者水平有限,文章存在错误之处,肯请斧正,谢谢!


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring、mybatis整合源码简单分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏