MyBatis笔记(八):Spring 整合 MyBatis 包源码分析

       在读本文之前,默认你已经了解 MyBatis 编程的基本使用,熟悉 MyBatis 的使用流程,了解了SqlSessionFactoryBuilderSqlSessionFactorySqlSessionMapper这四大对象以及getMapper()方法的用法。如果你对这些还不是太了解,建议你先翻回去了解一下:MyBatis基础使用,这样会加深你对 MyBatis 使用的了解。

       本文接下来重点分析 Spring 整合 MyBatis 的流程,请继续往下看 →Go 皮卡丘。在了解 Spring 整合 MyBatis 之前,我们先来了解一下这个问题:

1.为什么要将 MyBatis 整合到 Spring?

  1. Spring会帮助我们管理对象。
           省去我们对 SqlSessionFactory、SqlSession、Mapper 这些核心对象的创建,不需要我们手工去创建了,Spring 会通过 IOC 机制来帮我们管理这些对象。
  2. Spring 使用 xxxTemplate 封装了方法
           Spring整合MyBatis,它为我们提供了一个xxxTemplate方法,方便我们去调用。

2. Spring 整合 MyBatis 原理

       将 MyBatis 集成到 Spring中,首先我们需要使用到一个"桥梁",即:MyBatis 官方为我们提供的 mybatis-spring jar 包。它会依赖 Spring 中为我们预留的扩展点,来完成 Spring 与 MyBatis 的整合。

2.1 Spring整合MyBatis流程

Spring 整合 MyBatis 过程,共以下两步:

2.1.1 项目中添加 Maven 依赖

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>${mybatis-spring.version}</version>
</dependency>

2.1.2 Spring核心配置applicationContext.xml添加如下配置

<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- dataSource 数据源,使用的是Spring的数据源 -->
	<property name="dataSource" ref="mysqlDatasource" />
	<!-- 指定全局配置文件 -->
	<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
	<!-- 自动扫描mapping.xml文件 -->
	<property name="mapperLocations" value="classpath:com/mvc/mapper/*.xml"></property>
</bean>

<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.mvc.dao"/>
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

2.2 Spring整合 MyBatis 过程源码分析

        我们已经了解到,MyBatis的使用离不开SqlSessionFactorySqlSessionMapper这三大对象。这三大对象是如何被创建的呢?我们接下了进一步分析

第一步:SqlSessionFactory对象的创建

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- dataSource 数据源,使用的是Spring的数据源 -->
	<property name="dataSource" ref="mysqlDatasource" />
	<!-- 指定全局配置文件 -->
	<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
	<!-- 自动扫描mapping.xml文件 -->
	<property name="mapperLocations" value="classpath:com/mvc/mapper/*.xml"></property>
</bean>

        我们已经在 applicationContext.xml 中有配置以上 sqlSessionFactoryBean,通过名称我们便可以大概了解到该 Bean 就是用来为我们创建 sqlSessionFactory 对象的类。(我们继续往下分析 → Go → Go)

1.1 我们来分析sqlSessionFactoryBean 类
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
	......
}

我们看到该类实现了一个 FactoryBeanInitializingBean的接口。

        我们先来分析一下 InitializingBean接口,在源码中,我们能够看到该接口有一个 afterPropertiesSet() 方法需要实现。如下所示,那么该方法是用来干嘛的呢?

public interface InitializingBean {
	 /**
	  * 用于对象实例化设置bean属性完成后,执行的一些操作,
	  * 该方法由 BeanFactory调用。即创建工厂类的时候,一定会调用这个方法
	  */
	void afterPropertiesSet() throws Exception;
}

        我们已经知道了 afterPropertiesSet() 方法的意思。接下来我们再来分析FactoryBean 接口,我们看到该接口共有三个方法需要实现,此处我们重点来关注一下 getObject() 方法。

public interface FactoryBean<T> {

	/**
	 * 该方法用来获取对象。
	 * 即:从Spring IOC容器中获取创建的对象,会调用到该方法
	 */
	T getObject() throws Exception;

	Class<?> getObjectType();
	boolean isSingleton();

}

        介绍完这两个接口,我们再来看看 getObject() 方法和 afterPropertiesSet() 方法在 sqlSessionFactoryBean 类中的具体实现过程。

1.2 分析getObject() 、afterPropertiesSet() 方法在类中的实现

源码显示,实际调用流程如下:

扫描二维码关注公众号,回复: 10276559 查看本文章

