# 精尽 MyBatis 源码解析 —— Spring 集成(二)之初始化 # 1. 概述 在前面的,我们已经看了四篇 MyBatis 初始化相关的文章: - [《MyBatis 初始化(一)之加载 mybatis-config》](http://svip.iocoder.cn/MyBatis/builder-package-1) - [《MyBatis 初始化(二)之加载 Mapper 映射配置文件》](http://svip.iocoder.cn/MyBatis/builder-package-2) - [《MyBatis 初始化(三)之加载 Statement 配置》](http://svip.iocoder.cn/MyBatis/builder-package-3) - [《MyBatis 初始化(四)之加载注解配置》](http://svip.iocoder.cn/MyBatis/builder-package-4) 那么,本文我们就来看看,Spring 和 MyBatis 如何集成。主要涉及如下三个包: - `annotation` - `config` - `mapper` # 2. SqlSessionFactoryBean `org.mybatis.spring.SqlSessionFactoryBean` ,实现 FactoryBean、InitializingBean、ApplicationListener 接口,负责创建 SqlSessionFactory 对象。 使用示例如下: ``` ``` 另外,如果胖友不熟悉 Spring FactoryBean 的机制。可以看看 [《Spring bean 之 FactoryBean》](https://www.jianshu.com/p/d6c42d723464) 文章。 ## 2.1 构造方法 ``` // SqlSessionFactoryBean.java private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class); /** * 指定 mybatis-config.xml 路径的 Resource 对象 */ private Resource configLocation; private Configuration configuration; /** * 指定 Mapper 路径的 Resource 数组 */ private Resource[] mapperLocations; private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; //EnvironmentAware requires spring 3.1 private String environment = SqlSessionFactoryBean.class.getSimpleName(); private boolean failFast; private Interceptor[] plugins; private TypeHandler[] typeHandlers; private String typeHandlersPackage; private Class[] typeAliases; private String typeAliasesPackage; private Class typeAliasesSuperType; //issue #19. No default provider. private DatabaseIdProvider databaseIdProvider; private Class vfs; private Cache cache; private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory; // 省略 setting 方法 ``` - 是不是各种熟悉的属性,这里就不多解释每个对象了。你比我懂,嘿嘿。 ## 2.2 afterPropertiesSet `#afterPropertiesSet()` 方法,构建 SqlSessionFactory 对象。代码如下: ``` // SqlSessionFactoryBean.java @Override 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"); // 创建 SqlSessionFactory 对象 this.sqlSessionFactory = buildSqlSessionFactory(); } /** * Build a {@code SqlSessionFactory} instance. * * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a * {@code SqlSessionFactory} instance based on an Reader. * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file). * * @return SqlSessionFactory * @throws IOException if loading the config file failed */ protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; // 初始化 configuration 对象,和设置其 `configuration.variables` 属性 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 { LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } // 设置 `configuration.objectFactory` 属性 if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } // 设置 `configuration.objectWrapperFactory` 属性 if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } // 设置 `configuration.vfs` 属性 if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } // 设置 `configuration.typeAliasesPackage` 属性 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); LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases"); } } // 设置 `configuration.typeAliases` 属性 if (!isEmpty(this.typeAliases)) { for (Class typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'"); } } // 初始化 `configuration.interceptorChain` 属性,即拦截器 if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); } } // 扫描 typeHandlersPackage 包,注册 TypeHandler if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers"); } } // 如果 typeHandlers 非空,注册对应的 TypeHandler if (!isEmpty(this.typeHandlers)) { for (TypeHandler typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); } } // 设置 `configuration.databaseId` 属性 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); } } // 设置 `configuration.cache` 属性 if (this.cache != null) { configuration.addCache(this.cache); } // <1> 解析 mybatis-config.xml 配置 if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); 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(); } } // 初始化 TransactionFactory 对象 if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } // 设置 `configuration.environment` 属性 configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // 扫描 Mapper XML 文件们,并进行解析 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(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found"); } // 创建 SqlSessionFactory 对象 return this.sqlSessionFactoryBuilder.build(configuration); } ``` - 代码比较长,胖友自己耐心看下注释,我们只挑重点的瞅瞅。 - `<1>` 处,调用 `XMLConfigBuilder#parse()` 方法,解析 `mybatis-config.xml` 配置。即对应 [《MyBatis 初始化(一)之加载 mybatis-config》](http://svip.iocoder.cn/MyBatis/builder-package-1) 文章。 - `<2>` 处,调用 `XMLMapperBuilder#parse()` 方法,解析 Mapper XML 配置。即对应 [《MyBatis 初始化(二)之加载 Mapper 映射配置文件》](http://svip.iocoder.cn/MyBatis/builder-package-2) 。 - `<3>` 处,调用 `SqlSessionFactoryBuilder#build(Configuration config)` 方法,创建 SqlSessionFactory 对象。 - 另外,我们看到 `LOGGER` 的使用。那么,可能胖友会跟艿艿有一样的疑问,`org.mybatis.logging` 包下,又定义了 Logger 呢?因为想使用方法参数为 `Supplier` 的方法,即使用起来更加方便。这也是 `mybatis-spring` 项目的 `logging` 包的用途。感兴趣的胖友,可以自己去看看。 ## 2.3 getObject `#getObject()` 方法,获得 SqlSessionFactory 对象。代码如下: ``` // SqlSessionFactoryBean.java @Override public SqlSessionFactory getObject() throws Exception { // 保证 SqlSessionFactory 对象的初始化 if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; } ``` ## 2.4 getObjectType ``` // SqlSessionFactoryBean.java @Override public Class getObjectType() { return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); } ``` ## 2.5 isSingleton ``` // SqlSessionFactoryBean.java @Override public boolean isSingleton() { return true; } ``` ## 2.6 onApplicationEvent `#onApplicationEvent()` 方法,监听 ContextRefreshedEvent 事件,如果 MapperStatement 们,没有都初始化**都**完成,会抛出 IncompleteElementException 异常。代码如下: ``` // SqlSessionFactoryBean.java @Override public void onApplicationEvent(ApplicationEvent event) { if (failFast && event instanceof ContextRefreshedEvent) { // fail-fast -> check all statements are completed // 如果 MapperStatement 们,没有都初始化完成,会抛出 IncompleteElementException 异常 this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } } ``` - 使用该功能时,需要设置 `fastFast` 属性,为 `true` 。 ------ 在 `mybatis-spring` 项目中,提供了多种配置 Mapper 的方式。下面,我们一种一种来看。😈 实际上,配置 `SqlSessionFactoryBean.mapperLocations` 属性,也是方式之一。嘿嘿嘿。 # 3. MapperFactoryBean `org.mybatis.spring.mapper.MapperFactoryBean` ,实现 FactoryBean 接口,继承 SqlSessionDaoSupport 抽象类,创建 Mapper 对象。 使用示例如下: ``` ``` - 该示例来自 `org.mybatis.spring.sample.SampleMapperTest` 单元测试。胖友可以基于它调试。 ## 3.1 MapperFactoryBean ``` // MapperFactoryBean.java /** * Mapper 接口 */ private Class mapperInterface; /** * 是否添加到 {@link Configuration} 中 */ private boolean addToConfig = true; public MapperFactoryBean() { //intentionally empty } public MapperFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; } // 省略 setting 方法 ``` ## 3.2 checkDaoConfig ``` // MapperFactoryBean.java @Override protected void checkDaoConfig() { // <1> 校验 sqlSessionTemplate 非空 super.checkDaoConfig(); // <2> 校验 mapperInterface 非空 notNull(this.mapperInterface, "Property 'mapperInterface' is required"); // <3> 添加 Mapper 接口到 configuration 中 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(); } } } ``` - 该方法,是在 `org.springframework.dao.support.DaoSupport` 定义,被 `#afterPropertiesSet()` 方法所调用,代码如下: ``` // DaoSupport.java public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed", var2); } } ``` - 所以此处,就和 [「2.2 afterPropertiesSet」](https://svip.iocoder.cn/MyBatis/Spring-Integration-2/#) 的等价。 - `<1>` 处,调用父 `SqlSessionDaoSupport#checkDaoConfig()` 方法,校验 `sqlSessionTemplate` 非空。😈 关于 SqlSessionDaoSupport 抽象类,后续我们详细解析。 - `<2>` 处,校验 `mapperInterface` 非空。 - `<3>` 处,调用 `Configuration#addMapper(mapperInterface)` 方法,添加 Mapper 接口到 `configuration` 中。 ## 3.3 getObject `#getObject()` 方法,获得 Mapper 对象。**注意**,返回的是基于 Mapper 接口自动生成的代理对象。代码如下: ``` // MapperFactoryBean.java @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } ``` ## 3.4 getObjectType ``` // MapperFactoryBean.java @Override public Class getObjectType() { return this.mapperInterface; } ``` ## 3.5 isSingleton ``` // MapperFactoryBean.java @Override public boolean isSingleton() { return true; } ``` # 4. @MapperScan > 艿艿:本小节,需要胖友对 Spring IOC 有一定的了解。如果不熟悉的胖友,建议不需要特别深入的理解。或者说,先去看 [《精尽 Spring 源码解析》](http://svip.iocoder.cn/categories/Spring/) 。 `org.mybatis.spring.annotation.@MapperScan` 注解,指定需要扫描的包,将包中符合的 Mapper 接口,注册成 `beanClass` 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。代码如下: ``` // MapperScan.java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { /** * Alias for the {@link #basePackages()} attribute. Allows for more concise * annotation declarations e.g.: * {@code @MapperScan("org.my.pkg")} instead of {@code @MapperScan(basePackages = "org.my.pkg"})}. * * 和 {@link #basePackages()} 相同意思 * * @return base package names */ String[] value() default {}; /** * Base packages to scan for MyBatis interfaces. Note that only interfaces * with at least one method will be registered; concrete classes will be * ignored. * * 扫描的包地址 * * @return base package names for scanning mapper interface */ String[] basePackages() default {}; /** * Type-safe alternative to {@link #basePackages()} for specifying the packages * to scan for annotated components. The package of each class specified will be scanned. *

