了解了MyBatis的单独使用过程之后,我们再来看看它也Spring整合的使用方式,比对之前的示例来找出Spring究竟为我们做了什么操作,哪些操作简化了程序开发。

  1. 准备spring71.xml
<?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"><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://localhost:3306/pple_test?characterEncoding=utf-8"></property><property name="username" value="root"></property><property name="password" value="Hello1234"></property><property name="initialSize" value="1"></property><property name="maxIdle" value="2"></property><property name="minIdle" value="1"></property></bean><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation" value="classpath:spring_1_100/config_71_80/spring71_mybatis_origin/spring-mybatis-config.xml"></property><property name="dataSource" ref="dataSource"></property></bean><bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.spring_1_100.test_71_80.test71_spring_mybatis.UserMapper"></property><property name="sqlSessionFactory" ref="sqlSessionFactory"></property></bean>
</beans>

对比之前独立的MyBatis的配置文件,我们发现在之前environments中设置的dataSource被转移到了Spring的核心配置文件中管理,而且针对MyBatis,注册了org.mybatis.Spring.SqlSessionFactoryBean类型的bean,以及用于映射接口的org.mybatis.spring.mapper.MapperFactoryBean,这两个bean的作用我们会稍后分析。
        之前我们了解到,MyBatis提供的配置文件包含了诸多属性,虽然大多数情况下我们会保持MyBatis的原有风格,将MyBatis的配置文件独立出来,并在Spring中的org.mybatis.spring.SqlSessionFactoryBean类型的bean中通过configLocation属性来引入,但是并不代表Spring不支持直接配置,以上的示例为例,你完全可以省略mybatis-configuration.xml,而将其中的配置以属性的方式注入到SqlSessionFactoryBean中,至于每个属性名称及用法,我们会在后面进行详细的分析。

  1. MyBatis配置文件
            对比独立的使用的MyBatis时的配置文件,当前的配置文件除了移除environments配置外并没有太多的变化 。
<?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><settings><setting name="cacheEnabled" value="false"/><setting name="useGeneratedKeys" value="true"/><setting name="defaultExecutorType" value="REUSE"/><setting name="mapUnderscoreToCamelCase" value="true" /></settings><typeAliases><typeAlias type="com.spring_1_100.test_71_80.test71_spring_mybatis.User" alias="User"></typeAlias></typeAliases><mappers><mapper resource="spring_1_100/config_71_80/spring71_mybatis_origin/UserMapper.xml"></mapper></mappers>
</configuration>

3.映射文件(保持不变)