1.getObject() → 2.afterPropertiesSet() → 3.buildSqlSessionFactory()

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
	
	/**
	 * 其他属性、方法 省略,仅展示要分析的源码
	 */
	@Override
	public SqlSessionFactory getObject() throws Exception {
    	if (this.sqlSessionFactory == null) {
    		//1.第一步
      		afterPropertiesSet();
    	}
    	return this.sqlSessionFactory;
  	}
  	
  	@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");
		//2.第二步
    	this.sqlSessionFactory = buildSqlSessionFactory();
  	}
	
	/**
	 * buildSqlSessionFactory()方法
	 */
	protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    	Configuration configuration;
		//3.第三步:XMLConfigBuilder 用来读取配置的 xmlConfig
    	XMLConfigBuilder xmlConfigBuilder = null;
    	if (this.configuration != null) {
      		configuration = this.configuration;
      		if (configuration.getVariables() == null) {
        		configuration.setVariables(this.configurationProperties);
      		} else if (this.configurationProperties != null) {
        		configuration.getVariables().putAll(this.configurationProperties);
      		}
    	} else if (this.configLocation != null) {
      		xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      		configuration = xmlConfigBuilder.getConfiguration();
    	} else {
     		//省略部分判断代码
     	}

    	if (xmlConfigBuilder != null) {
      		try {
      			//4.执行 xmlConfigBuilder.parse()方法,此处已经走入到 mybatis 源码部分,已经是MyBatis的执行流程了。
        		xmlConfigBuilder.parse();

        		if (LOGGER.isDebugEnabled()) {
          			LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        		}
      		} catch (Exception ex) {
        		throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      		} finally {
        		ErrorContext.instance().reset();
      		}
    	}

    	if (!isEmpty(this.mapperLocations)) {
      		for (Resource mapperLocation : this.mapperLocations) {
        		if (mapperLocation == null) {
          			continue;
        		}

        		try {
        			//5.解析XMLMapper映射器
          			XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              		configuration, mapperLocation.toString(), configuration.getSqlFragments());
              		//6.调用 xmlMapperBuilder.parse()方法,跳转到MyBatis源码去解析 XMLMapper 配置
          			xmlMapperBuilder.parse();
        		} catch (Exception e) {
          			throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        		} finally {
          			ErrorContext.instance().reset();
        		}
      		}
    	} else {
      		if (LOGGER.isDebugEnabled()) {
        		LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      		}
    	}
		//7.通过sqlSessionFactoryBuilder.build()方法,创建一个sqlSessionFactory
    	return this.sqlSessionFactoryBuilder.build(configuration);
  	}
}
public SqlSessionFactory build(Configuration config) {
	return new DefaultSqlSessionFactory(config);
}

        通过如上代码分析,此处我们整理了一下,执行流程如下图所示:
在这里插入图片描述

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- dataSource 数据源,使用的是Spring的数据源 -->
	<property name="dataSource" ref="mysqlDatasource" />
	<!-- 指定全局配置文件 -->
	<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
	<!-- 自动扫描 mapping.xml文件 -->
	<property name="mapperLocations" value="classpath:com/mvc/mapper/*.xml"></property>
</bean>

        执行到此处,配置完上面这个<bean id=“sqlSessionFactory”>,它便会在 Spring 启动创建 sqlSessionFactoryBean 时,调用 getObject() 等一系列方法,将全局配置文件mapper配置文件等一些列操作在上面源码中都已经完成。最后调用了一个 return this.sqlSessionFactoryBuilder.build(configuration); 返回了一个DefaultSqlSessionFactory() 对象。

       到达此处---->创建 sqlSessionFactory 对象就 OK 了。接下来继续了解 sqlSession 对象的创建流程。皮卡丘,继续向下看↓↓↓↓↓

第二步:SqlSession对象的创建

        在 MyBatis 中,我们创建 sqlSession 使用的是 new DefaultSqlSession 的方式,代码如下:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      	final Environment environment = configuration.getEnvironment();
      	final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      	tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      	final Executor executor = configuration.newExecutor(tx, execType);
      	//MyBatis源码:使用的是 new DefaultSqlSession()的方式,返回一个 SqlSession 对象
      	return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      	closeTransaction(tx); // may have fetched a connection so lets call close()
      	throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      	ErrorContext.instance().reset();
    }
}
2.1 我们来分析整合Spring时,如何创建SqlSession类

 2.1.1 为什么不能使用 DefaultSqlSession 来创建 SqlSession?

       在整合 Spring 的过程中,我们是不能使用new DefaultSqlSession()的方式来创建 SqlSession,这是因为 DefaultSqlSession 类在 MyBatis 中是线程不安全的。 如下图所示:
