《spring源码深度解析》spring Jdbc(Mybatis)

———— 5.1.3.RELEASE
words: 3.3k    views:    time: 17min

Spring的jdbc模块中处理除了提供了JdbcTemplate等封装工具,还提供了对各种第三方持久层框架的封装,比如Mybatis,Hibernate,帮助开发者可以更高效率的进行业务开发,这里主要分析下spring中如何对mybatis实现的封装。关于Mybatis,可以参考相关笔记:[Mybatis]

示例

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {

private long id;

private String name;

public User(long id, String name){
this.id = id;
this.name = name;
}

// ...
}
UserMapper.java
1
2
3
4
5
6
public interface UserMapper {

void save(User user);

List<User> query();
}
bean.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- DataSource有很多第三方实现,这里直接使用简单DriverManagerDataSource,实际上没有缓存 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://192.168.141.13:3306/demo" />
<property name="username" value="shanhm" />
<property name="password" value="shanhm" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="mybatis-config.xml" />
</bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="test.sm.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
</beans>
mybatis-config.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
<typeAliases>
<typeAlias alias="User" type="test.sm.User" />
</typeAliases>

<mappers>
<mapper resource="test/sm/UserMapper.xml" />
</mappers>
</configuration>
UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 调用时传入sql的id为namespace.sqlId,
所以如果使用代理的话namespace应该配置成接口类全路径,sqlId应该配成方法名,否则创建的代理无法定位要执行的sql是哪一个 -->
<mapper namespace="test.sm.UserMapper">
<select id="save" resultType="User" >
select id, name from user
</select>

<insert id="query" parameterType="User" >
insert into user(id, name) values (#{name}, #{age})
</insert>
</mapper>

spring对于Mybatis的两种使用方式都提供了支持,一种是直接使用模板SqlSessionTemplate,其中帮我们代理了SqlSession的创建和关闭,并考虑了事务问题,另一种是直接将接口对应的代理注册到容器之中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class App {

public static void main( String[] args ) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = new User(1, "shanhm");

SqlSessionTemplate sqlSessionTemplate = context.getBean(SqlSessionTemplate.class);
sqlSessionTemplate.insert("test.sm.UserMapper.save", user);
sqlSessionTemplate.selectList("test.sm.UserMapper.query");

UserMapper userMapper = context.getBean(UserMapper.class);
userMapper.save(new User(1, "shanhm"));
List<User> userList = userMapper.query();
}
}

实现

SqlSessionFactoryBean

SqlSessionFactory是Mybatis的基础,所以封装了SqlSessionFactory初始化的SqlSessionFactoryBean也就是spring代理Mybatis的基础,其实它是通过实现对应的事件接口将SqlSessionFactory的初始化加入到了spring容器的初始化过程中,,具体可以从其实现的接口进行分析

  • InitializingBean

实现InitializingBean的bean,在初始化过程中,当设置完属性时会调用其afterPropertiesSet方法,这里的实现中首先检查了下dataSource有没有设置,然后初始化一个SqlSessionFactory,另外对Mybatis的Configuration尽量做了一些支持,就是说可以直接将mybatis-config.xml中的配置配成SqlSessionFactory的属性,然后在初始化时它会尝试获取和填充;

  • FactoryBean

实现FactoryBean就是将上面构造的SqlSessionFactory实例注册到spring容器中

  • ApplicationListener

实现ApplicationListener是为在spring启动时触发对Mapper做一些初始化,其实就是提前检查,如果有错误可以及时失败,而不用等到真正调用时才发现;

org.mybatis.spring.SqlSessionFactoryBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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();
}

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

Configuration configuration;

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 (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}

if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}

if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}

if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}

if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}

if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}

if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}

if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}

if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}

if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}

if (this.cache != null) {
configuration.addCache(this.cache);
}

if (xmlConfigBuilder != null) {
try {
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 (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}

configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

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

try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}

if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}

return this.sqlSessionFactoryBuilder.build(configuration);
}

public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}

return this.sqlSessionFactory;
}

public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
SqlSessionTemplate

SqlSessionTemplate作为SqlSession的使用模板,其作用类似于之前的JdbcTemplate,帮我们封装了SqlSeesion的创建与关闭并考虑了事务问题,使我们可以更方便简洁的使用SqlSession