Consider creating a special no-op marker class or interface in each package * that serves no purpose other than being referenced by this attribute. * * @return classes that indicate base package for scanning mapper interface */ Class[] basePackageClasses() default {}; /** * The {@link BeanNameGenerator} class to be used for naming detected components * within the Spring container. * * @return the class of {@link BeanNameGenerator} */ Class nameGenerator() default BeanNameGenerator.class; /** * This property specifies the annotation that the scanner will search for. *

* The scanner will register all interfaces in the base package that also have * the specified annotation. *

* Note this can be combined with markerInterface. * * 指定注解 * * @return the annotation that the scanner will search for */ Class annotationClass() default Annotation.class; /** * This property specifies the parent that the scanner will search for. *

* The scanner will register all interfaces in the base package that also have * the specified interface class as a parent. *

* Note this can be combined with annotationClass. * * 指定接口 * * @return the parent that the scanner will search for */ Class markerInterface() default Class.class; /** * Specifies which {@code SqlSessionTemplate} to use in the case that there is * more than one in the spring context. Usually this is only needed when you * have more than one datasource. * * 指向的 SqlSessionTemplate 的名字 * * @return the bean name of {@code SqlSessionTemplate} */ String sqlSessionTemplateRef() default ""; /** * Specifies which {@code SqlSessionFactory} to use in the case that there is * more than one in the spring context. Usually this is only needed when you * have more than one datasource. * * 指向的 SqlSessionFactory 的名字 * * @return the bean name of {@code SqlSessionFactory} */ String sqlSessionFactoryRef() default ""; /** * Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean. * * 可自定义 MapperFactoryBean 的实现类 * * @return the class of {@code MapperFactoryBean} */ Class factoryBean() default MapperFactoryBean.class; } ``` - 属性比较多,实际上,胖友常用的就是 `value()` 属性。 - 重点是 `@Import(MapperScannerRegistrar.class)` 。为什么呢?`@Import` 注解,负责资源的导入。如果导入的是一个 Java 类,例如此处为 MapperScannerRegistrar 类,Spring 会将其注册成一个 Bean 对象。而 MapperScannerRegistrar 类呢?详细解析,见 [「4.1 MapperScannerRegistrar」](https://svip.iocoder.cn/MyBatis/Spring-Integration-2/#) 。 使用示例如下: ``` @Configuration @ImportResource("classpath:org/mybatis/spring/sample/config/applicationContext-infrastructure.xml") @MapperScan("org.mybatis.spring.sample.mapper") // here static class AppConfig { } ``` - 该示例来自 `org.mybatis.spring.sample.SampleEnableTest` 单元测试。胖友可以基于它调试。 ## 4.1 MapperScannerRegistrar `org.mybatis.spring.annotation.MapperScannerRegistrar` ,实现 ImportBeanDefinitionRegistrar、ResourceLoaderAware 接口,`@MapperScann` 的注册器,负责将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。 ### 4.1.1 构造方法 ``` // MapperScannerRegistrar.java /** * ResourceLoader 对象 */ private ResourceLoader resourceLoader; @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } ``` - 因为实现了 ResourceLoaderAware 接口,所以 `resourceLoader` 属性,能够被注入。 ### 4.1.2 registerBeanDefinitions ``` // MapperScannerRegistrar.java @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // <1> 获得 @MapperScan 注解信息 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); // <2> 扫描包,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象 registerBeanDefinitions(mapperScanAttrs, registry); } void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) { // <3.1> 创建 ClassPathMapperScanner 对象 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // <3.2> 设置 resourceLoader 属性到 scanner 中 // this check is needed in Spring 3.1 if (resourceLoader != null) { scanner.setResourceLoader(resourceLoader); } // <3.3> 获得 @(MapperScan 注解上的属性,设置到 scanner 中 Class annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); } Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass)); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); // <4> 获得要扫描的包 List 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())); // <5> 注册 scanner 的过滤器 scanner.registerFilters(); // <6> 执行扫描 scanner.doScan(StringUtils.toStringArray(basePackages)); } ``` - `<1>` 处,获得 `@MapperScan` 注解信息。 - `<2>` 处,调用 `#registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry)` 方法,扫描包,将扫描到的 Mapper 接口,注册成 `beanClass` 为 MapperFactoryBean 的 BeanDefinition 对象。 - ``` <3.1> ``` 处,创建 ClassPathMapperScanner 对象。下面的代码,都是和 ClassPathMapperScanner 相关。所以,详细的解析,最终看 「4.2 ClassPathMapperScanner」 。 - `<3.2>` 处, 设置 `resourceLoader` 属性到 `scanner` 中。 - `<3.3>` 处,获得 `@MapperScan` 注解上的属性,设置到 `scanner` 中。 - `<4>` 处,获得要扫描的包。 - `<5>` 处,调用 `ClassPathMapperScanner#registerFilters()` 方法,注册 scanner 的过滤器。 - `<6>` 处,调用 `ClassPathMapperScanner#doScan(String... basePackages)` 方法,执行扫描,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。 - 😈 上面看了一堆的 ClassPathMapperScanner 类的调用,下面开始我们的旅程。 ## 4.2 ClassPathMapperScanner `org.mybatis.spring.mapper.ClassPathMapperScanner` ,继承 `org.springframework.context.annotation.ClassPathMapperScanner` 类,负责执行扫描,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。 可能很多胖友不熟悉 ClassPathMapperScanner 类,可以看看 [《Spring自定义类扫描器》](https://fangjian0423.github.io/2017/06/11/spring-custom-component-provider/) 文章。哈哈哈,艿艿也是现学的。 ### 4.2.1 构造方法 ``` // ClassPathMapperScanner.java /** * 是否添加到 {@link org.apache.ibatis.session.Configuration} 中 */ private boolean addToConfig = true; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; /** * {@link #sqlSessionTemplate} 的 bean 名字 */ private String sqlSessionTemplateBeanName; /** * {@link #sqlSessionFactory} 的 bean 名字 */ private String sqlSessionFactoryBeanName; /** * 指定注解 */ private Class annotationClass; /** * 指定接口 */ private Class markerInterface; /** * MapperFactoryBean 对象 */ private MapperFactoryBean mapperFactoryBean = new MapperFactoryBean<>(); // ... 省略 setting 方法 ``` ### 4.2.2 registerFilters `#registerFilters()` 方法,注册过滤器。代码如下: ``` // ClassPathMapperScanner.java /** * Configures parent scanner to search for the right interfaces. It can search * for all interfaces or just for those that extends a markerInterface or/and * those annotated with the annotationClass * * 注册过滤器 */ public void registerFilters() { boolean acceptAllInterfaces = true; // 是否接受所有接口 // if specified, use the given annotation and / or marker interface // 如果指定了注解,则添加 INCLUDE 过滤器 AnnotationTypeFilter 对象 if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; // 标记不是接受所有接口 } // override AssignableTypeFilter to ignore matches on the actual marker interface // 如果指定了接口,则添加 INCLUDE 过滤器 AssignableTypeFilter 对象 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); } // exclude package-info.java // 添加 INCLUDE 过滤器,排除 package-info.java addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); } ``` - 根据配置,添加 INCLUDE 和 EXCLUDE 过滤器。 ### 4.2.3 doScan `#doScan(String... basePackages)` 方法,执行扫描,将扫描到的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象。代码如下: ``` // ClassPathMapperScanner.java /** * Calls the parent search that will search and register all the candidates. * Then the registered objects are post processed to set them as * MapperFactoryBeans */ @Override public Set doScan(String... basePackages) { // <1> 执行扫描,获得包下符合的类们,并分装成 BeanDefinitionHolder 对象的集合 Set beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 处理 BeanDefinitionHolder 对象的集合 processBeanDefinitions(beanDefinitions); } return beanDefinitions; } ``` - `<1>` 处,调用父 `ClassPathBeanDefinitionScanner#doScan(basePackages)` 方法,执行扫描,获得包下符合的类们,并分装成 BeanDefinitionHolder 对象的集合。 - `<2>` 处,调用 `#processBeanDefinitions((Set beanDefinitions)` 方法,处理 BeanDefinitionHolder 对象的集合。代码如下: ``` // ClassPathMapperScanner.java private void processBeanDefinitions(Set beanDefinitions) { GenericBeanDefinition definition; // <1> 遍历 BeanDefinitionHolder 数组,逐一设置属性 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 // <2> 此处 definition 的 beanClass 为 Mapper 接口,需要修改成 MapperFactoryBean 类,从而创建 Mapper 代理对象 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); // <3> 设置 `MapperFactoryBean.addToConfig` 属性 definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; // <4.1>是否已经显式设置了 sqlSessionFactory 或 sqlSessionFactory 属性 // <4.2> 如果 sqlSessionFactoryBeanName 或 sqlSessionFactory 非空,设置到 `MapperFactoryBean.sqlSessionFactory` 属性 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; } // <4.3> 如果 sqlSessionTemplateBeanName 或 sqlSessionTemplate 非空,设置到 `MapperFactoryBean.sqlSessionTemplate` 属性 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; } // <4.4> 如果未显式设置,则设置根据类型自动注入 if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } } ``` - 虽然方法很长,重点就是修改 BeanDefinitionHolder 的相关属性。 - `<1>` 处,遍历 BeanDefinitionHolder 数组,逐一设置属性。 - `<2>` 处,此处 `definition` 的 `beanClass` 为 Mapper 接口,需要修改成 MapperFactoryBean 类,从而创建 Mapper 代理对象。 - `<3>` 处,设置 `MapperFactoryBean.addToConfig` 属性。 - ``` <4.1> ``` 处,是否已经显式设置了 ``` sqlSessionFactory 或 sqlSessionFactory ``` 属性。 - `<4.2>` 处,如果 `sqlSessionFactoryBeanName` 或 `sqlSessionFactory` 非空,设置到 `MapperFactoryBean.sqlSessionFactory` 属性。 - `<4.3>` 处,如果 `sqlSessionTemplateBeanName` 或 `sqlSessionTemplate` 非空,设置到 `MapperFactoryBean.sqlSessionTemplate` 属性。 - `<4.4>` 处,如果未显式设置,**则设置根据类型自动注入**。 ### 4.3 @MapperScans `org.mybatis.spring.annotation.@MapperScans` ,多 `@MapperScan` 的注解,功能是相同的。代码如下: ``` // MapperScans.java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.RepeatingRegistrar.class) public @interface MapperScans { /** * @return @MapperScan 数组 */ MapperScan[] value(); } ``` - 此处,`@Import(MapperScannerRegistrar.RepeatingRegistrar.class)` 是 RepeatingRegistrar 类。 ### 4.4 RepeatingRegistrar RepeatingRegistrar ,是 MapperScannerRegistrar 的内部静态类,继承 MapperScannerRegistrar 类,`@MapperScans` 的注册器。代码如下: ``` // MapperScannerRegistrar.java static class RepeatingRegistrar extends MapperScannerRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 获得 @MapperScans 注解信息 AnnotationAttributes mapperScansAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName())); // 遍历 @MapperScans 的值,调用 `#registerBeanDefinitions(mapperScanAttrs, registry)` 方法,循环扫描处理 Arrays.stream(mapperScansAttrs.getAnnotationArray("value")) .forEach(mapperScanAttrs -> registerBeanDefinitions(mapperScanAttrs, registry)); } } ``` - [「4.1.2 registerBeanDefinitions」](https://svip.iocoder.cn/MyBatis/Spring-Integration-2/#) 的**循环**版。 # 5. 自定义 `` 标签 使用示例如下: ``` ``` - 该示例来自 `org.mybatis.spring.sample.SampleNamespaceTest` 单元测试。胖友可以基于它调试。 简单来理解,`` 标签,和 `@MapperScan` 注解,用途是等价的。 ## 5.1 spring.schemas MyBatis 在 `META-INF/spring.schemas` 定义如下: ``` http\://mybatis.org/schema/mybatis-spring-1.2.xsd=org/mybatis/spring/config/mybatis-spring.xsd http\://mybatis.org/schema/mybatis-spring.xsd=org/mybatis/spring/config/mybatis-spring.xsd ``` - xmlns 为 `http://mybatis.org/schema/mybatis-spring-1.2.xsd` 或 `http://mybatis.org/schema/mybatis-spring.xsd` 。 - xsd 为 `org/mybatis/spring/config/mybatis-spring.xsd` 。 ## 5.2 mybatis-spring.xsd [![mybatis-spring.xsd](https://github.com/YunaiV/mybatis-spring/blob/master/src/main/java/org/mybatis/spring/config/mybatis-spring.xsd)](https://github.com/YunaiV/mybatis-spring/blob/master/src/main/java/org/mybatis/spring/config/mybatis-spring.xsd)mybatis-spring.xsd定义如下:[mybatis-spring.xsd](http://static.iocoder.cn/images/MyBatis/2020_06_04/01.png) ## 5.3 spring.handler `spring.handlers` 定义如下: ``` http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler ``` 定义了 MyBatis 的 XML Namespace 的处理器 NamespaceHandler 。 ## 5.4 NamespaceHandler `org.mybatis.spring.config.NamespaceHandler` ,继承 NamespaceHandlerSupport 抽象类,MyBatis 的 XML Namespace 的处理器。代码如下: ``` // NamespaceHandler.java public class NamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser()); } } ``` - `` 标签,使用 MapperScannerBeanDefinitionParser 解析。 ## 5.5 MapperScannerBeanDefinitionParser `org.mybatis.spring.config.MapperScannerBeanDefinitionParser` ,实现 BeanDefinitionParser 接口,`` 的解析器。代码如下: ``` // BeanDefinitionParser.java public class MapperScannerBeanDefinitionParser implements BeanDefinitionParser { private static final String ATTRIBUTE_BASE_PACKAGE = "base-package"; private static final String ATTRIBUTE_ANNOTATION = "annotation"; private static final String ATTRIBUTE_MARKER_INTERFACE = "marker-interface"; private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator"; private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref"; private static final String ATTRIBUTE_FACTORY_REF = "factory-ref"; /** * {@inheritDoc} */ @Override public synchronized BeanDefinition parse(Element element, ParserContext parserContext) { // 创建 ClassPathMapperScanner 对象 ClassPathMapperScanner scanner = new ClassPathMapperScanner(parserContext.getRegistry()); ClassLoader classLoader = scanner.getResourceLoader().getClassLoader(); XmlReaderContext readerContext = parserContext.getReaderContext(); scanner.setResourceLoader(readerContext.getResourceLoader()); // 设置 resourceLoader 属性 try { // 解析 annotation 属性 String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION); if (StringUtils.hasText(annotationClassName)) { @SuppressWarnings("unchecked") Class markerInterface = (Class) classLoader.loadClass(annotationClassName); scanner.setAnnotationClass(markerInterface); } // 解析 marker-interface 属性 String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE); if (StringUtils.hasText(markerInterfaceClassName)) { Class markerInterface = classLoader.loadClass(markerInterfaceClassName); scanner.setMarkerInterface(markerInterface); } // 解析 name-generator 属性 String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR); if (StringUtils.hasText(nameGeneratorClassName)) { Class nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName); BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class); scanner.setBeanNameGenerator(nameGenerator); } } catch (Exception ex) { readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause()); } // 解析 template-ref 属性 String sqlSessionTemplateBeanName = element.getAttribute(ATTRIBUTE_TEMPLATE_REF); scanner.setSqlSessionTemplateBeanName(sqlSessionTemplateBeanName); // 解析 factory-ref 属性 String sqlSessionFactoryBeanName = element.getAttribute(ATTRIBUTE_FACTORY_REF); scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryBeanName); // 注册 scanner 的过滤器 scanner.registerFilters(); // 获得要扫描的包 String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE); // 执行扫描 scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); return null; } } ``` - 代码实现上,和 [「4.2 ClassPathMapperScanner」](https://svip.iocoder.cn/MyBatis/Spring-Integration-2/#) 是基本一致的。所以就不详细解析啦。 # 6. MapperScannerConfigurer `org.mybatis.spring.mapper.MapperScannerConfigurer` ,实现 BeanDefinitionRegistryPostProcessor、InitializingBean、ApplicationContextAware、BeanNameAware 接口,定义需要扫描的包,将包中符合的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象。 使用示例如下: ``` // XML ``` - 该示例来自 `org.mybatis.spring.sample.MapperScannerConfigurer` 单元测试。胖友可以基于它调试。 ## 6.1 构造方法 ``` // MapperScannerConfigurer.java private String basePackage; private boolean addToConfig = true; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionFactoryBeanName; private String sqlSessionTemplateBeanName; private Class annotationClass; private Class markerInterface; private ApplicationContext applicationContext; private String beanName; private boolean processPropertyPlaceHolders; private BeanNameGenerator nameGenerator; // 省略 setting 方法 ``` ## 6.2 afterPropertiesSet ``` // MapperScannerConfigurer.java @Override public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); } ``` - 啥都不做。 ## 6.3 postProcessBeanFactory ``` // MapperScannerConfigurer.java @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // left intentionally blank } ``` ## 6.4 postProcessBeanDefinitionRegistry ``` // MapperScannerConfigurer.java @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { // <1> 如果有属性占位符,则进行获得,例如 ${basePackage} 等等 if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } // <2> 创建 ClassPathMapperScanner 对象,并设置其相关属性 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 过滤器 scanner.registerFilters(); // 执行扫描 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } ``` - `<1>` 处,调用 `#processPropertyPlaceHolders()` 方法,如果有属性占位符,则进行获得,例如 `${basePackage}` 等等。代码如下: ``` // MapperScannerConfigurer.java private void processPropertyPlaceHolders() { Map prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) { BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext) .getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer does not expose any methods to explicitly perform // property placeholder substitution. Instead, create a BeanFactory that just // contains this mapper scanner and post process the factory. DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition(beanName, mapperScannerBean); for (PropertyResourceConfigurer prc : prcs.values()) { prc.postProcessBeanFactory(factory); } PropertyValues values = mapperScannerBean.getPropertyValues(); this.basePackage = updatePropertyValue("basePackage", values); this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values); this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values); } } // 获得属性值,并转换成 String 类型 private String updatePropertyValue(String propertyName, PropertyValues values) { PropertyValue property = values.getPropertyValue(propertyName); Object value = property.getValue(); if (value instanceof String) { return value.toString(); } else if (value instanceof TypedStringValue) { return ((TypedStringValue) value).getValue(); } else { return null; } } ``` - `<2>` 处,代码实现上,和 [「4.2 ClassPathMapperScanner」](https://svip.iocoder.cn/MyBatis/Spring-Integration-2/#) 是基本一致的。所以就不详细解析啦。 # 666. 彩蛋 略微冗长,但是易于理解的一篇文章。 我们简单把 3 ~ 6 小节做个小结的话: - 「3」MapperFactoryBean 类,是最**基础**的、**单个**的负责创建 Mapper 代理对象的类。 - 「4」「5」「6」,都是基于 MapperFactoryBean 之上,使用 ClassPathMapperScanner 扫描指定包,创建成 MapperFactoryBean 对象,从而创建 Mapper 代理对象。