在这里插入图片描述
       在与 Spring 整合的过程中,Spring 为了解决 DefaultSqlSession 线程不安全的问题,为我们提供了一个线程安全的 SqlSessionTemplate 类。如下图所示:
在这里插入图片描述
 2.1.2 SqlSessionTemplate为什么是线程安全的?

        我们从SqlSessionTemplate类开始分析。该类同我们在 MyBatais 中使用的 DefaultSqlSession 类相同,也都为我们提供了直接操作数据库的一些方法,比如:selectOne(),selectList() 等。
在这里插入图片描述
       我们从 selectOne() 方法入手来分析,发现此处使用的是 sqlSessionProxy 对象,这个对象我们通过名称可以看出它是一个代理类。

public class SqlSessionTemplate implements SqlSession, DisposableBean {

	//部分代码省略	
  	private final SqlSession sqlSessionProxy;
  
  	@Override
  	public <T> T selectOne(String statement) {
  		//使用 sqlSessionProxy 来调用 selectOne() 方法
    	return this.sqlSessionProxy.<T> selectOne(statement);
  	}
}

       通过属性定义private final SqlSession sqlSessionProxy;发现,它还是一个 SqlSession 类。最终这个 SqlSession 还是指代的是 DefaultSqlSession类。
在这里插入图片描述
       我们来了解一下 sqlSessionProxy 的实例化过程,我们发现它是在构造器中进行实例化的,代码如下:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //发现:sqlSessionProxy其实是 SqlSessionInterceptor 的代理类
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        		SqlSessionFactory.class.getClassLoader(),
        		new Class[] { SqlSession.class },
        		new SqlSessionInterceptor());
}

       我们继续对 SqlSessionInterceptor 类进行分析,代理类肯定会执行它的 invoke() 方法,接下来我们对 invoke()方法进行了解,代码如下:

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//这个方法,此处这个 getSqlSession() 方法就是一个重点了
      	SqlSession sqlSession = getSqlSession(
          	SqlSessionTemplate.this.sqlSessionFactory,
          	SqlSessionTemplate.this.executorType,
          	SqlSessionTemplate.this.exceptionTranslator);
      	try {
        	Object result = method.invoke(sqlSession, args);
        	//此处是通过 Transactional 事务的方式
        	if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          		//sqlSession提交
          		sqlSession.commit(true);
        	}
        	return result;
      	} catch (Throwable t) {
        	//省略
        }
        throw unwrapped;
      	} finally {
        	if (sqlSession != null) {
	        	//关闭sqlSession
          		closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        	}
      	}
    }
}

       我们继续对 getSqlSession() 方法进行分析,代码如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
	//此处,通过事务管理器的 getResource()方法,将我们的sqlSessionFactory工厂类传入
	//返回的是一个 SqlSessionHolder持久器类
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      	return session;
    }

    if (LOGGER.isDebugEnabled()) {
      	LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

       我们继续对 getResource() 方法进行分析,代码如下:

public abstract class TransactionSynchronizationManager {
	private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");

	public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		//执行 doGetResource()方法
		Object value = doGetResource(actualKey);
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		return value;
	}

	private static Object doGetResource(Object actualKey) {
		//我们通过 resources.get() 的方式来获得,我们对这个 resources 属性进行分析
		//发现它返回的 SqlSession的持久器 SqlSessionHolder 也是一个ThreadLocal对象。
		//即:我们每一个获取到的 SqlSession都是有自己的SqlSessionHolder对象的
		//此处,一个事务一个SqlSession。本身ThreadLocal 就是线程安全的,所以每一个SqlSession 也都是线程安全的
		Map<Object, Object> map = (Map)resources.get();
		if (map == null) {
			return null;
		} else {
			Object value = map.get(actualKey);
			if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
				map.remove(actualKey);
				if (map.isEmpty()) {
					resources.remove();
				}

				value = null;
			}
			return value;
		}
	}
}

       到达此处---->通过 SqlSessionTemplate 就可以创建 线程安全的 SqlSession 对象了。接下来继续了解 getMapper 的流程。皮卡丘,继续向下看↓↓↓↓↓

