分析Mybatis如何利用Spring的擴展點集成到框架中的,Mybatis自己的擴展點再也不本次分析範疇html
上Github上下載https://github.com/mybatis/spring。經過Git的方式試了幾回沒成功,後來直接Down的zip包,再導入的Idea中的。java
導入的過程中會有點慢,要下載很多東西。記得必定要修改Maven的配置文件和本地倉庫地址,不然可能以前你已經下過的相關包會又下載到C盤的本地倉庫當中mysql
直接在源碼目錄下新建了一個目錄來寫測試代碼git
測試類github
@Configuration @MapperScan("com.jv.mapper") @ComponentScan("com.jv.scan") public class TestMybatis { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestMybatis.class); UserService bean = ac.getBean(UserService.class); System.out.println(bean.query()); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); PooledDataSource dataSource = new PooledDataSource(); dataSource.setDriver("org.mariadb.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://192.168.10.12:3306/acct?useSSL=false&serverTimezone=UTC"); dataSource.setUsername("dev01"); dataSource.setPassword("12340101"); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } }
Service類 spring
@Service public class UserService { @Autowired private UserMapper userMapper; public List<User> query(){ return userMapper.query(); } }
Mapper類sql
public interface UserMapper { @Select("SELECT name,age FROM user") List<User> query(); }
實體類mybatis
@ToString public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } private Integer age; }
注意:運行以前必定要修改pom.xml。由於Mybatis導入的Spring相關的依賴不在運行時生效app
<scope>provided</scope>所有註釋掉,不然運行的時候會報好不到類框架
表結構:
CREATE TABLE `user` (
`name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`age` int(4) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
insert into user(name,age) values("Messi",35);
commit;
從mybatis-spring官方文檔中能夠找到@MapperScan的用法:http://mybatis.org/spring/mappers.html
既然和Spring集成是經過@MapperScan完成的,那從它入手
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { .... }
其中@Import(MapperScannerRegistrar.class)是重點,再看MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { /** * {@inheritDoc} * * @deprecated Since 2.0.2, this method not used never. */ @Override @Deprecated public void setResourceLoader(ResourceLoader resourceLoader) { // NOP } /** * {@inheritDoc} */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { /** * 註冊一個MapperScannerConfigurer的BeanDefinition,MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor * BeanDefinitionRegistryPostProcessor接口的實現類,一旦放入Spring容器中,那麼在Spring容器啓動的時候它能夠擔任註冊本身須要BeanDefinition的功能 */ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); 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); } //自定義BeanNameGenerator Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass)); } //自定義MapperFactoryBean Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } //自定義sqlSessionTemplate String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef"); if (StringUtils.hasText(sqlSessionTemplateRef)) { builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef")); } //自定義sqlSessionFactory String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef"); if (StringUtils.hasText(sqlSessionFactoryRef)) { builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef")); } //生成全部的scan要掃描的基礎包路徑 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"); if (StringUtils.hasText(lazyInitialization)) { builder.addPropertyValue("lazyInitialization", lazyInitialization); } builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); //將MapperScannerConfigurer的BeanDefinition註冊 registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } }
MapperScannerRegistrar implements ImportBeanDefinitionRegistrar
這就是它集成到Spring的關鍵點,關於ImportBeanDefinitionRegistrar能夠參考http://www.javashuo.com/article/p-ccwckotr-q.html
MapperScannerRegistrar 在Spring容器初始化的時候完成從外部導入MapperScannerConfigurer類對應的BeanDefinition
MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor(又是Spring的另一個擴展點,也是擴展BeanDefinition註冊功能,但它要晚於ImportBeanDefinitionRegistrar生效,由於前者的做用觸發時機是Spring的ConfigurationClassPostProcessor Implements PriorityOrder。 這個類是掃描路徑下全部Mapper的關鍵類
/** * BeanDefinitionRegistryPostProcessor遞歸地從基本包中搜索接口並將它們註冊爲MapperFactoryBean * MapperFactoryBean很是重要,它實現了InitializingBean,Spring會讓Bean屬性設置完以後調用它的抽象方法afterPropertiesSet,完成一些初始化操做 */ 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); //默認是MapperFactoryBean,能夠是自定義的 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)); } .................省略部分代碼.................. }
ClassPathMapperScanner 繼承自 Spring.ClassPathBeanDefinitionScanner,重寫了doScan方法
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { .................省略部分代碼.................. /** * 重寫了ClassPathBeanDefinitionScanner的doScan方法,可是掃描工做仍是由父類的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 { processBeanDefinitions(beanDefinitions); } return beanDefinitions; } /** * 重寫了ClassPathBeanDefinitionScanner的processBeanDefinitions方法 * 完成BeanDefinition的屬性填充 * 其中的setAutowireMode=AbstractBeanDefinition.AUTOWIRE_BY_TYPE 是Spring根據類型完成自動注入的關鍵。 * @param beanDefinitions */ 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 /** * 自定義的Mapper接口只是Bean最初的類,當Spring初始化以後Bean的Class其實是MapperFactoryBean */ definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 //設置BeanClass爲MapperFactoryBean 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() + "'."); // 至關的重要,這就是Spring的根據類型進行依賴注入。 // @Autowired使用的時候,其實Spring默認是沒有自動注入的,也就是說autowireMode是AUTOWIRE_NO definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); } } .................省略部分代碼.................. }
上面的代碼完成了對basePackage的類掃描,Mybatis根據掃描生成的BeanDefinition作加強,至關重要的兩點:
1.BeanClass=MapperFactoryBean
也就是說:最開始Spring容器初始化完成以後,全部Mapper並無真正的實例化(能夠經過觀察XXXApplicationContext.beanFactory.FactoryBeanObjectCache中是否有對象),而是它們的FactoryBean已經完成了實例化。當須要Mapper時再建立,若是是單例,則將已經實例化的Bean放到FactoryBeanObjectCache中
2.autowireMode=AUTOWIRE_BY_TYPE
按類型自動注入和@Autowired沒直接聯繫,Spring默認是AUTOWIRE_NO,只不過Spring發現你用了@Autowired註解會自動根據類型注入而已。按類型自動注入必需要有setXXX方法。
表明了被掃描的類支持按類型實例化,爲何要設置這個值喃?咱們本身寫的Mapper牢牢是一個接口,爲何還要注入東西喃。。。根本緣由是MapperFactoryBean extends SqlSessionDaoSupport
SqlSessionDaoSupport裏面有兩個set方法,分別是:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
能夠驗證一下,將「definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)」 註釋掉,報錯:
表明着沒有注入成功,也驗證了AUTOWIRE_BY_TYPE的重要性
截止到這裏,Mybatis相關類的BeanDefinition(BeanClass=MapperFactoryBean)所有完成了註冊,接下來就是實例化。
實例化時有兩個很是重要的點:
1.前面說過由於添加到BeanDefinitionMap中的bd都是MapperFactoryBean,因此完成實例化的Mapper都是MapperFactoryBean,而不是真正的Mapper。可是在實例化MapperFactoryBean所須要的SqlSessionFactory,SqlSessionTemplate從哪裏獲取到喃。
首先在TestMybatis類中使用了@Bean註解,Spring對於@Bean和ImportBeanDefinitionRegistrar,前者會先處理,也就是說@MapperScan掃描到的類會晚於@Bean修飾的SqlSessionFactory註冊到容器中。因此Spring在實例化MapperFactoryBean的時候自動注入SqlSessionFactory是能夠成功的,具體源碼見:
根據BeanDefinition.propertValues+setterXXX標記的屬性獲取Bean,在這裏就是被setSqlSesstionFactory()標記的SqlSessionFactory屬性:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#autowireByType()
protected void autowireByType( String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) { TypeConverter converter = getCustomTypeConverter(); if (converter == null) { converter = bw; } Set<String> autowiredBeanNames = new LinkedHashSet<>(4); //過濾掉不須要自動注入的屬性,好比沒有setter方法啊,簡單類:Class,URL,Number等 String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); //循環處理屬性,最終會將要注入的對象獲取到並放入到PVS當中,等候後面的注入 for (String propertyName : propertyNames) { try { PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); // Don't try autowiring by type for type Object: never makes sense, // even if it technically is a unsatisfied, non-simple property. if (Object.class != pd.getPropertyType()) { MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); // Do not allow eager init for type matching in case of a prioritized post-processor. boolean eager = !PriorityOrdered.class.isInstance(bw.getWrappedInstance()); //獲取到Class對象 DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager); //獲取到Class對象 Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter); if (autowiredArgument != null) { pvs.add(propertyName, autowiredArgument); } for (String autowiredBeanName : autowiredBeanNames) { registerDependentBean(autowiredBeanName, beanName); if (logger.isTraceEnabled()) { logger.trace("Autowiring by type from bean name '" + beanName + "' via property '" + propertyName + "' to bean named '" + autowiredBeanName + "'"); } } autowiredBeanNames.clear(); } } catch (BeansException ex) { throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex); } } }
根據獲取到的Bean進行注入:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
/** * Populate the bean instance in the given BeanWrapper with the property values * from the bean definition. 用bean定義中的屬性值填充給定BeanWrapper中的bean實例 * @param beanName the name of the bean * @param mbd the bean definition for the bean * @param bw the BeanWrapper with bean instance */ @SuppressWarnings("deprecation") // for postProcessPropertyValues protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { 。。。。省略部分代碼。。。。 /** * 給任何InstantiationAwareBeanPostProcessors機會在屬性被設置以前修改Bean的狀態(屬性),好比設置特殊的屬性值, * 或者修改PropertyValues中的值 */ boolean continueWithPropertyPopulation = true; //第十二次PostProcessor InstantiationAwareBeanPostProcessors if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continueWithPropertyPopulation = false; break; } } } } if (!continueWithPropertyPopulation) { return; } PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); int resolvedAutowireMode = mbd.getResolvedAutowireMode(); //根據名稱或類型注入依賴 if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. // 經過屬性名稱注入依賴 BeanDefinition.autowireMode=AUTOWIRE_BY_NAME if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. // 經過屬性類型注入依賴 BeanDefinition.autowireMode=AUTOWIRE_BY_TYPE if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; //PostProcessor 和下面調用是一樣的效果,後者已通過期了,Spring5就是在這裏注入的, // -----------------好比對象和屬性值的注入------------------------ PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } //PostProcessor pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } 。。。。省略部分代碼。。。。 }
可是Spring容器中並無SqlSessionTemplate,爲何MapperFactoryBean最後也仍是有喃,由於在注入SqlSessionFactory的時候Mybatis自動實例化了它
public abstract class SqlSessionDaoSupport extends DaoSupport { 。。。。。註釋部分代碼。。。。 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); } } 。。。。。註釋部分代碼。。。。 }
2.執行DML所須要的信息從哪裏來?
接下來看看MapperFactoryBean的類圖
能夠看到最終實現了InitializingBean,Spring針對實現了該接口的Bean,在完成屬性填充以後會調用實現類的afterPropertiesSet()方法。
調用位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) throws Throwable { boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { 。。。。省略部分代碼。。。。 if (System.getSecurityManager() != null) { 。。。。省略部分代碼。。。。 } else { //執行實現了InitializingBean子類的afterPropertiesSet ((InitializingBean) bean).afterPropertiesSet(); } } //執行設置的InitMethod if (mbd != null && bean.getClass() != NullBean.class) { String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } }
來看看Mybatis的DaoSupport類的afterPropertiesSet()方法幹了什麼
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { //運行時會調用MapperFactoryBean的checkDaoConfig this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed", var2); } }
checkDaoConfig最終調用的是實現類MapperFactoryBean.checkDaoConfig方法
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)完成了MappedStatement添加。
configuration是DefaultSqlSesstionFactory(默認的)中的屬性,全局惟一
MappedStatement描述了一個要執行的SQL,參數、返回類型等等
經過這一步,調用真正的query方法所須要的東西都準備好了。
總結:Mybatis利用了@Import(class implements ImportBeanDefinitionRegistrar),BeanDefinitionRegistryPostProcessor,InitializingBean三個擴展點來完成整合。
其中Mybatis的以下幾個類很是重要:
MapperScan MapperScannerRegistrar MapperScannerConfigurer ClassPathMapperScanner MapperFactoryBean DefaultSqlSessionFactory Configuration MapperStatement