0%

Mybatis-Spring核心源码分析

简介

在最开始接触Mybatis之前就好奇Mybatis是如何将我们执行我们定义的接口?其如何和我们编写的xml关联起来,最近又带着这个疑问分析了一下Mybatis Spring的源码,发现其核心也不复杂就是java 的动态代理。

配置入口

依旧是老套路,我们先从入口分析,Mybatis需要使用@Mapper注解来在Spring中注册,我们只需要全局搜索看在哪里调用打@Mapper即可,在MybatisAutoConfiguration中找到了内部类以下方法

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
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

private BeanFactory beanFactory;

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}

logger.debug("Searching for mappers annotated with @Mapper");

List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors())
// Need to mybatis-spring 2.0.2+
.filter(x -> x.getName().equals("lazyInitialization")).findAny()
.ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}

@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

}

其实现了ImportBeanDefinitionRegistrar 接口,并且在外部类的配种中使用了@Import注解,那么在其初始化时就会调用registerBeanDefinitions方法,我们看到核心代码就是其在Spring中注册了一个MapperScannerConfigurer的类,我们继续进去查看

1
2
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

其实现了BeanDefinitionRegistryPostProcessor,InitializingBean这两个接口,其中BeanDefinitionRegistryPostProcessor在Bean被定义前没调用,而InitializingBean在初始化后被调用且InitializingBean并没有太多逻辑,所以我们重点查看BeanDefinitionRegistryPostProcessor的接口方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
Mapper的扫描

我们看到这里新建了一个类ClassPathMapperScanner,并且调用了scan方法,看类命名我们就猜到其应该就是扫描@Mapper的地方,我们继续进去

1
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

发现其继承了ClassPathBeanDefinitionScanner类,而ClassPathBeanDefinitionScanner类时Spring 扫描类的核心,其会扫描配置路径下的所有类,这也印证了我们之前的猜想。我们寻该类型scan相关方法,发现其调用了父类的scan方法,父类又调用了doScan方法,而其重写了doScan方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@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 {
processBeanDefinitions(beanDefinitions);
}

return beanDefinitions;
}

super.doScan 方法会扫描包下所有包含我们过滤条件的所有的类,并且将其注册到Spring,而在AutoConfiguredMapperScannerRegistrar配置类中,我们已经将我们需要扫描的注解@Mapper加入进来了,并且在MapperScannerConfigurer#postProcessBeanDefinitionRegistry中初始化了拦截器

1
builder.addPropertyValue("annotationClass", Mapper.class);

我们顺着逻辑继续查看processBeanDefinitions方法,上面说到ClassPathBeanDefinitionScanner的doScan方法会扫描所有符合条件的类,并且注册到Spring,我们这里执行的了doScan方法,我们定义的接口已经被注册到Spring中了,如果我们这时候去调用肯定会报错,因为根本没有实现类,于是Mybatis投机取巧在processBeanDefinitions下修改BeanDefinition强行将@Mapper扫描到的接口关联到他的代理类中

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
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");

// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);

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

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

if (StringUtils.hasText(this.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) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}

if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}

这里 很多逻辑,我们并不需要全部都看懂,具体注意以下两点

1
2
3
4
5
6
7
8
9
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
.
.
.
definition.setBeanClass(this.mapperFactoryBeanClass);
.
.
.
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

它将我们@Mapper扫描的类定义强行替换成MapperFactoryBean,且将其Spring的注入类型改成根据类型自动注入,这样我们去Spring中获取相关类时只要有get set方法,Spring就能自动根据类型注入,不需要再去使用注解或者手动去注入,在刚开始时我一直没找到类属性的输入点,知道看到这里才恍然大悟

所以这时候我们查看MapperFactoryBean的逻辑,第一步查看他的继承结构

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

其继承了SqlSessionDaoSupport 而SqlSessionDaoSupport又实现了InitializingBean,在Bean初始化时会去调用checkDaoConfig方法

而MapperFactoryBean重写了checkDaoConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();

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

我们继续进入 configuration.addMapper(this.mapperInterface); 方法一直往下找找到了MapperRegistry#addMapper方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

而这里就是解析Spring 我们编写xml的地方,并且解析完成后注册到Configuration的mapperRegistry中

1
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
Mapper对象的查找和注入

知道了xml是如何解析的,当时Mapper对象是如何注入的呢,我们现在知道了其是我们在Spring中注册的并不是我们定义的对象而是MapperFactoryBean ,我们查看其类的继承关系

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

他继承了Spring的FactoryBean,之前网上只说FactoryBean可以实现懒加载,当时并不理解,知道看了这里的代码才知道,FactoryBean不止可以实现懒加载,还可以实现Bean对象实例化前的增强,和BeanPostProcessor异曲同工,而不同的是FactoryBean可以针对单个具体Bean对象做增强,而BeanPostProcessor需要遍历一遍所有Bean对象然后选择自己需要增强的Bean效率较低。

我们顺着FactoryBean定义的接口getObject往下找,在Spring去查找Mapper对象时他回去调用FactoryBean的getObject对象去获取对象

1
2
3
4
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

继续往下我们发现其本质就是调用了Configuration下的mapperRegistry去获取对象的,而这个对象在我们之前解析xml时写入到mapperRegistry中

1
2
3
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

继续进入mapperRegistry.getMapper方法

1
2
3
4
5
6
7
8
9
10
11
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}

再进入mapperProxyFactory.newInstance方法

1
2
3
4
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

再进入newInstance方法,发现其本质就是利用了java的反射

1
2
3
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

而MapperProxy类是其的代理类,所以我们使用Mybatis Spring时我们调用的就是使用动态代理的MapperProxy,而其根本没有调用原始声明的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
总结

Mybatis Spring就是使用了java 的动态代理机制和Spring FactoryBean 增强机制,在Spring进行依赖注入时,使用动态代理技术,代理到我们代理的类上,其去执行sql语句,这也就是为什么我们去定义Mapper时只能用接口,这是由于java的动态代理只能使用接口而导致的,且利用依赖注入和动态代理的组合,往往能做到很多意想不到且有意思的事。