第三步:在 DAO 层如何获取 SqlSessionTemplate 来调用数据库

        这个过程,即 MyBatis 中的 getMapper() 的过程。我们在 DAO 层是怎么能够拿到 SqlSessionTemplate 的呢?(实际开发中我们使用的是注解的方式,此处不通过注解的形式,我们来分析一下如何拿到这个 SqlSessionTemplate)

 3.1.1 我们通过继承 SqlSessionDaoSupport 的方式来获取SqlSessionTemplate
        在 mybatis-spring 整合包中,它为我们提供了一个SqlSessionDaoSupport 类。我们只需要继承这个类,通过getSqlSession()方法,便能够获取到一个 sqlSessionTemplate。

public abstract class SqlSessionDaoSupport extends DaoSupport {

  	private SqlSession sqlSession;

  	private boolean externalSqlSession;

    /**
  	 * 省略部分代码
  	 */

  	/**
   	 * Users should use this method to get a SqlSession to call its statement methods
     * This 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.sqlSession;
  	}
}

 3.1.2 【过渡阶段】Dao层实现类继承自SqlSessionDaoSupport,完成对数据库的操作
        此部分仅是一个过渡阶段,在我们的项目中不会使用这种方式开发,此处只是用来介绍一下(但是,有的公司还是使用的这种方式,我公司有的项目就是这样子开发。也是What…我佛了)

        我们定义一个父类 BaseDao,让其继承 SqlSessionDaoSupport ,然后使用我们的 Mapper 接口的实现类来继承这个 BaseDao,我们便能够拿到 sqlSessionTemplate,完成对数据库的操作。

public class BaseDao<T> extends SqlSessionDaoSupport {
	
	private static final Log log = LogFactory.getLog(BaseDao.class);
	
	public List<T> query(String sql, Object obj) throws DaoException{
		List<T> lists = null;
		try {
			lists = super.getSqlSession().selectList(sql, obj);
		} catch (Exception e) {
			String info = String.format("qury list for db, sql is [%s], param is [%s] failed.", sql,
					null != obj ? obj.toString() : null);
			log.error(info , e);
			throw new DaoException(ReportConstant.DB_ACTION_EXCEPTION, info);
		}
		return lists;
	}

	public int save(String sql, Object obj) throws DaoException{
		//省略
	}
	public boolean delete(String sql,Object obj) throws DaoException {
		//省略
	}

	public boolean update(String sql, Object obj) throws DaoException{
		//省略
	}
}
@Repository
public class ColumnMgrDaoImpl extends BaseDao<ColumnMgr> {
	
	public ColumnMgr query(int cid){
		ColumnMgr columnMgr = (ColumnMgr)this.selectOne("com.springboot.dao.UserMapper.selectAllUser",cid);
		return columnMgr;
	}
}
public class DaoSupportTest{
	@Autowired
	ColumnMgrDaoImpl columnMgrDao;

	@Test
	public void test{
		ColumnMgr columnMgr = columnMgrDao.query(1);
		System.out.println(columnMgr);
	}
}

       这样子虽然可以查询数据库,但是此处会有很多实现类,显然实际开发中不是这样开发,我们只使用了一个注解@Autowired即可。那这又是为什么呢?(皮卡丘,继续向下看↓↓↓↓↓)

 3.1.3 既然不想写那么多Dao的实现类,那么此处来了解一下接口扫描注册的过程

       当使用 @Autowired 注入一个接口的时候,首先这个接口肯定是从Spring IOC 容器中获得的,那么就说明在之前我们肯定已经把它注册到 IOC 容器了。那我们是在什么时候注册的?此处就涉及到了一个接口扫描的配置,如下配置部分:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<!-- 注入要扫描的包名 -->
	<property name="basePackage" value="com.mvc.dao"/>
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

        我们先来分析一下 MapperScannerConfigurer 这个类,我们发现 它有实现一个 BeanDefinitionRegistryPostProcessor 这个接口,该接口有一个postProcessBeanDefinitionRegistry()方法需要被实现,接下来我们就分析一下这个方法。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

	//省略部分代码

	@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.registerFilters();
    	//1.调用 scan() 方法    	
		scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  	}

	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		//2.调用 doScan()方法
		doScan(basePackages);
		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
}

       doScan()方法,实际上调用的是ClassPathMapperScanner 类中的 doScan() 方法,如下所示:

@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 {
    	//3.继续调用此处的方法
      	processBeanDefinitions(beanDefinitions);
    }
	return beanDefinitions;
}

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    //4.此处,拿到所有beanDefinition(即接口)
    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
      	//5.(重点)注意此处:我们向IOC容器中注册的对象,本来应该是Mapper接口本身
      	//但是,此处我们真正注册到IOC容器的是一个 MapperFactoryBean
      	definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      	//6.将接口的 beanClass 替换成 MapperFactoryBean
      	definition.setBeanClass(this.mapperFactoryBean.getClass());

      	definition.getPropertyValues().add("addToConfig", this.addToConfig);
	//部分代码省略     
    }
}

        我们再来分析一下这个 MapperFactoryBean,我们发现它竟然继承了 SqlSessionDaoSupport,其实就是为了通过 getSqlSession()方法来返回一个 sqlSession,解决我们在3.1.2 步骤中的繁琐操作的。现在就省去了3.1.2步骤中实现类的编写了。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
	......
}

第四步:我们继续分析getMapper()的过程

       我们看到 MapperFactoryBean 对象还有实现一个 FactoryBean 接口,在本文开始我们有介绍它有一个 getObject() 方法需要别实现,那我们就看一下 MapperFactoryBean 类中对 getObject() 方法时如何实现的。(前方高能,重点代码出现了↓↓↓)

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

	//部分代码省略
  	@Override
  	public T getObject() throws Exception {
  		//1.通过 getSqlSession()方法,可以获取一个SqlSession对象
  		//通过 getMapper()方法,来获取一个接口实现类
    	return getSqlSession().getMapper(this.mapperInterface);
  	}
}

       那我们继续对 getMapper()方法进行分析,跳转到 SqlSessionTemplate 类中去分析它的实现过程。Go Go Go,皮卡丘,快要见太阳了

public class SqlSessionTemplate implements SqlSession, DisposableBean {
	@Override
	public <T> T getMapper(Class<T> type) {
		//2.通过getConfiguration()方法,来获取MyBatis的全局配置对象
		//通过getMapper()方法,继续向下分析就进入到 MyBatis包中的代码了
    	return getConfiguration().getMapper(type, this);
	}
}

       继续向下分析,进入MyBatis 源码包

/**
 * 进入到 MyBatis 的 Configuration 类
 */
public class Configuration {
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    	//3.此处返回的就是一个MapperProxy的代理类对象了
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
}