<?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">
<mapper namespace="com.spring_1_100.test_71_80.test71_spring_mybatis.UserMapper"><insert id="insertUser" parameterType="com.spring_1_100.test_71_80.test71_spring_mybatis.User">INSERT INTO lz_user (username, password, real_name, manager_id) VALUES (#{username},#{password},#{realName},#{managerId})</insert><select id="getUser" resultType="com.spring_1_100.test_71_80.test71_spring_mybatis.User" parameterType="java.lang.Long">select * from lz_user where id=#{id}</select></mapper>
  1. 测试

至此,我们己经完成了Spring与MyBatis的整合,我们发现,对于MyBatis方面的配置文件,除了将dataSource配置移到Spring配置文件中管理外,并没有太多的变化,而在Spring的配置文件中又增加了用于处理的MyBatis的两个bean 。
        Spring整合MyBatis的优势主要在使用上,我们来看看Spring使用MyBatis的用法。

public class Test71 {public static void main(String[] args) {ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_71_80/spring71_mybatis_origin/spring71.xml");UserMapper userMapper = (UserMapper) ac.getBean("userMapper");User user = userMapper.getUser(456l);System.out.println(JSON.toJSONString(user));}
}

在测试中我们可以看到,Spring中使用MyBatis是非常的方便,用户甚至无法察觉自己正在使用MyBatis,而对于一切的独立使用MyBatis时必需要做各种冗余的操作来说无非是大大简化了我们的工作量。

sqlSessionFactory创建

通过配置文件的分析,对于配置文件的读取和解析,Spring应该通过org.mybatis.spring.SqlSessionFactoryBean封装了MyBatis中的实现,我们进入这个类,首先查看这个类的层次结构。

根据这个类层次结构找出我们感兴趣的两个接口,FactoryBean和InitializingBean

  • InitializingBean:实现此接口的bean会在初始化的时候调用afterPropertiesSet方法来进行bean的逻辑初始化。
  • FactoryBean:一旦某个bean实现此接口,那么通过getBean方法获取bean其实是获取此类的getObject()返回的实例。

我们首先以InitializingBean接口的afterPropertiesSet()方法作为突破口。

SqlSessionFactoryBean的初始化

查看SqlSessionFactoryBean类型的bean在初始化的时候做了哪些逻辑。

public void afterPropertiesSet() throws Exception {notNull(dataSource, "Property 'dataSource' is required");notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");this.sqlSessionFactory = buildSqlSessionFactory();
}

很显然,此函数主要目的就是对sqlSessionFactory的初始化,通过此前的展示独立的使用MyBatis的示例,我们了解到了SqlSessionFactory是所有的MyBatis功能的基础。

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {Configuration configuration;XMLConfigBuilder xmlConfigBuilder = null;if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);configuration = xmlConfigBuilder.getConfiguration();} else {if (logger.isDebugEnabled()) {logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");}configuration = new Configuration();configuration.setVariables(this.configurationProperties);}if (this.objectFactory != null) {//<objectFactory type="com.spring_101_200.test_121_130.test_128_mybatis_objectfactory.UserObjectFactory">//   <property name="email" value="哈哈"/>//</objectFactory>//使用SqlSessionFactoryBean的objectFactory属性替代MyBatis中objectFactory标签configuration.setObjectFactory(this.objectFactory);}if (this.objectWrapperFactory != null) {//<objectWrapperFactory type="com.spring_101_200.test_121_130.test_129_mybatis_objectwrapper.MyMapWrapperFactory"></objectWrapperFactory>//使用SqlSessionFactoryBean的objectFactory属性替代MyBatis中objectWrapperFactory标签configuration.setObjectWrapperFactory(this.objectWrapperFactory);}if (hasLength(this.typeAliasesPackage)) {//<typeAliases>//   <package name="com.spring_101_200.test_121_130.test_128_mybatis_objectfactory"/>//</typeAliases>//使用SqlSessionFactoryBean的typeAliasesPackage属性替代MyBatis中typeAliases标签下的package标签//CONFIG_LOCATION_DELIMITERS=",; \t\n"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)) {//<typeAliases>//    <package name="com.spring_101_200.test_121_130.test_128_mybatis_objectfactory"/>//</typeAliases>//使用SqlSessionFactoryBean的typeAliases属性替代MyBatis中typeAliases标签for (Class<?> typeAlias : this.typeAliases) {configuration.getTypeAliasRegistry().registerAlias(typeAlias);if (logger.isDebugEnabled()) {logger.debug("Registered type alias: '" + typeAlias + "'");}}}if (!isEmpty(this.plugins)) {//<plugins>//    <plugin interceptor="com.spring_101_200.test_121_130.test_127_mybatis_plugins.DataScopeInterceptor">//     <property name="someProperty" value="100"/>//   </plugin>//</plugins>//使用SqlSessionFactoryBean的plugins属性替代MyBatis中plugins标签for (Interceptor plugin : this.plugins) {configuration.addInterceptor(plugin);if (logger.isDebugEnabled()) {logger.debug("Registered plugin: '" + plugin + "'");}}}if (hasLength(this.typeHandlersPackage)) {//<typeHandlers>//  <package name="com.spring_101_200.test_151_160.test_153_mybatis_self_typehandler"/>//</typeHandlers>//使用SqlSessionFactoryBean的typeHandlersPackage属性替代MyBatis中typeHandlers标签下的package标签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)) {//<typeHandlers>//  <package name="com.spring_101_200.test_151_160.test_153_mybatis_self_typehandler"/>//</typeHandlers>//使用SqlSessionFactoryBean的typeHandlersPackage属性替代MyBatis中typeHandlers标签for (TypeHandler<?> typeHandler : this.typeHandlers) {configuration.getTypeHandlerRegistry().register(typeHandler);if (logger.isDebugEnabled()) {logger.debug("Registered type handler: '" + typeHandler + "'");}}}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();}Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);configuration.setEnvironment(environment);if (this.databaseIdProvider != null) {try {configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException e) {throw new NestedIOException("Failed getting a databaseId", e);}}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);
}

从函数中可以看到,尽管我们还是不习惯将MyBatis的配置与Spring的配置独立出来,但是,这并不代表Spring中不支持直接配置,也就是说,在上面的提供的示例中,你完全可以取消配置中的configLocation属性,而把其中的属性直接写在SqlSessionFactoryBean中。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation"value="classpath:spring_1_100/config_71_80/spring72/spring-mybatis-config.xml"></property><property name="dataSource" ref="dataSource"></property><property name="typeAliasesPackage" value="aaaaa"></property>...
</bean>

从这个函数中可以得知,配置文件还可以支持其他的多种属性的配置,如configLocation,objectFactory,objectWrapperFactory,typeAliasesPackage,typeHandlerPackage,plugins,typeHandler,transactionFactory,dabaseIdProvider,mapperLocations。
        其实,如果只是按照常用的配置,那么我们只需在函数最开始按照如下方式处理configuration:
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
        根据configuLocation构造XMLConfigBuilder并进行解析,但是,为了体现Spring更强大的兼容性,Spring还整合了MyBatis的其他的属性注入,并通过实例configuration来承载每一步所获取的信息并最终使用sqlSessionFactoryBuilder实例根据解析到的configuration创建sqlSessionFactory实例。

获取SqlSessionFactoryBean

由于SqlSessionFactoryBean实现了FactoryBean接口,所以当通过getBean方法获取对应的实例时,其实是获取该类的getObject()函数返回的实例,也就是获取初始化后的sqlSessionFactory属性。

public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();}return this.sqlSessionFactory;
}
MapperFactoryBean的创建

