目录
2.2.2.1 Spring是怎么管理Mapper接口的动态代理的?
2.2.2.1.2 如何将Mapper的代理对象加入到Spring容器中
2.2.2.2 创建Mapper接口的代理对象,并将其注册到Spring容器的源码分析
一、前言
在真实的项目我们几乎不会将MyBatis 单独运用到项目中,而是将其整合到Spring框架或者Spring Boot中,本文将通过讲解MyBatis 与Spring和Spring Boot的整合的方法和原理。
二、Spring整合MyBatis
Spring整合MyBatis的原理也是一道非常高频的面试题, Spring中集成Mybatis主要有两个难点,一是事务的集成,二是Mapper接口注册到Spring容器中,Spring中集成MyBatis需要引入mybatis和mybatis-spring依赖包。
在Spring中我们通过mybatis-spring 中间框架将MyBatis和Spring 两个完全不相关的框架整合起来
该框架一方面负责加载和解析MyBatis相关配置,另一方面,该框架还会通过Spring提供的扩展点,把各种Dao接口对应的对象放入IOC容器中。
2.1 在Spring中使用MyBatis
2.1.1 引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
2.1.2 完成初始化
JavaConfig + 注解完成初始化:
我们可以通过JavaConfig + 注解将SqlSessionFactoryBean注册到Spring容器中,以及通过@MapperScan或@MapperScans确定将哪些Mapper接口注册到Spring容器中来完成初始化。
@Configuration
// 将这个包下面的所有Mapper接口注册到Spring容器中
@MapperScan(basePackages = {"com.eleven.icode.ispring.mapper"})
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
// 创建SqlSessionFactoryBean
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 解析全局配置文件和SQL映射文件
// 配置数据源
factoryBean.setDataSource(dataSource);
// 配置全局配置文件
factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
// 配置SQL映射文件
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
// 返回SqlSessionFactoryBean,将其注册到Spring容器中
return factoryBean;
}
}
XML配置文件完成初始化:
我们也可以通过在Spring的配置文件SpringContext.xml中配置这些Bean,这些Bean主要用于配置数据源,配置sqlSessionFactory用于生成SqlSession,配置MapperScannerConfigurer用于扫描接口,这种方式也可以将配置的组件注册到Spring容器中,来完成初始化。配置文件如下:
<context:property-placeholder location="jdbc.properties"/>
<context:component-scan base-package="com.jay"/>
<!--配置数据源-->
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
<!-- 配置与数据库交互的4个必要属性 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 给 SqlSessionFactory 配置数据源,这里引用上面的数据源配置 -->
<property name="dataSource" ref="dataSource"/>
<!--配置MyBatis-cfg.xml的路径-->
<property name="configLocation" value="MyBatis-cfg.xml"/>
<!--配置映射文件-->
<property name="mapperLocations" value="mybatis/*.xml"/>
</bean>
<!--配置MapperScannerConfigurer-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--配合Dao接口所在的包-->
<property name="basePackage" value="com.jay.mapper"/>
</bean>
不管用上面的哪种方法,我们都需要配置一下MyBatis的配置文件MyBatis-cfg.xml(具体内容在之前的笔记中已经讲过了:MyBatis的配置文件详解),配置如下:
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias type="com.jay.entity.ClassRoom" alias="ClassRoom"/>
<typeAlias type="com.jay.entity.Student" alias="Student"/>
</typeAliases>
</configuration>
需要注意的是,MyBatis-cfg.xml 中的配置除了settings元素是必须配置在MyBatis的配置文件中,其余元素都可以配置到Spring的配置文件中。
2.1.3 测试代码
配置文件处理完成之后,接着我们来新建一个映射文件以及Dao 接口测试下。此处我新建了一个名为StudentMapper.xml的映射文件:
<mapper namespace="com.jay.mapper.StudentMapper">
<resultMap id="studentResult" type="Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sexEnum" column="sex"
typeHandler="com.jay.Handler.GeneralEnumHandler"/>
<association property="classRoom" javaType="ClassRoom">
<id property="id" column="id"/>
<result property="name" column="name"/>
</association>
</resultMap>
<resultMap id="classRoomResult" type="ClassRoom">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<select id="selectStudentById" resultMap="studentResult">
SELECT s.id,s.`name`,s.age,s.sex,c.id,c.`name` FROM student s, class c
WHERE s.class_id=c.id AND s.id=#{id}
</select>
</mapper>
对应的Dao接口如下:
@Repository
public interface StudentMapper {
/**
* 根据id查询学生
* @param id
* @return
*/
Student selectStudentById(Integer id);
}
最后我们新建一个测试类,测试下结果:
@RunWith(SpringJUnit4ClassRunner.class)
// 配置Spring配置
@ContextConfiguration("classpath:SpringContext.xml")
public class StudentMapperTest {
@Autowired
private StudentMapper studentMapper;
@Test
public void testSelectStudentById() {
Student student = studentMapper.selectStudentById(1);
System.out.println(student.toString());
}
}
测试结果如下:
2.1.4 小结
至此我们MyBatis与Spring的整合就完成了,当然这是一个很小的示范demo,但是其包括了Spring与MyBatis整合的要点。其流程无非就是:
- 先引入相关的依赖,Spring的依赖和MyBatis的依赖等。
- 接着就是对数据源,SqlSessionFactory等信息进行配置。
- 最后就是编写映射文件和Dao接口。
下面我们就深入源码,讲解Spring整合MyBatis的底层原理。
我们就以Spring初始化MyBatis为讲解入口,也就是上面讲过的代码2.1.2 完成初始化中:1、构造SqlSessionFactoryBean将其注册到Spring容器;2、将Mapper接口注册到Spring容器 这两个步骤来进行源码分析。
2.2 源码分析
2.2.1 SqlSessionFactoryBean
SqlSessionFactoryBean是一个FactoryBean,且重写了getObjectType方法将代理的Bean的类型设置为了原始的SqlSessionFactory类型,SqlSessionFactoryBean实现了InitializingBean接口,主要逻辑都在其实例化完成时调用的afterPropertiesSet()(实现了InitializingBean接口的类,可以实现这个接口的afterPropertiesSet()方法,这个方法会在Bean初始化的时候被调用),其作用就是创建Mybatis的SqlSessionFactory,其可以代替全局配置文件,若设置了配置文件可对配置文件内容做一个补充。
// SqlSessionFactoryBean是Spring整合MyBatis时使用的类,如果只是单独MyBatis框架的话,是不用这个类的。所以前面单独讲MyBatis源码的时候并没有出现这个类,毕竟这个类都实现了Spring提供的InitializingBean接口,这也进一步说明了这个类是和Spring有关的。
package org.mybatis.spring;
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
// 这个是MyBatis提供的构造类,在前面也已经讲过了:SqlSessionFactoryBuilder类,Spring也要通过它来初始化框架
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// SqlSessionFactoryBean实现InitializingBean接口,需要实现其afterPropertiesSet()。SqlSessionFactoryBean 初始化的时候Spring会自动调用这个方法
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");
// 在方法buildSqlSessionFactory()中通过SqlSessionFactoryBean的成员属性sqlSessionFactoryBuilder对象来构建我们的sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 声明一个Configuration对象用于保存mybatis的所有的配置信息
final Configuration targetConfiguration;
// 声明一个XMLConfigBuilder对象,用于解析XML配置文件,这个类前面也讲过了:XMLConfigBuilder
XMLConfigBuilder xmlConfigBuilder = null;
// 初始化configuration对象,和设置其 `configuration.variables` 属性
if (this.configuration != null) {
// 把配置的SqlSessionFactoryBean配置的configuration赋值给targetConfiguration
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
}
// 创建xml配置构建器对象(xmlConfigBuilder),对全局配置文件mybatis-config.xml配置文件进行解析
else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
targetConfiguration = new Configuration();
// 判断configurationProperties不为空,则调用targetConfiguration.set方法把configurationProperties注入到Configuration对象中
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// typeAliasesPackage配置情况分为二种:在mybaits-config.xml中配置,在配置SqlSessionFactoryBean时配置
if (hasLength(this.typeAliasesPackage)) {
// 扫描typeAliasesPackage包路径下所有实体类class类型进行过滤注册到Configuration的别名映射器中
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
// 判断SqlSessionFactory是否配置了typeAliases,一般typeAliasesPackage配置了就没有必要配置typeAliases,注册到Configuration的别名映射器中
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);});
}
// 把自定义的插件注册到的mybatis的配置类上
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin);});
}
// 扫描自定义的类型处理器(用来处理的java类型和数据库类型的转化) 并且注册到的 targetConfiguration(批量注册)
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
// 通过配置<TypeHandlers></TypeHandlers>的形式来注册的类型处理器对象
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler);});
}
// Mybatis从3.2开始支持可插拔的脚本语言,因此可插入一种语言的驱动来写基于这种语言的动态SQL查询
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { targetConfiguration.getLanguageRegistry().register(languageDriver);});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver).ifPresent(targetConfiguration::setDefaultScriptingLanguage);
// 设置数据库厂商
if (this.databaseIdProvider != null) {
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
// 若二级缓存配置不为空,注册二级缓存
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
// 真正的解析的配置(mybatis-config.xml)的document对象
xmlConfigBuilder.parse();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// 为的configuration设置一个环境变量
targetConfiguration.setEnvironment(new Environment(this.environment, this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource));
// 循环遍历解析mapper.xml文件
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
// 真正的解析mapper.xml文件。这个过程我们在前面也已经讲过了:解析SQL映射文件(解析XML映射文件)
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
}
}
// 通过建造者模式构建的SqlSessionFactory对象 默认是DefaultSqlSessionFactory。整个流程前面都已经讲过:SqlSessionFactoryBuilder类
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
Set<Class<?>> classes = new HashSet<>();
// 把的多个配置报路径转换成字符串数组
String[] packagePatternArray = tokenizeToStringArray(packagePatterns, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// 循环的包路径
for (String packagePattern : packagePatternArray) {
// 把包路径下的class解析成的Resouce数组
Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
for (Resource resource : resources) {
try {
// 挨个解析成的class类型
ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
// 判断当前的class类型是不是被支持
if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
// 加入到结合中
classes.add(clazz);
}
} catch (Throwable e) {
LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
}
}
}
return classes;
}
// 重写了getObjectType方法将代理的Bean的类型设置为了原始的SqlSessionFactory类型
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
/**
* 实现FactoryBean接口的getObject方法
* 将SqlSessionFactory对象注入spring容器
* {@inheritDoc}
*/
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
}
可知SqlSessionFactoryBean主要通过对applicationContext.xml解析来完成对Configuration的实例化以及对映射文件*mapper.xml的解析。
关键点:
- *XMLConfigBuilder:在MyBatis中主要负责解释mybatis-config.xml
- 解析完后,如果我们自己设置了则使用我们的设置的进行覆盖,不做一一介绍了
- XMLMapperBuilder:负责解析映射配置文件
- targetConfiguration.setEnvironment 这里注意一下,事务工厂会使用一个新的new SpringManagedTransactionFactory(),而不是MyBatis之前的ManagedTransactionFactory。这个SpringManagedTransactionFactory会使用Spring事务中的dataSource,从而达到跟事务集成。
2.2.1.1 事务集成
事务集成是在SqlSessionFactoryBean中完成的,在为configuration设置环境变量时创建传入SpringManagedTransactionFactory,在MyBatis中是使用的ManagedTransactionFactory创建一个ManagedTransaction,其是直接从DataSource中获取的Connection。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
}
public class ManagedTransactionFactory implements TransactionFactory {
public Transaction newTransaction(Connection conn) {
return new ManagedTransaction(conn, closeConnection);
}
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new ManagedTransaction(ds, level, closeConnection);
}
}
public class ManagedTransaction implements Transaction {
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
protected void openConnection() throws SQLException {
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
}
SpringManagedTransactionFactory中创建的是SpringManagedTransaction,其获取的数据库连接是通过DataSourceUtils工具类从事务同步管理器TransactionSynchronizationManager中获取的连接,从而达到与Spring事务集成的目的。
public class SpringManagedTransactionFactory implements TransactionFactory {
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
}
public class SpringManagedTransaction implements Transaction {
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}
}
2.2.2 Mapper接口注册
2.2.2.1 Spring是怎么管理Mapper接口的动态代理的?
面试的时候会经常问到Spring整合MyBatis的问题,而在这个问题上,我们重点要关注的就是这个代理对象。因为Spring整合MyBatis的目的就是:把某个Mapper的代理对象作为一个bean放入Spring容器中,使得能够像使用一个普通bean一样去使用这个代理对象,比如能被@Autowire自动注入。
比如当Spring和Mybatis整合之后,我们就可以使用如下的代码来使用MyBatis中的代理对象了:
@Component
public class UserService {
// 自动注入
@Autowired
private UserMapper userMapper;
public User getUserById(Integer id) {
return userMapper.selectById(id);
}
}
UserService中的userMapper属性就会被自动注入MyBatis中的代理对象。如果你基于一个已经完成整合的项目去调试即可发现,userMapper的类型为:org.apache.ibatis.binding.MapperProxy@41a0aa7d。证明确实是MyBatis中的代理对象。(之前我们已经讲过了MyBatis给Mapper接口创建代理类的原理:3.1.1 为 Mapper 接口创建代理对象)
好,那么现在我们要解决的问题的就是:如何能够把MyBatis的代理对象作为一个bean放入Spring容器中?
要解决这个,我们需要对Spring的bean生成过程有一个了解。
2.2.2.1.1 Spring中Bean的产生过程
这一块的内容我们讲Spring源码的时候已经学过了(Spring IoC 容器源码分析——ClassPathXmlApplicationContext和Spring IoC 容器源码分析——AnnotationConfigApplicationContext),这里就简单复习一下。
Spring启动过程中,大致会经过如下步骤去生成bean:
- 扫描指定的包路径下的class文件
- 根据class信息生成对应的BeanDefinition
- 在此处,程序员可以利用某些机制去修改BeanDefinition
- 根据BeanDefinition生成bean实例
- 把生成的bean实例放入Spring容器中
假设有一个A类,假设有如下代码:
一个A类:
@Component
public class A {
}
一个B类,不存在@Component注解
public class B {
}
执行如下代码:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));
输出结果为:com.tulingxueyuan.beans.A@6acdbdf5
A类对应的bean对象类型仍然为A类。但是我们可以认为的改变这个结果,比如我们可以利用BeanFactory后置处理器来修改BeanDefinition,我们添加一个BeanFactory后置处理器:
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a");
beanDefinition.setBeanClassName(B.class.getName());
}
}
这样就会导致,原本的A类对应的BeanDefiniton被修改了,被修改成了B类,那么后续正常生成的bean对象的类型就是B类。此时,调用如下代码会报错:
context.getBean(A.class);
但是调用如下代码不会报错,尽管B类上没有@Component注解:
context.getBean(B.class);
并且,下面代码返回的结果是:com.tulingxueyuan.beans.B@4b1c1ea0
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));
之所以讲这个问题,是想说明一个问题:在Spring中,bean对象跟class没有直接关系,跟BeanDefinition才有直接关系。
在Spring中,如果你想生成一个bean,那么得先生成一个BeanDefinition,就像你想new一个对象实例,得先有一个class。
那么回到我们要解决的问题:如何能够把MyBatis的代理对象作为一个bean放入Spring容器中?
2.2.2.1.2 如何将Mapper的代理对象加入到Spring容器中
继续回到我们的问题,我们现在想自己生成一个bean,那么得先生成一个BeanDefinition,只要有了BeanDefinition,通过在BeanDefinition中设置bean对象的类型,然后把BeanDefinition添加给Spring,Spring就会根据BeanDefinition自动帮我们生成一个类型对应的bean对象。
所以,现在我们要解决两个问题:
- MyBatis的代理对象的类型是什么?因为我们要设置给BeanDefinition
- 我们怎么把BeanDefinition添加给Spring容器?
注意:上文中我们使用的BeanFactory后置处理器,它只能修改BeanDefinition,并不能新增一个BeanDefinition。我们应该使用Import技术来添加一个BeanDefinition。后文再详细介绍如果使用Import技术来添加一个BeanDefinition,可以先看一下伪代码实现思路。
假设:我们有一个UserMapper接口,它的代理对象的类型为UserMapperProxy。
那么我们的思路就是这样的,伪代码如下:
// 创建BeanDefinitoin
BeanDefinitoin bd = new BeanDefinitoin();
// 将代理类设置给BeanDefinitoin
bd.setBeanClassName(UserMapperProxy.class.getName());
// 将设置好的BeanDefinitoin注入到Spring容器中
SpringContainer.addBd(bd);
但是,这里有一个严重的问题,就是上文中的UserMapperProxy是我们假设的,它表示一个代理类的类型,然而MyBatis中的代理对象是利用的JDK的动态代理技术实现的,也就是代理对象的代理类是动态生成的,我们根本无法确定代理对象的代理类到底是什么。
所以回到我们的第一个问题:MyBatis的代理对象的类型是什么?
这个问题可能有两种答案:
- 代理对象对应的代理类
- 代理对象对应的接口
由上面的分析可知,答案1肯定是不可能了,因为代理类是动态生成的,那么我们来看答案2:代理对象对应的接口
如果我们采用答案2,那么我们的思路就是:
BeanDefinition bd = new BeanDefinitoin();
// 注意这里,设置的是UserMapper
bd.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bd);
但是,实际上给BeanDefinition对应的类型设置为一个接口是行不通的,因为Spring没有办法根据这个BeanDefinition去new出对应类型的实例,接口是没法直接new出实例的。
那么现在问题来了,我要解决的问题:MyBatis的代理对象的类型是什么?
两个答案都被我们否定了,所以这个问题是无解的,所以我们不能再沿着这个思路去思考了,只能回到最开始的问题:如何能够把MyBatis的代理对象作为一个bean放入Spring容器中?
总结上面的推理:我们想通过设置BeanDefinition的class类型,然后由Spring自动的帮助我们去生成对应的bean,但是这条路是行不通的。
2.2.2.1.3 最终的解决方案
那么我们还有没有其他办法,可以去生成bean呢?并且生成bean的逻辑不能由Spring来帮我们做了,得由我们自己来做。
2.2.2.1.3.1 FactoryBean
还是有一个解决方案的,那就是Spring中的FactoryBean接口。我们可以利用FactoryBean接口实现一个工厂类的Bean,去自定义我们要生成的bean对象,比如:
@Component
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
// 通过JDK动态代理创建代理对象
Object proxyInstance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 执行代理逻辑
return null;
}
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return UserMapper.class;
}
}
我们定义了一个MyFactoryBean,它实现了FactoryBean接口,getObject方法就是用来自定义生成bean对象逻辑的。
执行如下代码:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("myFactoryBean: " + context.getBean("myFactoryBean"));
System.out.println("&myFactoryBean: " + context.getBean("&myFactoryBean"));
System.out.println("myFactoryBean-class: " + context.getBean("myFactoryBean").getClass());
}
}
将打印:
myFactoryBean: com.tulingxueyuan.beans.myFactoryBean$1@4d41cee
&myFactoryBean: com.tulingxueyuan.beans.myFactoryBean@3712b94
myFactoryBean-class: class com.sun.proxy.$Proxy20
从结果我们可以看到,从Spring容器中拿名字为"myFactoryBean"的bean对象,就是我们所自定义的jdk动态代理所生成的代理对象。
所以,我们可以通过FactoryBean来向Spring容器中添加一个自定义的bean对象。上文中所定义的MyFactoryBean对应的就是UserMapper,表示我们定义了一个MyFactoryBean,相当于把UserMapper对应的代理对象作为一个bean放入到了容器中。
但是作为程序员,我们不可能每定义了一个Mapper,还得去定义一个MyFactoryBean,这是很麻烦的事情,我们改造一下MyFactoryBean,让他变得更通用,比如:
@Component
public class MyFactoryBean implements FactoryBean {
// 注意这里
private Class mapperInterface;
public MyFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 执行代理逻辑
return null;
}
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
改造MyFactoryBean之后,MyFactoryBean变得灵活了,可以在构造MyFactoryBean时,通过构造传入不同的Mapper接口。
实际上MyFactoryBean也是一个Bean,我们也可以通过生成一个BeanDefinition来生成一个MyFactoryBean,并给构造方法的参数设置不同的值,比如伪代码如下:
BeanDefinition bd = new BeanDefinitoin();
// 注意一:设置的是MyFactoryBean
bd.setBeanClassName(MyFactoryBean.class.getName());
// 注意二:表示当前BeanDefinition在生成bean对象时,会通过调用MyFactoryBean的构造方法来生成,并传入UserMapper
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(bd);
特别说一下注意二,表示表示当前BeanDefinition在生成bean对象时,会通过调用MyFactoryBean的构造方法来生成,并传入UserMapper的Class对象。那么在生成MyFactoryBean时就会生成一个UserMapper接口对应的代理对象作为bean了。
到此为止,其实就完成了我们要解决的问题:把MyBatis中的代理对象作为一个bean放入Spring容器中。只是我们这里是用简单的JDK代理对象模拟的MyBatis中的代理对象,这是为了让我们快速了解Spring将MyBatis的Mapper代理对象加入Spring容器的原理,在后面的章节我们会针对实际的源码进行分析。
2.2.2.1.3.2 Import
到这里,我们还有一个事情没有做,就是怎么真正的定义一个BeanDefinition,并把它添加到Spring中,上文说到我们要利用Import技术,比如可以这么实现:
定义如下类:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 创建BeanDefinition构造器对象
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 通过构造器创建一个BeanDefinition
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
beanDefinition.setBeanClass(MyFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
// 添加beanDefinition
registry.registerBeanDefinition("my"+UserMapper.class.getSimpleName(), beanDefinition);
}
}
并且在JavaConfig类AppConfig上添加@Import注解:
@Import(MyImportBeanDefinitionRegistrar.class)
public class AppConfig {
}
这样在启动Spring时就会新增一个BeanDefinition,该BeanDefinition会生成一个MyFactoryBean对象,并且在生成MyFactoryBean对象时会传入UserMapper.class对象,通过MyFactoryBean内部的逻辑,相当于会自动生产一个UserMapper接口的代理对象作为一个bean。
2.2.2.1.4 小结
总结一下,通过我们的分析,我们要整合Spring和Mybatis,需要我们做的事情如下:
- 定义一个MyFactoryBean
- 定义一个MyImportBeanDefinitionRegistrar
- 在AppConfig上添加一个注解@Import(MyImportBeanDefinitionRegistrar.class)
这样就可以基本完成整合的需求了,当然还有两个点是可以优化的。
第一,单独再定义一个@MyScan的注解,如下:
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyScan {
}
这样在AppConfig上直接使用@MyScan即可
第二,在MyImportBeanDefinitionRegistrar中,我们可以去扫描Mapper,在MyImportBeanDefinitionRegistrar我们可以通过AnnotationMetadata获取到对应的@MyScan注解,所以我们可以在@MyScan上设置一个value,用来指定待扫描的包路径。然后在MyImportBeanDefinitionRegistrar中获取所设置的包路径,然后扫描该路径下的所有Mapper,生成BeanDefinition,放入Spring容器中。
所以,到此为止,Spring整合Mybatis的核心原理就结束了,再次总结一下:
- 定义一个MyFactoryBean,用来将Mybatis的代理对象生成一个bean对象
- 定义一个MyImportBeanDefinitionRegistrar,用来生成不同Mapper对象的MyFactoryBean
- 定义一个@MynScan,用来在启动Spring时执行MyImportBeanDefinitionRegistrar的逻辑,并指定包路径
以上这个三个要素分别对象org.mybatis.spring中的:
- MapperFactoryBean
- MapperScannerRegistrar
- @MapperScan
经过上面的分析,相信大家已经明白了将Mapper代理对象加入到Spring容器的原理,下面就带大家分析真实的源码。
2.2.2.2 创建Mapper接口的代理对象,并将其注册到Spring容器的源码分析
Mapper接口扫描是通过@MapperScan和@MapperScans注解中导入了MapperScannerRegistrar来完成的,Mapper接口注册到Spring容器中是借助FactoryBean来完成的,注解中设置了factoryBean注册Mapper接口的默认值为MapperFactoryBean。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
String lazyInitialization() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
public @interface MapperScans {
MapperScan[] value();
}
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,在容器扫描BeanDefinition时会调用registerBeanDefinitions。其作用其实就是为容器注册一个或多个MapperScannerConfigurer以及给该类设置一些从@MapperScan注解上解析出来的属性。@MapperScans注解上可以配置多个@MapperScan注解在导入时通过RepeatingRegistrar遍历注册多个MapperScannerConfigurer。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 从传入的配置类中解析@MapperScan注解信息,把MapperScan注解的属性转化为AnnotationAttributes类型
AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
// 若上一步解析出来的mapperScanAttrs不为空,说明配置类上加了@MapperScan注解
if (mapperScanAttrs != null) {
// 调用重写的方法registerBeanDefinitions#generateBaseBeanName(importingClassMetadata, 0)即将注册的bean定义的名称进行处理
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
// 创建bean定义构造器通过够构造器来构建出的bean定义<MapperScannerConfigurer>应用到的设计模式[建造者模式]
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// 手动为MapperScannerConfigurer开启processPropertyPlaceHolders属性为true,需要着重研究下MapperScannerConfigurer类的继承结构
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 为的MapperScannerConfigurer解析@MapperScanner指定扫描的的注解类型
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
// 是否配置了标记接口
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
// 是否设置了MapperScannerConfigurer的beanName生成器对象
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
// 解析@MapperScan注解属性MapperFactoryBean设置到MapperScannerConfigurer声明一个自定义的MapperFactoryBean返回一个代理对象
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
// 解析@MapperScan的sqlSessionTemplateRef到底使用是哪个sqlSessionTemplate设置到MapperScannerConfigurer多数据源的情况下需要指定
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
// 解析@MapperScan的sqlSessionFactoryRef属性 设置到 MapperScannerConfigurer 多数据情况下的话 ,需要指定使用哪个 sqlSessionFactory
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
// 解析@MapperScan扫描的的包或者是class对象
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
String lazyInitialization = annoAttrs.getString("lazyInitialization");
// 指定MapperScannerConfigurer是否为懒加载
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 为的容器中注册了MapperScannerConfigurer的接口
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
}
static class RepeatingRegistrar extends MapperScannerRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScansAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
for (int i = 0; i < annotations.length; i++) {
registerBeanDefinitions(annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));
}
}
}
}
}
MapperScannerConfigurer中显示创建了ClassPathMapperScanner包扫描器对象,其继承自ClassPathBeanDefinitionScanner,其作用是为了扫描@MapperScan注解中配置包路径下的目标类。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders(); // 若MapperScannerConfigurer属性的processPropertyPlaceHolders为ture时则执行processPropertyPlaceHolders(),解析动态参数
}
// 显示的new一个ClassPathMapperScanner包扫描器对象该对象继承了spring的ClassPathBeanDefinitionScanner为了扫描器指定@MapperScan属性
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(); // 注册扫描规则过滤器
// 真正的去扫描@MapperScan指定的路径下的bean定义信息,先会去调用ClassPathMapperScanner.scan()方法
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
private void processPropertyPlaceHolders() {
// 因为postProcessBeanDefinitionRegistry是为注册bean定义的,但注册bean定义时需要解析${basepackaage},但PropertyResourceConfigurer类型的bean定义还没有实例化成bean对象 ,故不能提供解析${basepackaage}的能力
// 故显示的设置processPropertyPlaceHolders为ture,即想通过applicationContext.getBeansOfType(PropertyResourceConfigurer.class),提前把PropertyResourceConfigurer组件实例化出来从而解析${basepackaage}
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
// 判断PropertyResourceConfigurer的集合不为空,且applicationContext是ConfigurableApplicationContext
if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
// 通过名称去容器中获取MapperScannerConfigurer组件
BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBeanDefinition(beanName);
// PropertyResourceConfigurer类没有暴露任何的方法来处理的property placeholder substitution即${basepackaage},有且只有提供一个BeanFactoryPostProcessor接口来处理的bean定义
// 但调用BeanFactoryPostProcessor.postProcessBeanFactory()方法需要一个beanFactory,若从外面传入一个Ioc的容器进来会提前破坏ioc容器,故这里创建了一个临时的ioc容器,然后把mapperScannerBean注册到该临时ioc容器中
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(beanName, mapperScannerBean);
for (PropertyResourceConfigurer prc : prcs.values()) {
//这时就可以大胆放心的处理临时的ioc容器factory中的bean定义,即当前的mapperScannerBean
prc.postProcessBeanFactory(factory);
}
// 处理完之后重新获取通过PropertyResourceConfigurer解析后的mapperScannerBean的属性
PropertyValues values = mapperScannerBean.getPropertyValues();
// 更新MapperScannerBean属性可能有${}包裹的字段
this.basePackage = updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
}
this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);
this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName).map(getEnvironment()::resolvePlaceholders).orElse(null);
this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders).orElse(null);
}
}
首先会根据在@MapperScan注解上设置的注解类annotationClass、以及markerInterface来添加过滤器,若未设置将会将包下的所有接口扫描出来。
在调用超类ClassPathBeanDefinitionScanner的scan方法扫描包时会调用子类ClassPathMapperScanner中的doScan,而该类又去调用了超类的doScan方法,但有一点不同的是这复写了isCandidateComponent方法只扫描接口,而Spring中默认恰好相反。
当将所有符合条件的Mapper接口的BeanDefinition信息扫描出来后,进行遍历将beanClass设置成MapperFactoryBean的Class,从而达到偷天换日将Mapper接口通过MapperFactoryBean来创建生成Bean。因为接口是不能被实例化生成Bean的。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
// 注册扫描规则过滤器
public void registerFilters() {
boolean acceptAllInterfaces = true;
// 若annotationClass不为空,表示用户设置了此属性,则根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是 AnnotationTypeFiter,其保证在扫描对应Java文件时只接受标记有注解为annotationClass的接口
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// 如果markerlnterface不为空,表示要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是实现AssignableTypeFiter接口的局部类,扫描过程中只有实现markerIntrface接口的接口才会被接受
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
// 若接受所有接口,则添加自定义INCLUDE过滤器TypeFilter,全部返回true
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// 对于命名为package-info Java文件,默认不作为逻辑实现接口,将其排除掉,使用TypeFiltler接口的局部类实现match 法
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类ClassPathBeanDefinitionScanner来进行扫描
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
// 若扫描后mapper包下有接口类,则扫描bean定义就不会为空
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// 这里是将Mapper接口注册到Spring容器中的关键
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
// 循环所有扫描出mapper的bean定义出来
for (BeanDefinitionHolder holder : beanDefinitions) {
// 获取的bean定义
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 获取的bean定义的名称
String beanClassName = definition.getBeanClassName();
// 设置ConstructorArgumentValues会通过构造器初始化对象
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 进行真的偷天换日操作,将当前Bean的Class设置成MapperFactoryBean的Class
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// 为的Mapper对象绑定sqlSessionFactory引用,实际上是为MapperFactoryBean添加一个sqlSessionFactory的属性
// 然后SpringIoc在实例化MapperFactoryBean时为通过populate()为UserMapper(MapperFactoryBean)的sqlSessionFactory属性赋值,调用set方法
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;
}
// 同上sqlSessionFactory
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
// 将sqlSessionTemplate通过AUTOWIRE_BY_TYPE自动装配
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
// 设置UserMapper<MapperFactoryBean>定义的注入模型是通过包扫描进来的,所有默认注入模型为AutowireCapableBeanFactory.AUTOWIRE_NO=0标识通过@Autowire注解注入
// 因为字段上是没有@AutoWired注解,则MapperFactoryBean的字段属性永远自动注入不了值,故需要把UserMapper<MapperFactoryBean>bean定义的注入模型给改成的AUTOWIRE_BY_TYPE=1表示根据类型自动装配
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
// 设置bean定义的加载模型(是否为懒加载)
definition.setLazyInit(lazyInitialization);
}
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
对于MapperFactoryBean代码就比较简单了,上面在给BeanDefinition设置属性时,给构造函数参数列表中添加了一个参数即原始Mapper类名,故实例化MapperFactoryBean时会调用有参构造函数。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
public Class<T> getObjectType() {
return this.mapperInterface;
}
}
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public <T> T getMapper(Class<T> type) {
// 最终去sqlSessionFactory.Configuration.mapperRegistry去获取我们的Mapper对象
return getConfiguration().getMapper(type, this);
}
}
2.3 总结流程图
三、Spring Boot整合MyBatis
接下来我们学习下在SpringBoot 中整合MyBatis。其实基本原理和上面讲的Spring是一样的,就不重复讲了,这里我们就介绍一下Spring Boot整合MyBatis的使用方法。
首先我们需要新建一个Spring Boot项目,新建项目的细节再此处不详细说明。新建完项目之后之后我们首先需要添加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
然后在application.yml中添加相关的数据源,MyBatis的相关信息
#DataSource config 配置数据源信息
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisdemo?useUnicode=true&characterEncoding=utf-8
username: root
password: admin
#配置MyBatis的配置文件地址和MyBatis的映射文件地址
mybatis:
config-location: MyBatis-cfg.xml
mapper-locations: mybatis/*.xml
接着我们还需要在启动类MybatisSpringbootDemoApplication中添加dao接口的扫描信息
@SpringBootApplication
@MapperScan(value = "com.jay.mapper")
public class MybatisSpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisSpringbootDemoApplication.class, args);
}
}
最后我们看看测试类
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class StudentMapperTest {
// 自动注入Mapper对象
@Autowired
private StudentMapper studentMapper;
@Test
public void selectStudentById() throws Exception {
Student student = studentMapper.selectStudentById(1);
System.out.println("---->查询结果为={}"+student);
}
}
测试结果如下:
通过上面的这个demo,我们能看出来Spring Boot的配置相对Spring较少,这与SpringBoot的理念相关,约定大于配置,注解多于配置。
相关文章:【MyBatis】MyBatis缓存原理详解-CSDN博客
【MyBatis】MyBatis的一级缓存和二级缓存简介-CSDN博客