       到此处,我们得出结果,最终通过@Autowired 注解的 Mapper类,返回来的其实就是一个 MapperProxy 代理类的对象。(此处我们以 UserService 为例分析)

       如下所示:我们最终自己编写的 UserService 中,返回来的这个 UserMapper 就是一个 MapperProxy 代理类。

@Service("userService")
public class UserService implements IUserService {

    @Autowired
    public UserMapper userDao;//4.此处就是一个 MapperProxy 代理类

    /**
     * 获取用户信息
     * @return
     */
    @Override
    public List<User> getUser() {
        List<User> users = userDao.selectAllUser();
        return users;
    }
}

       最后一步,通过userDao.selectAllUser()的调用,最后执行的其实还是 MapperProxy 代理类中的 invoke()方法。

public class MapperProxy<T> implements InvocationHandler, Serializable {
	//省略部分代码
	
	//5.最终执行的还是 MapperProxy 代理类中的 invoke()方法
  	@Override
  	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	if (Object.class.equals(method.getDeclaringClass())) {
      		try {
        		return method.invoke(this, args);
      		} catch (Throwable t) {
        		throw ExceptionUtil.unwrapThrowable(t);
      		}
    	}
    	//6.在此处,它会通过反射的方式,获取到使用注解类的 class
  		//传递给上两步,MyBatis源码中的getMapper(type,sqlSession)方法中的type参数
		//从而实现 MyBatis 中,通过getMapper()的方式获得当前类。
    	final MapperMethod mapperMethod = cachedMapperMethod(method);
    	return mapperMethod.execute(sqlSession, args);
  	}
}

       至此,MyBatis中通过 getMapper() 获得 Mapper 类,在Spring 中的实现方式也就介绍完了。

       这就是我们在 Spring 中,只需要在 UserMapper 类上添加一个 @Autowired 注解,便能够实现 MyBatis 中的 getMapper()的过程。


恭喜您,看到这里,说明你是最棒的 Yeah、Yeah、Yeah

Spring 整合 MyBatis 源码分析,介绍到此为止

如果本文对你有所帮助,那就给我点个赞呗 ^ _ ^

End

发布了301 篇原创文章 · 获赞 66 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/lzb348110175/article/details/104604750
今日推荐