其实它是创建了一个对自身的代理sqlSessionProxy,但是在回调中对于操作并没有调用自己,而是对真正创建出来的SqlSession的调用(不然会出现调用死循环),另外就是对调用做了一些前后处理。

org.mybatis.spring.SqlSessionTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}

private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
MapperFactoryBean

Mybatis还有一种使用方式,就是直接创建目标接口的代理实现,spring则提供了MapperFactoryBean用来帮忙将其注册到容器中,具体是通过实现FactoryBean实现的,另外它还实现了InitializingBean,主要是为了在初始化时检查SqlSession有没有创建,以及利用addMapper检查接口对应的Mapper有没有问题。

另外,这里会发现MapperFactoryBean在注册属性时其实初始化的也是SqlSessionTemplate,就是说也可以跳过接口代理,直接获取容器中的MapperFactoryBean实例,然后获取它的SqlSessionTemplate直接进行sql查询,像上面一样

org.springframework.dao.support.DaoSupport
1
2
3
4
5
6
7
8
9
10
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();

try {
initDao();
}catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
org.mybatis.spring.mapper.MapperFactoryBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}

protected void checkDaoConfig() {
super.checkDaoConfig(); // 检查sqlSession有没有设置

notNull(this.mapperInterface, "Property 'mapperInterface' is required");

Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
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();
}
}
}

public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

MapperScannerConfigurer

上面的方式都有一个缺点,就是当需要配置的映射器比较多时会显得麻烦,因此spring提供了MapperScannerConfigurer来帮我们实现自动扫描指定包下的接口,比如可以将示例中配置的Mapper换成:

bean.xml
1
2
3
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="test.sm" />
</bean>

其主要是实现了接口BeanDefinitionRegistryPostProcessor,所以当BeanDefinition注册时会触发postProcessBeanDefinitionRegistry,然后在实现中构造了一个ClassPathMapperScanner,并将扫描和注册Mapper映射器的任务委托给它,至于扫描时的过滤规则在构造时确定,默认是所有接口,如下面代码所示

org.mybatis.spring.mapper.MapperScannerConfigurer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

ClassPathMapperScanner

ClassPathMapperScanner继承自ClassPathBeanDefinitionScannerClassPathBeanDefinitionScanner前面已经介绍过,参考笔记:[spring. 应用上下文(ApplicationContext)],这里主要是覆盖了isCandidateComponent,改成了只要是接口就通过,另外在doScan之后加入了processBeanDefinitions操作,其实就是注册一个MapperFactoryBeanBeanDefinition,并且可以指定构建时使用的sqlSessionFactory,如果没有指定则自动注入,但是当应用中涉及到多个数据源时,还是要手动指定的,不然spring无法知道应该注入哪一个

org.mybatis.spring.mapper.ClassPathMapperScanner
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public void registerFilters() {
boolean acceptAllInterfaces = true;

// 指定注解标识了的接口
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}

// 或者指定父接口过滤
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}

// 否则接受所有接口
if (acceptAllInterfaces) {
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}

// 排除掉package-info.java
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}

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 {
processBeanDefinitions(beanDefinitions);
}

return beanDefinitions;
}

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();

if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}

// 构建MapperFactoryBean的BeanDefinition,名称是目标接口名称,但实际class是MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());

definition.getPropertyValues().add("addToConfig", this.addToConfig);

boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { // 指定了sqlSessionFactoryBeanName
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) { // 指定了 sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}

if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { // 指定了sqlSessionTemplateBeanName
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) { // 指定了sqlSessionTemplate
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}

if (!explicitFactoryUsed) { // 啥也没指定,就自动注入
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}

MapperScannerRegistrar

MapperScannerRegistrar也是用来自动扫描目标路径下的接口的,不过相对于上面通过xml配置的方式,它是通过注解实现的。具体则是实现了ImportBeanDefinitionRegistrar,然后定义@MapperScan,并用@Import进行标识,同时指定了引入配置类为MapperScannerRegistrar,这样只要将@MapperScan标识到任何一个被spring扫描的bean上就可以执行MapperScannerRegistrar中的逻辑了,事实也是委托给了ClassPathMapperScanner,与上面一样。

对于@MapperScan的实现,即如何能在启动时就知道要扫描哪些目录下的哪些bean,并进行注册,前面有过详细分析,可以参考笔记:[spring. 应用上下文(ApplicationContext)]


参考:

  1. 《spring源码深度解析》 郝佳