为了使用MyBatis的功能,示例中Spring配置文件提供了两个bean,除了之前分析的SqlSessionFactoryBean类型的bean以外,还有一个MapperFactoryBean类型的bean。
        结合两个测试用例综合分析,对于单独的使用MyBatis的时候调用数据库接口的方式是:
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        而在这一过程中,其实MyBatis在获取映射的过程是根据配置信息为UserMapper类型动态创建的代理类,而对于Spring创建的方式:
        UserMapper userMapper = (UserMapper) ac.getBean(“userMapper”);
        Spring中获取名为userMapper的bean,其实是与单独使用MyBatis完成的功能一样,那么我们可以推断,在bean的创建过程中一定使用了MyBatis中的原生方法sqlSession.getMapper(UserMapper.class)进行了再一次封装,结合配置文件,我们把分析的目标转向org.mybatis.spring.mapper.MyBatisFactoryBean,初步推测其中的逻辑应该在此类中实现,同样还是首先查看类层次结构图MapperFactoryBean,如下图所示:

        同样,在实现的接口中发现了我们感兴趣的两个接口InitializingBean和FactoryBean,我们分析是从bean的初始化开始。

MapperFactoryBean的初始化

因为实现了InitializingBean接口,Spring会保证在bean的初始化时首先调用afterPropertiesSet方法来完成其初始化的逻辑,追踪父类,发现afterPropertiesSet方法是在DaoSupport类中实现的,代码如下:

DaoSupport.java
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {// Let abstract subclasses check their configuration.checkDaoConfig();// Let concrete implementations initialize themselves.try {initDao();}catch (Exception ex) {throw new BeanInitializationException("Initialization of DAO failed", ex);}
}

但是从函数的名称来看我们大致推测,MapperFactoryBean的初始化包括对Dao配置的验证以及对Dao的初始化工作,其中initDao方法是模板方法,设计留给子类做进一步的逻辑处理,而checkDaoConfig()方法才是我们的重点。

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

super.checkDaoConfig()在SqlSessionDaoSupport()类中的实现。

SqlSessionDaoSupport.java
protected void checkDaoConfig() {notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}

结合代码我们了解到对于DAO配置的验证,Spring做了以下的几个方面的工作。

  • 父类中对于sqlSession不能为空的验证。

sqlSession作为根据接口创建映射器代理的接触类一定不可以为空,而sqlSession的初始化工作是在设定其sqlSessionFactory属性时完成的。

SqlSessionDaoSupport.java
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {if (!this.externalSqlSession) {this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);}
}

