前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring、mybatis整合源码简单分析

spring、mybatis整合源码简单分析

作者头像
LiosWong
发布2018-10-29 17:42:14
8190
发布2018-10-29 17:42:14
举报
文章被收录于专栏:后端沉思录后端沉思录

配置

代码语言:javascript
复制
<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方法里,会调用:

代码语言:javascript
复制
invokeBeanFactoryPostProcessors(beanFactory);

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

代码语言:javascript
复制
// 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方法中,关键代码:

代码语言:javascript
复制
...
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

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

代码语言:javascript
复制
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);

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

代码语言:javascript
复制
// 调用父类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方法很重要:

代码语言:javascript
复制
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接口时:

代码语言:javascript
复制
@Autowired
UserInfoDao userInfoDao;

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

代码语言:javascript
复制
@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文件,关键代码:

代码语言:javascript
复制
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();

这里不作具体分析。

生成mapper接口动态代理类

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

代码语言:javascript
复制
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

调用了 AbstractBeanFactory类的getObjectForBeanInstance方法:

代码语言:javascript
复制
object = getObjectFromFactoryBean(factory, beanName, !synthetic);

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

代码语言:javascript
复制
Object object = doGetObjectFromFactoryBean(factory, beanName);

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

代码语言:javascript
复制
...
object = factory.getObject();
...

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

代码语言:javascript
复制
return getSqlSession().getMapper(this.mapperInterface);

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

代码语言:javascript
复制
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方法:

代码语言:javascript
复制
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实现:

代码语言:javascript
复制
@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执行流程没有分析,后续文章会做分析。由于作者水平有限,文章存在错误之处,肯请斧正,谢谢!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 配置
  • 分析
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档