其中basePackage就是我们配置的Mapper文件所在的包,我们追踪下代码来看一下,MapperScannerConfigurer是如何通过包扫描的方式将我们的Mapper保存到Configuration。
<!-- mybatis操作接口扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="annotationClass" value="javax.annotation.Resource" />
<property name="basePackage" value="com.xxx.md.xxx.xxx.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
1.1 MapperScannerConfigurer 类 该类的主要作用是初始化我们配置的参数,并调用方法扫描配置的包。
从上面代码我们可以看到真正执行扫描的是ClassPathMapperScanner类的Scan方法。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
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));
}
1.2 ClassPathMapperScanner
ClassPathMapperScanner类继承了Spring提供的ClassPathBeanDefinitionScanner类。Scan方法的实现在父类中。
在执行scan()方法时,会执行doScan()方法,这个方法在ClassPathMapperScanner类中有具体实现。
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
该方法首先会执行父类中的doScan()方法,父类中的doScan()方法也即Spring的包扫描,根据注解生成对象的方法,从源码中我们也可以看到在MapperScannerConfigure中配置的annotationClass是如何起到过滤作用的,如果不配置这个参数,会根据Spring中的默认注解生成对象,配置这个参数,我们可以指定我们自定义的注解。简单来说Spring在生成对象的时候会有一个注解过滤器进行过滤,使用特定注解的类,才能由Spring生成对象,这也解释了我们常用的注解起作用的过程。
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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
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) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
我们关键看一下这三行代码,很明显由Spring包扫描的Mapper接口添加到了MapperFactoryBean中。 1.3 MapperFactoryBean类 MapperFactoryBean类实现了FactroyBean类,其中主要起作用的方法是cheakDaoConfig()。
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
- definition.getPropertyValues().add("addToConfig", this.addToConfig);
在这个方法中我们看到一行熟悉的代码,configuration.addMapper(this.mapperInterface);从源码解析一中我们知道这行代码的实际作用就是将Mapper接口使用JDK动态代理技术生成代理对象,并添加到Configuration中。
@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 (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
根据以上三个类的源码解析,我们已经看到Mybatis已经实现了通过包扫描的方式生成Mapper接口的代理对象。下面我们再了解下Mapper接口动态代理对象起作用的过程。
二、Mapper动态代理对象 MapperProxy的方法执行过程
我们知道使用JDK动态代理技术生成的动态代理对象,方法在执行时,会执行代理的invoke()方法。
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);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
从代码中我们可以看到如果mapper是一个接口实现对象的话会执行method.invoke()方法,但我们使用的mapper实际是一个接口,所以会执行生成MapperMethod对象,并执行excute()方法。简单理解为,我们执行方法时,会生成代理方法,并执行代理方法。
研究MapperMethod源码我们可以发现,Mybatis会根据我们传递的参数,分析方法的签名、返回值、参数名、参数类型、参数顺序以及获取绑定的mapper.xml文件中对应的SQL(mapper.xml文件与Mapper接口的绑定,会在第三篇博客中介绍)。
我们看下MapperMethod中的excute()方法。
上面代码很清晰的显示,根据绑定的不同的sql,执行不同的方法,我们简单看一个查询多个结果的方法,excuteForMany();
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
上述代码很清晰的表明最后执行查询的依然是我们熟悉的SqlSession对象