也就是说,对于下面的配置如果忽略了对于sqlSessionFactory属性的设置,那么在此时就会检测出来。

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.spring_1_100.test_71_80.test72_mybatis_scan.UserMapper"></property><property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
  • 映射接口的验证

接口是映射器的基础,sqlSession会根据接口动态创建相应的代理类,所以接口必不可少。

  • 映射文件存在性验证

对于函数的前半部分的验证我们都很容易理解,无非是对配置文件中的属性是否存在做验证,但昌后面的部分是完成什么方法的验证呢?如果读者读过我之前的博客就会知道,在MyBatis实现过程中并没有手动调用configuration.addMapper()方法,而是在映射文件解析过程中一旦解析到命名空间<mapper namespace=“com.spring_1_100.test_71_80.test72_mybatis_scan.UserMapper”> ,便会自动进行类型注册,那么Spring中为什么会把这个功能单独拿出来放在验证里呢?这是不是多此一举?
        上面的函数中,configuration.addMapper(this.interface)其实就是将UserMapper注册到映射类型中去,如果你可以保证这个接口一定存在对应的映射文件,那么其实这个验证并没有必要,但是,由于我们自行决定配置,无法保证这里的配置的接口一定存在对应的映射文件,所以这里有必要验证,在执行此代码的过程中,MyBatis会检查嵌入的映射接口是否存在对应的映射文件,如果没有,则抛出异常,Spring正是用这种方式来完成接口对应的映射文件存在性验证的。

获取MapperFactoryBean的实例

由于MapperFactoryBean实现的FactoryBean接口,所以当通过getBean方法获取对应的实例的时候其实是获取该类的getObject()函数返回的实例。

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

这段代码正是我们在提供MyBatis独立使用的时候的一个代码调用,Spring通过FactoryBean进行了封装。

MapperScannerConfigurer

我们在applicationContext.xml中配置了userMapper供需要时使用,但是如果需要用到映射器较多的话,采用这种方式配置显然效率低下,为了解决这个问题,我们可以使用MapperScannerConfigurer,让它扫描特定的包,自动帮我们成批的创建映射器,这样一来,就能大大的减少配置的工作量,比如我们将spring71.xml文件中的配置改成如下:

<?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"><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://172.16.157.238:3306/pple_test?characterEncoding=utf-8"></property><property name="username" value="ldd_biz"></property><property name="password" value="Hello1234"></property><property name="initialSize" value="1"></property><property name="maxIdle" value="2"></property><property name="minIdle" value="1"></property></bean><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation"value="classpath:spring_1_100/config_71_80/spring72/spring-mybatis-config.xml"></property><property name="dataSource" ref="dataSource"></property><property name="typeAliasesPackage" value="aaaaa"></property></bean><!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.spring_1_100.test_71_80.test72_mybatis_scan.UserMapper"></property><property name="sqlSessionFactory" ref="sqlSessionFactory"></property></bean>--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.spring_1_100.test_71_80.test72_mybatis_scan"></property></bean>
</beans>

在上面的配置中,我们屏蔽掉了原始代码(userMapper的创建)而增加了MapperScannerConfigurer的配置,basePackage属性让你为映射器接口文件设置基本的包路径,你可以使用分号或逗号作为分隔符设置多于一个包路径,每个映射器将会在指定的包路径中递归的搜索,被发现的映射器将会使用Spring对自动侦测组件默认的命名策略来命名,也就是说,如果没有发现注解,它就会使用映射器的非大写的非 完全限定类名,但是如果发现了@Component或JSR-330@Named注解,它会获取名称。
        通过上面的配置,Spring就会帮助我们对com.spring_1_100.test_71_80.test72_mybatis_scan下面所有的接口进行自动的注入而不需要为每个接口重复的在Spring配置文件中进行声明,那么这个功能又是如何做到的呢?MapperScannerConfigurer中又有哪些核心操作呢?同样,首先查看类层次结构图,如图所示:

        我们又看到了令人感兴趣的接口InitializingBean,马上查找类的afterPropertiesSet方法来看类的初始化逻辑。

MapperScannerConfigurer.java
public void afterPropertiesSet() throws Exception {notNull(this.basePackage, "Property 'basePackage' is required");
}

很遗憾,分析并没有我们之前那样的顺利,afterPropertiesSet()方法除了对basePackage属性验证代码外并没有太多的逻辑实现,好吧,让我们回过头再去查看MapperScannerConfigurer类层次结构图中感兴趣的接口,于是,我们发现了BeanDefinitionRegistryPostProcessor与BeanFactoryPostProcessor,Spring在初始化的过程中同样会保证这两个接口的调用。

首先查看MapperScannerConfigurer类中对BeanDefinitionRegistryPostProcessor与BeanFactoryPostProcessor,Spring在初始化的过程中同样会保证这两个接口的调用。
        首先查看MapperScannerConfigurer类对于BeanFactoryPostProcessor接口的实现:

MapperScannerConfigurer.java
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {// left intentionally blank
}

没有任何逻辑的实现,只能说明我们找错地方了,继续找,查看MapperScannerConfigurer类中对于BeanDefinitionRegistryPostProcessor接口的实现。

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

Bingo!,这次地方找对了,大致看一下代码的实现,正是完成了对指定路径的扫描逻辑,那么,我们就以此为入口,详细的分析MapperScannerConfigurer所提供的逻辑实现。

processPropertyPlaceHolders属性处理

首先,难题就是processPropertyPlaceHolders属性的处理了,或许读者并未过多的接触此属性,我们只能查看processPropertyPlaceHolders()函数来反推此属性所代码的功能。

/** BeanDefinitionRegistries are called early in application startup, before* BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been* loaded and any property substitution of this class' properties will fail. To avoid this, find* any PropertyResourceConfigurers defined in the context and run them on this class' bean* definition. Then update the values.*/
private void processPropertyPlaceHolders() {Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {BeanDefinition mapperScannerBean = ((GenericApplicationContext) 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);}
}

不知道你有没有悟出此函数的作用呢?或许此函数的说明会给我们一些提示:BeanDefinitionRegistries会在应用启动的时候调用,并且会早于BeanFactoryPostProcessors的调用,这就意味着PropertyResourceConfigurers还没有被加载所有的属性文件的引用将会失效,为了避免此种情况发生,此方法手动的找出定义的PropertyResourceConfigurers并进行提前调用以保证对于属性的引用正常工作。
        我想你己经有所感悟,结合之前讲过的PropertyResourceConfigurer的用法,举例说明一下,如要创建配置文件如test72.properties,并添加属性对:
        base.package=com.spring_1_100.test_71_80.test72_mybatis_scan

<bean id="mesHandler" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"><property name="locations"><list><value>spring_1_100/config_71_80/config_72/test72.properties</value></list></property>
</bean>

修改MapperScannerConfigurer类型的bean的定义。

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="${base.package}"></property>
</bean>

此时你会发现,这个配置并没有达到预期的效果,因为在解析${basePackage}的时候,PropertyPlaceholderConfigurer还没有被调用,也就是属性文件中的属性还没有被加载到内存中,Spring还不能直接使用它,为了解决这个问题,Spring提供了processPropertyPlaceHolders属性,你需要这样配置MapperScannerConfigurer类型的bean 。

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="${base.package}"></property><property name="processPropertyPlaceHolders" value="true"></property>
</bean>

通过processPropertyPlaceHolders属性的配置,将程序引入我们正在分析的processPropertyPlaceHolders函数中来完成对属性文件的加载,至此,我们终于理清了这个属性的作用,再次回顾这个函数所做的事情 。

  1. 找到所有己经注册的PropertyResourceConfigurer类型的bean
  2. 模似Spring中环境来用处理器,这里通过使用new DefaultListableBeanFactory()来模似,Spring中的环境(完成处理器的调用后便会失效),将映射的bean,也就是MapperScannerConfigurer类型的bean注册到环境中来进行后处理器的调用,处理器PropertyPlaceholderConfigurer调用完成的功能,即找出所有的bean中的属性文件的变量并替换,也就是说,在处理器调用后,模似环境中模拟的MapperScannerConfigurer类型的bean如果引入属性文件中的属性那么己经被替换了,这时,再将模拟bean中相关的属性来提取出来的应用在真实的bean中。
根据配置属性生成过滤器

在postProcessBeanDefinitionRegistry方法中可以看到,配置中支持很多属性的设定,但是我们感兴趣的或者说影响扫描结果的并不多,属性设置后通过scanner.regissterFilters()代码中生成对应的过滤器来控制扫描结果。

/*** 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 interfaceif (this.annotationClass != null) {addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}// override AssignableTypeFilter to ignore matches on the actual marker interfaceif (this.markerInterface != null) {addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {@Overrideprotected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}if (acceptAllInterfaces) {// default include filter that accepts all classesaddIncludeFilter(new TypeFilter() {public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return true;}});}// exclude package-info.javaaddExcludeFilter(new TypeFilter() {public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");}});
}

代码中得知,根据之前的属性生成对应的过滤器。
        1. annotationClass属性处理
        如果annotationClass不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器就是AnnotationTypeFilter,AnnotationTypeFilter保证在扫描对应的Java文件时只接受标记有注解为annotationClass接口。
        2. markerInterface属性处理
        如果markerInterface属性不为空,表示用户设置了此属性,那么就要根据此属性生成过滤器以保证达到用户想要的效果,而封装此属性的过滤器是实现了AssignableTypeFilter接口的局部类,表示扫描过程中只有实现了markerInterface接口的接口才会被接受。
        3. 全局默认处理
        如果上面的两个属性中如果存在其中任何一个属性,acceptAllInterfaces的值将会发生改变,如果用户没有设定以上的两个属性,那么Spring会为我们增加一个默认的过滤器实现TypeFilter接口的局部类,旨在接受所有接口文件。
        4. package-info.java处理。
        对于命名为package-info的java文件,默认不作逻辑实现接口,将其排除掉,使用TypeFilter接口局部类实现match方法。
        从上面的函数中我们可以看出,控制扫描文件Spring能完不同的过滤器完成,这些定义的过滤器记录在includeFilters和excludeFilters属性中。

/*** Add an include type filter to the <i>end</i> of the inclusion list.*/
public void addIncludeFilter(TypeFilter includeFilter) {this.includeFilters.add(includeFilter);
}/*** Add an exclude type filter to the <i>front</i> of the exclusion list.*/
public void addExcludeFilter(TypeFilter excludeFilter) {this.excludeFilters.add(0, excludeFilter);
}

至于过滤器为什么会在扫描过程中起作用,我们地讲解扫描实现的时候再继续深入研究。

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");}//开始构造MapperFactoryBean类型的beandefinition.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中对于自动扫描注册,声明MapperScannerConfigurer类型的bean目的是不需要我们对每个接口都注册一个MapperFactoryBean类型对应的bean的,但是不是在配置文件中注册,并不代表这个bean不存在,而是在扫描的过程中通过编码的方式注册,实现过程我们在上面的函数中可以看得非常的清楚。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();for (String basePackage : basePackages) {//扫描basePackage路径下的java文件Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}if (candidate instanceof AnnotatedBeanDefinition) {//如果是AnnotationBeanDefinition类型的bean,需要检测下常用注解如Primary,Lazy等AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//检测当前bean是否己经被注册if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);//如果当前bean是用于生成代理的bean,那么需要进一步处理definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;
}
 /*** Scan the class path for candidate components.* @param basePackage the package to check for annotated classes* @return a corresponding Set of autodetected bean definitions*/
public Set findCandidateComponents(String basePackage) {Set candidates = new LinkedHashSet();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + "/" + this.resourcePattern;Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);}else {if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}}else {if (traceEnabled) {logger.trace("Ignored because not readable: " + resource);}}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;
}

findCandidateComponents方法是根据传入的包路径信息并结合类文件路径拼接成文件的绝对路径,同时完成了文件的扫描过程并且根据对应的文件生成了对应的bean,使用ScannedGenericBeanDefinition类型的bean承载信息,bean中记录了resource和source信息,这里,我们更感兴趣的是isCandidateComponent(metadataReader),此名代码用于判断当前扫描的文件是否符合要求,而我们之前注册的一些过滤器信息也正是此时派上用场的。

/*** Determine whether the given class does not match any exclude filter* and does match at least one include filter.* @param metadataReader the ASM ClassReader for the class* @return whether the class qualifies as a candidate component*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, this.metadataReaderFactory)) {return false;}}for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, this.metadataReaderFactory)) {return isConditionMatch(metadataReader);}}return false;
}

我们看到了之前加入到过滤器中的两个属性excludeFilters,includeFilters,并且知道了对应的文件是否符合要求是根据过滤器中的match方法所返回的信息来判断的,当然用户可以实现并注册满足自己业务逻辑的过滤器来控制扫描结果,metadataReader中有过滤所需要的全部文件信息,至此,我们完成了文件扫描过程的分析。

这篇博客是Spring源码深度解析(郝佳)书中第九章的内容,我只是实现了其中的示例而已,有兴趣的读者可以自己去看Spring源码深度解析(郝佳)这本书,虽然书中有一些示例有一点点小问题,但是如果想深入理解Spring源码,这本书对你的帮助还是很大的,其他的书籍可以看看,但是这本书,可以多次看,每一次看,你都有想不到的收获。

本文github地址是https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_71_80/test71_spring_mybatis

Spring源码深度解析(郝佳)-学习-源码解析-Spring整合MyBatis相关推荐

  1. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理实现(八)

    继上一篇博客,我们继续来分析下面示例的 Spring 静态代理源码实现. 静态 AOP使用示例 加载时织入(Load -Time WEaving,LTW) 指的是在虚拟机载入字节码时动态织入 Aspe ...

  2. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  3. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(三)-Controller 解析

    在之前的博客中Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一),己经对 Spring MVC 的框架做了详细的分析,但是有一个问题,发现举的例子不常用,因为我们在实际开发项 ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  6. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理(七)

    加载时织入(Load-Time Weaving ,LTW) 指的是在虚拟机加载入字节码文件时动态织入Aspect切面,Spring框架的值添加为 AspectJ LTW在动态织入过程中提供了更细粒度的 ...

  7. Spring源码深度解析(郝佳)-学习-源码解析-factory-method

    本文要解析的是Spring factory-method是如何来实现的,话不多说,示例先上来. Stu.java public class Stu {public String stuId;publi ...

  8. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一)

    Spring框架提供了构建Web应用程序的全部功能MVC模块,通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer Pages(JSP),Velocity,Ti ...

  9. Spring源码深度解析(郝佳)-学习-RMI使用及Spring源码解读

    java远程方法调用.即Java RMI(Java Remote Method Invocation),是Java编程语言里一种用于实现远程过程调用的应用程序编程接口,它使客户机上运行的程序可以调用远 ...

最新文章

  1. 数据派新年寄语 | 新时代,新年好!
  2. [Js/Jquery]jquery插件开发
  3. c++string类默认函数实现
  4. Golang的日志记录器
  5. cmake-debug和release模式
  6. Kafka消息格式中的变长字段(Varints)
  7. IHttpModule 与IHttpHandler的区别
  8. docker-for-windows配置了阿里云镜像,仍然无法获得链接:(Client.Timeout exceeded while awaiting headers)
  9. 特斯拉被曝储存大量未加密个人数据,你的隐私正在“裸奔”!
  10. linux下定时执行任务方法【转】
  11. 电商平台环境下的图像分析在线服务产品——电商图像分析
  12. visio哪个版本好用
  13. abb机器人指令手册_ABB机器人制动闸未释放故障维修
  14. EasyBoot教程二:制作PE多重启动盘方法
  15. 【线性代数】1.3伴随矩阵和逆矩阵
  16. Unity(OpenGL)实现“阴阳师画符”、划线功能
  17. 设置vscode背景图片
  18. bat计算机清理原理,科技教程:电脑如何一键清除垃圾bat
  19. 德勤oracle offer,会计工作:刚刚拿到德勤 Offer,和大家分享一下
  20. 1293. 夏洛克和他的女朋友【二分图】

热门文章

  1. 正点原子linux阿尔法开发板使用——platform平台总线模型
  2. 车用半导体风口上的猪,各大巨头抢占布局
  3. SQL经典练习题(x30)
  4. 新人报道,拙作手机联网阅读器 欢迎拍砖
  5. mybatis嵌套循环map(高级用法)
  6. 有人的地方就有江湖,有江湖就有恩怨,人就是恩怨,程序员也是人
  7. Web运行原理(第一阶段1)
  8. 杭电ACM基础题(2201、2212、2304、2309、2317、2401、2500、2502、2503、1708、1161)
  9. 华为设备NAC配置命令
  10. 农村土地确权之例会纪要—— 新蔡县土地确权第十二次例会纪要