深入Spring配置项问题,全面解析

打印 上一主题 下一主题

主题 884|帖子 884|积分 2654

  1. 本文就Spring配置项解析问题展开分析,这其中涉及到bean定义注册表后置处理、bean工厂后置处理、工厂bean等Spring相关的概念。本文将以上述问题作为切入点,进行分析和展开介绍。
复制代码
问题背景介绍
我们的项目中某次依赖了某个第三方包及其中的XML文件,相关代码如下所示:XML文件中定义了Mybatis相关的bean,以及对自定义数据源myDataSource的引用。在@Configuration配置类中,我们引入了XML文件,并通过@Bean注解的方式声明了数据源bean。
  1. <bean id="thirdPartySqlSessionFactory"
  2.       
  3.       depends-on="myDataSource">
  4.    
  5.     <property name="dataSource" ref="myDataSource"/>
  6.     <property name="mapperLocations" value="classpath:mybatis/third-party/*.xml"/>
  7. </bean>
  8. <bean id="thirdPartyMapperScannerConfigurer"
  9.       
  10.       depends-on="thirdPartySqlSessionFactory">
  11.     <property name="basePackage" value="com.alibaba.thirdparty.dao"/>
  12.     <property name="sqlSessionFactoryBeanName" value="thirdPartySqlSessionFactory"/>
  13. </bean>
复制代码
  1.  
复制代码
  1. @Configuration
  2. @EnableTransactionManagement(proxyTargetClass = true)
  3. // 引入上述XML文件
  4. @ImportResource("classpath*:/mybatis-third-party-config.xml")
  5. public class MyDataSourceConfiguration {
  6.     // 声明自定义数据源
  7.     @Bean(name = "myDataSource")
  8.     public DataSource createMyDataSource(Environment env) {
  9. <bean id="myInputStream"
  10.       factory-method="getResourceAsStream">
  11.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  12. </bean>
  13. <bean id="mySqlSessionFactoryBuilder" />
  14. <bean id="mySqlSessionFactory"
  15.       factory-bean="mySqlSessionFactoryBuilder"
  16.       factory-method="build">
  17.     <constructor-arg ref="myInputStream"/>
  18. </bean>// 返回数据源实例,具体代码略
  19.     }
  20.    
  21. }
复制代码
项目启动后,我们发现一个原有的通过XML定义的HSF(HSF全称High-speed Service Framework,是阿里内部主要使用的RPC服务框架)客户端bean中的配置项无法被正常解析。由于这是一个与我们新引入的包无关的bean,大家都对问题产生的原因感到奇怪,也尝试了各种不同的处理方式,然而都没有效果。无奈之下,我们通过将整个XML文件改写为Java注解声明的形式,才最终解决了问题。相关代码如下所示:
  1. <bean id="myInputStream"
  2.       factory-method="getResourceAsStream">
  3.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  4. </bean>
  5. <bean id="mySqlSessionFactoryBuilder" />
  6. <bean id="mySqlSessionFactory"
  7.       factory-bean="mySqlSessionFactoryBuilder"
  8.       factory-method="build">
  9.     <constructor-arg ref="myInputStream"/>
  10. </bean>    com.taobao.custom.MyHsfClient<bean id="thirdPartySqlSessionFactory"
  11.       
  12.       depends-on="myDataSource">
  13.    
  14.     <property name="dataSource" ref="myDataSource"/>
  15.     <property name="mapperLocations" value="classpath:mybatis/third-party/*.xml"/>
  16. </bean>
  17. <bean id="thirdPartyMapperScannerConfigurer"
  18.       
  19.       depends-on="thirdPartySqlSessionFactory">
  20.     <property name="basePackage" value="com.alibaba.thirdparty.dao"/>
  21.     <property name="sqlSessionFactoryBeanName" value="thirdPartySqlSessionFactory"/>
  22. </bean>${hsf.client.version}   
复制代码
 
  1. // 改写后的Java注解声明方式
  2. @Configuration
  3. public class MyHsfConfig {
  4.     @HSFConsumer(serviceVersion = "${hsf.client.version}")
  5.     private MyHsfClient myHsfClient;
  6.     // 其余代码省略
  7. }
复制代码
  1.  
复制代码
虽然问题得到了解决,但是大家仍旧对这其中的原因不明所以。笔者在事后通过本地调试的方式,找到了问题的原因。这其中涉及到bean定义注册表后置处理、bean工厂后置处理、工厂bean等Spring相关的概念。本文将以上述问题作为切入点,进行分析和展开介绍。
XML配置项解析
为了更好地解答上述问题产生的原因,我们先来看下Spring框架对bean使用的配置项的解析过程。我们知道,Spring会负责对我们在XML文件中声明的bean的创建。不过,对其中的配置项解析,并不是在这个环节发生,而是在其前置环节 —— bean工厂后置处理的过程中发生的。bean工厂(BeanFactory)是Spring的核心组件,除了负责初始化bean的实例,记录单例外,它还维护了各个bean的定义(BeanDefinition)。bean的定义中主要记录了bean的类型、作用域(singleton/prototype)、属性值、构造函数参数值等信息。bean的实例化便是基于bean的定义进行的。而bean工厂的后置处理环节,则可以在bean被创建之前,修改bean的定义,以达到影响最终生成的bean实例的效果。对XML中配置项的解析工作,Spring是通过PropertySourcesPlaceholderConfigurer这个bean工厂后置处理器(BeanFactoryPostProcessor)完成的。其核心代码如下所示。总体思路比较简单,即遍历bean工厂中的bean定义,对于每个bean的定义,访问其属性值、构造函数参数值等信息,解析其中的配置项占位符(placeholder)。这个环节完成之后,在bean工厂对bean进行初始化之前,bean定义中的配置项占位符就已经被替换为实际的属性值了。
  1. // 处理属性值
  2. protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
  3. <bean id="myInputStream"
  4.       factory-method="getResourceAsStream">
  5.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  6. </bean>
  7. <bean id="mySqlSessionFactoryBuilder" />
  8. <bean id="mySqlSessionFactory"
  9.       factory-bean="mySqlSessionFactoryBuilder"
  10.       factory-method="build">
  11.     <constructor-arg ref="myInputStream"/>
  12. </bean>StringValueResolver valueResolver) {
  13.     BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
  14.     String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
  15.     // 遍历bean工厂中的bean名称集合
  16.     for (String curName : beanNames) {
  17.       // 跳过对自身的处理
  18. <bean id="myInputStream"
  19.       factory-method="getResourceAsStream">
  20.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  21. </bean>
  22. <bean id="mySqlSessionFactoryBuilder" />
  23. <bean id="mySqlSessionFactory"
  24.       factory-bean="mySqlSessionFactoryBuilder"
  25.       factory-method="build">
  26.     <constructor-arg ref="myInputStream"/>
  27. </bean>if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
  28. <bean id="myInputStream"
  29.       factory-method="getResourceAsStream">
  30.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  31. </bean>
  32. <bean id="mySqlSessionFactoryBuilder" />
  33. <bean id="mySqlSessionFactory"
  34.       factory-bean="mySqlSessionFactoryBuilder"
  35.       factory-method="build">
  36.     <constructor-arg ref="myInputStream"/>
  37. </bean>    // 通过bean的名称获取bean的定义
  38. <bean id="myInputStream"
  39.       factory-method="getResourceAsStream">
  40.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  41. </bean>
  42. <bean id="mySqlSessionFactoryBuilder" />
  43. <bean id="mySqlSessionFactory"
  44.       factory-bean="mySqlSessionFactoryBuilder"
  45.       factory-method="build">
  46.     <constructor-arg ref="myInputStream"/>
  47. </bean>    BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
  48. <bean id="myInputStream"
  49.       factory-method="getResourceAsStream">
  50.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  51. </bean>
  52. <bean id="mySqlSessionFactoryBuilder" />
  53. <bean id="mySqlSessionFactory"
  54.       factory-bean="mySqlSessionFactoryBuilder"
  55.       factory-method="build">
  56.     <constructor-arg ref="myInputStream"/>
  57. </bean>    try {
  58. <bean id="myInputStream"
  59.       factory-method="getResourceAsStream">
  60.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  61. </bean>
  62. <bean id="mySqlSessionFactoryBuilder" />
  63. <bean id="mySqlSessionFactory"
  64.       factory-bean="mySqlSessionFactoryBuilder"
  65.       factory-method="build">
  66.     <constructor-arg ref="myInputStream"/>
  67. </bean><bean id="myInputStream"
  68.       factory-method="getResourceAsStream">
  69.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  70. </bean>
  71. <bean id="mySqlSessionFactoryBuilder" />
  72. <bean id="mySqlSessionFactory"
  73.       factory-bean="mySqlSessionFactoryBuilder"
  74.       factory-method="build">
  75.     <constructor-arg ref="myInputStream"/>
  76. </bean>// 访问bean的定义,解析并替换其中的配置项占位符
  77. <bean id="myInputStream"
  78.       factory-method="getResourceAsStream">
  79.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  80. </bean>
  81. <bean id="mySqlSessionFactoryBuilder" />
  82. <bean id="mySqlSessionFactory"
  83.       factory-bean="mySqlSessionFactoryBuilder"
  84.       factory-method="build">
  85.     <constructor-arg ref="myInputStream"/>
  86. </bean><bean id="myInputStream"
  87.       factory-method="getResourceAsStream">
  88.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  89. </bean>
  90. <bean id="mySqlSessionFactoryBuilder" />
  91. <bean id="mySqlSessionFactory"
  92.       factory-bean="mySqlSessionFactoryBuilder"
  93.       factory-method="build">
  94.     <constructor-arg ref="myInputStream"/>
  95. </bean>visitor.visitBeanDefinition(bd);
  96. <bean id="myInputStream"
  97.       factory-method="getResourceAsStream">
  98.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  99. </bean>
  100. <bean id="mySqlSessionFactoryBuilder" />
  101. <bean id="mySqlSessionFactory"
  102.       factory-bean="mySqlSessionFactoryBuilder"
  103.       factory-method="build">
  104.     <constructor-arg ref="myInputStream"/>
  105. </bean>    }
  106. <bean id="myInputStream"
  107.       factory-method="getResourceAsStream">
  108.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  109. </bean>
  110. <bean id="mySqlSessionFactoryBuilder" />
  111. <bean id="mySqlSessionFactory"
  112.       factory-bean="mySqlSessionFactoryBuilder"
  113.       factory-method="build">
  114.     <constructor-arg ref="myInputStream"/>
  115. </bean>    catch (Exception ex) {
  116. <bean id="myInputStream"
  117.       factory-method="getResourceAsStream">
  118.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  119. </bean>
  120. <bean id="mySqlSessionFactoryBuilder" />
  121. <bean id="mySqlSessionFactory"
  122.       factory-bean="mySqlSessionFactoryBuilder"
  123.       factory-method="build">
  124.     <constructor-arg ref="myInputStream"/>
  125. </bean><bean id="myInputStream"
  126.       factory-method="getResourceAsStream">
  127.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  128. </bean>
  129. <bean id="mySqlSessionFactoryBuilder" />
  130. <bean id="mySqlSessionFactory"
  131.       factory-bean="mySqlSessionFactoryBuilder"
  132.       factory-method="build">
  133.     <constructor-arg ref="myInputStream"/>
  134. </bean>throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
  135. <bean id="myInputStream"
  136.       factory-method="getResourceAsStream">
  137.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  138. </bean>
  139. <bean id="mySqlSessionFactoryBuilder" />
  140. <bean id="mySqlSessionFactory"
  141.       factory-bean="mySqlSessionFactoryBuilder"
  142.       factory-method="build">
  143.     <constructor-arg ref="myInputStream"/>
  144. </bean>    }
  145. <bean id="myInputStream"
  146.       factory-method="getResourceAsStream">
  147.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  148. </bean>
  149. <bean id="mySqlSessionFactoryBuilder" />
  150. <bean id="mySqlSessionFactory"
  151.       factory-bean="mySqlSessionFactoryBuilder"
  152.       factory-method="build">
  153.     <constructor-arg ref="myInputStream"/>
  154. </bean>}
  155.     }
  156.     // 将配置项解析器注册添加至bean工厂,供基于注解的配置项解析处理器使用(后文将详细介绍)
  157.     // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
  158.     beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
  159.     // 其余代码省略
  160. }
复制代码
了解了bean工厂后置处理环节后,让我们再往前探究一步,看下bean定义本身是如何被加载到bean工厂中的(这将有助于我们理解文章开头所提到的问题的产生原因)。bean定义主要是在bean定义注册表后置处理环节被加载到bean工厂中的。与我们前面提到的bean工厂后置处理环节类似,该环节也存在相应的处理器(BeanDefinitionRegistryPostProcessor)完成相关工作。其中典型的如ConfigurationClassPostProcessor。以Spring Boot场景为例,简单来说,该bean定义注册表后置处理器会从包含了@SpringBootApplication注解的启动引导类开始,根据其组合注解@ComponentScan,扫描被@Component,或者组合了@Component的注解(如@Configuration、@Service、@Repository等)标注的类,将这些配置类(注1)的bean定义注册至bean工厂。同时,处理器还会根据组合注解@EnableAutoConfiguration,获取Spring Boot中的自动配置类。在这之后,ConfigurationClassPostProcessor会尝试解析各个配置类中包含的@Bean、@ImportResource等注解,将对应的bean定义也注册到bean工厂中。最后,对于配置项本身来说,Spring的环境抽象(Environment)会拉取并聚合JVM系统属性、操作系统环境变量、应用属性配置文件等多个属性源的数据(注2),以供bean工厂中的bean定义或者bean实例使用。如前面提到的PropertySourcesPlaceholderConfigurer处理器,便是从Spring环境中获取bean定义中的配置项占位符所对应的属性值,并将其替换的。上文通过倒序的方式介绍了配置项解析的相关环节,下面我们用顺序表示的流程图作结,以便读者更好地理解。问题原因分析
现在,我们可以对文章开头提到的问题作进一步分析了。仔细查看我们所引入的XML文件可以发现,其中包含一个类型为MapperScannerConfigurer的bean声明。Spring借助该类完成对标注有@Mapper注解的MyBatis映射接口的扫描。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,是一个bean定义注册表后置处理器。它对映射接口的扫描及其对应的bean定义的注册,便是在该环节进行的。前面我们提到,ConfigurationClassPostProcessor这个bean定义注册表后置处理器会扫描并加载@Configuration和@ImportResource注解相关的bean定义。我们所引入的XML文件中的bean的定义,便是通过这个动作被注册到bean工厂中的(见上文MyDataSourceConfiguration配置类)。在ConfigurationClassPostProcessor完成其扫描及加载工作后,由于有新的bean定义被注册,Spring会再次尝试从bean工厂中找出并初始化其他的bean定义注册表后置处理器,以触发它们的处理动作。MapperScannerConfigurer便是在此时被实例化并触发的。观察问题背景介绍章节中的相关代码可以发现,MapperScannerConfigurer的bean实例(thirdPartyMapperScannerConfigurer)间接依赖了我们通过@Bean注解在配置类中声明的数据源bean实例(myDataSource)。因此,在本文案例中,Spring在创建MapperScannerConfigurer实例时,会首先对数据源bean进行初始化。而对于通过@Bean注解声明的bean,Spring是通过反射调用注解所在的工厂方法(factory method),完成bean的实例化的。我们的数据源myDataSource的实例化,便是通过反射调用其工厂方法createMyDataSource完成的。由于该方法包含了一个类型为Environment入参,Spring需要遍历bean工厂中的bean定义,找到并创建匹配的bean,作为反射调用时的方法传参。而问题恰恰就出现在这里的参数匹配环节。Spring在进行方法入参匹配时,会首先调用getBeanNamesForType方法,将符合参数类型的bean的名称找出来,然后依据一定的策略(注3)将bean进行实例化,作为方法入参使用。对于普通的bean来说,Spring只需要依据bean定义中包含的bean类型信息,与参数类型作匹配即可;而对于另一类较为特殊的工厂bean(FactoryBean)来说,其类型推断方式就会更加复杂些。下文将会展开介绍工厂bean的概念和案例,对此不太熟悉的读者,这里只需要了解,工厂bean的作用是负责产生某个我们最终实际需要使用的bean。因此,在进行参数匹配时,Spring关心的是这个最终产生的bean的类型,而不是工厂bean本身的类型。在判断工厂bean实际输出的bean的类型时(注4),Spring首先会尝试根据工厂bean定义中的某些元数据进行类型推断;其次会尝试对工厂bean进行一次简单创建后,通过其getObjectType方法获取目标bean的类型。如果前两种尝试都失败了,则会使用兜底逻辑 —— 对工厂bean进行正式创建后,再通过getObjectType获取类型信息。这里的「正式创建」,我们可以理解为Spring完成了工厂bean的实例化、属性字段的赋值、单例信息的记录等;而「简单创建」仅仅指工厂bean的实例化,不包括后续的字段初始化等动作。而我们在上文提到的myHsfClient,便是被声明为了一个类型为HSFSpringConsumerBean的工厂bean。Spring在对createMyDataSource的方法入参进行类型匹配时,由于前述的前两种类型推断方式都没有成功(其具体原因将在后文工厂bean小节中介绍),导致该工厂bean最终被「提前」正式创建了出来。读者可能已经发现,此时Spring正处在bean定义注册表后置处理环节。而我们在XML配置项解析章节中提到的对bean定义中的配置项占位符的解析替换,则是在该环节之后的bean工厂后置处理环节进行的 —— 这就是导致myHsfClient这个工厂bean中的配置项没有被正常解析的原因。整体方法调用关系如下图所示:至此可能读者会有疑问:难道我们的项目中之前没有对@Mapper映射器接口的扫描动作吗?答案是有扫描动作,不过是通过MapperScannerRegistrar这个bean定义注册器触发的。而由于其与我们通过XML所引入的MapperScannerConfigurer的一些细微区别,使得项目中原先不存在工厂bean被提前创建的问题。由于篇幅所限,这里不再对MapperScannerRegistrar作展开介绍。知道了问题背后的原因后,寻找对应的解法也就相对简单了。对于文中案例,一方面,我们可以看到,由于thirdPartyMapperScannerConfigurer依赖了SqlSessionFactoryBean实例(这就是我们刚刚说的「细微区别」所在),导致其间接依赖了myDataSource。而考察源码可以发现,其实MapperScannerConfigurer只需要SqlSessionFactory的bean名称(sqlSessionFactoryBeanName)作为输入即可,因此我们可以把XML中相关的depends-on声明去除。另一方面,由于createMyDataSource方法入参是Spring环境抽象,我们可以改由通过使配置类实现EnvironmentAware接口的方式,获得应用上下文中的Environment实例。这两种方法都能解决我们的工厂bean被提前创建的问题。在更一般化的场景中,如果在Spring启动的早期阶段,对某个bean的依赖注入无法避免,我们可以使相关的类实现ApplicationContextAware接口,尝试通过应用上下文(ApplicationContext)的getBean方法获取我们想要的对象。不过需要注意的是,getBean方法存在两类版本:根据bean名称获取实例,或是根据指定类型获取实例;而如果我们选择根据指定类型获取实例,则仍旧会触发上文提到的类型匹配机制,导致某些无法通过正常方式进行类型推断的工厂bean被提前创建出来。最后,对于前文提到的,在使用注解形式改写myHsfClient的bean声明后,问题得到解决的原因,我们将在后文分析介绍。一些引申扩展
经过上文让人感觉有些绕的分析,我们可以看到,文章开头所提到的问题的本质是,某些bean被Spring提前正式创建了出来,导致其bean声明中的配置项占位符没有来得及被解析和替换。这其中涉及到不少概念,诸如bean定义注册表后置处理、bean工厂后置处理、工厂bean等。由于我们在日常开发中一般接触得不多,读者对它们的理解可能还比较模糊,下文将尝试结合实际案例,进行一些引申和扩展介绍。 
bean定义注册表后置处理
我们在前文中已经介绍了ConfigurationClassPostProcessor和MapperScannerConfigurer这两个bean定义注册表后置处理器。这类处理器的主要作用便是扫描并向bean工厂中注册bean定义。其中,ConfigurationClassPostProcessor负责扫描配置类,处理其包含的注解,并将相关的bean定义注册至bean工厂中。随后,对于这些新增的bean定义,如果其中又包含了其他的bean定义注册表后置处理器,Spring会将它们实例化,并触发它们的处理动作(注5),继续注册可能被发现的新的bean定义……如此循环往复,直到所有该类型的处理器都被触发,完成bean定义的注册为止。如以下代码所示:
  1. public static void invokeBeanFactoryPostProcessors(
  2. <bean id="myInputStream"
  3.       factory-method="getResourceAsStream">
  4.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  5. </bean>
  6. <bean id="mySqlSessionFactoryBuilder" />
  7. <bean id="mySqlSessionFactory"
  8.       factory-bean="mySqlSessionFactoryBuilder"
  9.       factory-method="build">
  10.     <constructor-arg ref="myInputStream"/>
  11. </bean>ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
  12.     // 部分代码省略
  13.    
  14.     // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
  15.     boolean reiterate = true;
  16.     while (reiterate) {
  17. <bean id="myInputStream"
  18.       factory-method="getResourceAsStream">
  19.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  20. </bean>
  21. <bean id="mySqlSessionFactoryBuilder" />
  22. <bean id="mySqlSessionFactory"
  23.       factory-bean="mySqlSessionFactoryBuilder"
  24.       factory-method="build">
  25.     <constructor-arg ref="myInputStream"/>
  26. </bean>reiterate = false;
  27. <bean id="myInputStream"
  28.       factory-method="getResourceAsStream">
  29.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  30. </bean>
  31. <bean id="mySqlSessionFactoryBuilder" />
  32. <bean id="mySqlSessionFactory"
  33.       factory-bean="mySqlSessionFactoryBuilder"
  34.       factory-method="build">
  35.     <constructor-arg ref="myInputStream"/>
  36. </bean>// 从bean工厂中找出bean定义注册表后置处理器
  37. <bean id="myInputStream"
  38.       factory-method="getResourceAsStream">
  39.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  40. </bean>
  41. <bean id="mySqlSessionFactoryBuilder" />
  42. <bean id="mySqlSessionFactory"
  43.       factory-bean="mySqlSessionFactoryBuilder"
  44.       factory-method="build">
  45.     <constructor-arg ref="myInputStream"/>
  46. </bean>postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
  47. <bean id="myInputStream"
  48.       factory-method="getResourceAsStream">
  49.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  50. </bean>
  51. <bean id="mySqlSessionFactoryBuilder" />
  52. <bean id="mySqlSessionFactory"
  53.       factory-bean="mySqlSessionFactoryBuilder"
  54.       factory-method="build">
  55.     <constructor-arg ref="myInputStream"/>
  56. </bean>for (String ppName : postProcessorNames) {
  57. <bean id="myInputStream"
  58.       factory-method="getResourceAsStream">
  59.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  60. </bean>
  61. <bean id="mySqlSessionFactoryBuilder" />
  62. <bean id="mySqlSessionFactory"
  63.       factory-bean="mySqlSessionFactoryBuilder"
  64.       factory-method="build">
  65.     <constructor-arg ref="myInputStream"/>
  66. </bean>    // 如果当前处理器尚未被触发过
  67. <bean id="myInputStream"
  68.       factory-method="getResourceAsStream">
  69.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  70. </bean>
  71. <bean id="mySqlSessionFactoryBuilder" />
  72. <bean id="mySqlSessionFactory"
  73.       factory-bean="mySqlSessionFactoryBuilder"
  74.       factory-method="build">
  75.     <constructor-arg ref="myInputStream"/>
  76. </bean>    if (!processedBeans.contains(ppName)) {
  77. <bean id="myInputStream"
  78.       factory-method="getResourceAsStream">
  79.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  80. </bean>
  81. <bean id="mySqlSessionFactoryBuilder" />
  82. <bean id="mySqlSessionFactory"
  83.       factory-bean="mySqlSessionFactoryBuilder"
  84.       factory-method="build">
  85.     <constructor-arg ref="myInputStream"/>
  86. </bean><bean id="myInputStream"
  87.       factory-method="getResourceAsStream">
  88.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  89. </bean>
  90. <bean id="mySqlSessionFactoryBuilder" />
  91. <bean id="mySqlSessionFactory"
  92.       factory-bean="mySqlSessionFactoryBuilder"
  93.       factory-method="build">
  94.     <constructor-arg ref="myInputStream"/>
  95. </bean>// 初始化处理器,并加入到本次需要触发的处理器集合中
  96. <bean id="myInputStream"
  97.       factory-method="getResourceAsStream">
  98.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  99. </bean>
  100. <bean id="mySqlSessionFactoryBuilder" />
  101. <bean id="mySqlSessionFactory"
  102.       factory-bean="mySqlSessionFactoryBuilder"
  103.       factory-method="build">
  104.     <constructor-arg ref="myInputStream"/>
  105. </bean><bean id="myInputStream"
  106.       factory-method="getResourceAsStream">
  107.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  108. </bean>
  109. <bean id="mySqlSessionFactoryBuilder" />
  110. <bean id="mySqlSessionFactory"
  111.       factory-bean="mySqlSessionFactoryBuilder"
  112.       factory-method="build">
  113.     <constructor-arg ref="myInputStream"/>
  114. </bean>currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
  115. <bean id="myInputStream"
  116.       factory-method="getResourceAsStream">
  117.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  118. </bean>
  119. <bean id="mySqlSessionFactoryBuilder" />
  120. <bean id="mySqlSessionFactory"
  121.       factory-bean="mySqlSessionFactoryBuilder"
  122.       factory-method="build">
  123.     <constructor-arg ref="myInputStream"/>
  124. </bean><bean id="myInputStream"
  125.       factory-method="getResourceAsStream">
  126.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  127. </bean>
  128. <bean id="mySqlSessionFactoryBuilder" />
  129. <bean id="mySqlSessionFactory"
  130.       factory-bean="mySqlSessionFactoryBuilder"
  131.       factory-method="build">
  132.     <constructor-arg ref="myInputStream"/>
  133. </bean>// 标记处理器为已被处理
  134. <bean id="myInputStream"
  135.       factory-method="getResourceAsStream">
  136.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  137. </bean>
  138. <bean id="mySqlSessionFactoryBuilder" />
  139. <bean id="mySqlSessionFactory"
  140.       factory-bean="mySqlSessionFactoryBuilder"
  141.       factory-method="build">
  142.     <constructor-arg ref="myInputStream"/>
  143. </bean><bean id="myInputStream"
  144.       factory-method="getResourceAsStream">
  145.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  146. </bean>
  147. <bean id="mySqlSessionFactoryBuilder" />
  148. <bean id="mySqlSessionFactory"
  149.       factory-bean="mySqlSessionFactoryBuilder"
  150.       factory-method="build">
  151.     <constructor-arg ref="myInputStream"/>
  152. </bean>processedBeans.add(ppName);
  153. <bean id="myInputStream"
  154.       factory-method="getResourceAsStream">
  155.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  156. </bean>
  157. <bean id="mySqlSessionFactoryBuilder" />
  158. <bean id="mySqlSessionFactory"
  159.       factory-bean="mySqlSessionFactoryBuilder"
  160.       factory-method="build">
  161.     <constructor-arg ref="myInputStream"/>
  162. </bean><bean id="myInputStream"
  163.       factory-method="getResourceAsStream">
  164.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  165. </bean>
  166. <bean id="mySqlSessionFactoryBuilder" />
  167. <bean id="mySqlSessionFactory"
  168.       factory-bean="mySqlSessionFactoryBuilder"
  169.       factory-method="build">
  170.     <constructor-arg ref="myInputStream"/>
  171. </bean>// 继续循环,因为当前集合中的处理器被触发后,可能会引入新的bean定义,其中可能包含新的bean定义注册表后置处理器需要被触发
  172. <bean id="myInputStream"
  173.       factory-method="getResourceAsStream">
  174.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  175. </bean>
  176. <bean id="mySqlSessionFactoryBuilder" />
  177. <bean id="mySqlSessionFactory"
  178.       factory-bean="mySqlSessionFactoryBuilder"
  179.       factory-method="build">
  180.     <constructor-arg ref="myInputStream"/>
  181. </bean><bean id="myInputStream"
  182.       factory-method="getResourceAsStream">
  183.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  184. </bean>
  185. <bean id="mySqlSessionFactoryBuilder" />
  186. <bean id="mySqlSessionFactory"
  187.       factory-bean="mySqlSessionFactoryBuilder"
  188.       factory-method="build">
  189.     <constructor-arg ref="myInputStream"/>
  190. </bean>reiterate = true;
  191. <bean id="myInputStream"
  192.       factory-method="getResourceAsStream">
  193.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  194. </bean>
  195. <bean id="mySqlSessionFactoryBuilder" />
  196. <bean id="mySqlSessionFactory"
  197.       factory-bean="mySqlSessionFactoryBuilder"
  198.       factory-method="build">
  199.     <constructor-arg ref="myInputStream"/>
  200. </bean>    }
  201. <bean id="myInputStream"
  202.       factory-method="getResourceAsStream">
  203.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  204. </bean>
  205. <bean id="mySqlSessionFactoryBuilder" />
  206. <bean id="mySqlSessionFactory"
  207.       factory-bean="mySqlSessionFactoryBuilder"
  208.       factory-method="build">
  209.     <constructor-arg ref="myInputStream"/>
  210. </bean>}
  211. <bean id="myInputStream"
  212.       factory-method="getResourceAsStream">
  213.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  214. </bean>
  215. <bean id="mySqlSessionFactoryBuilder" />
  216. <bean id="mySqlSessionFactory"
  217.       factory-bean="mySqlSessionFactoryBuilder"
  218.       factory-method="build">
  219.     <constructor-arg ref="myInputStream"/>
  220. </bean>sortPostProcessors(currentRegistryProcessors, beanFactory);
  221. <bean id="myInputStream"
  222.       factory-method="getResourceAsStream">
  223.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  224. </bean>
  225. <bean id="mySqlSessionFactoryBuilder" />
  226. <bean id="mySqlSessionFactory"
  227.       factory-bean="mySqlSessionFactoryBuilder"
  228.       factory-method="build">
  229.     <constructor-arg ref="myInputStream"/>
  230. </bean>registryProcessors.addAll(currentRegistryProcessors);
  231. <bean id="myInputStream"
  232.       factory-method="getResourceAsStream">
  233.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  234. </bean>
  235. <bean id="mySqlSessionFactoryBuilder" />
  236. <bean id="mySqlSessionFactory"
  237.       factory-bean="mySqlSessionFactoryBuilder"
  238.       factory-method="build">
  239.     <constructor-arg ref="myInputStream"/>
  240. </bean>// 触发集合中的处理器的bean定义注册表后置处理动作
  241. <bean id="myInputStream"
  242.       factory-method="getResourceAsStream">
  243.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  244. </bean>
  245. <bean id="mySqlSessionFactoryBuilder" />
  246. <bean id="mySqlSessionFactory"
  247.       factory-bean="mySqlSessionFactoryBuilder"
  248.       factory-method="build">
  249.     <constructor-arg ref="myInputStream"/>
  250. </bean>// * 本文案例中,我们在第三方XML文件中引入的MapperScannerConfigurer,便是在此时被触发的
  251. <bean id="myInputStream"
  252.       factory-method="getResourceAsStream">
  253.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  254. </bean>
  255. <bean id="mySqlSessionFactoryBuilder" />
  256. <bean id="mySqlSessionFactory"
  257.       factory-bean="mySqlSessionFactoryBuilder"
  258.       factory-method="build">
  259.     <constructor-arg ref="myInputStream"/>
  260. </bean>invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
  261. <bean id="myInputStream"
  262.       factory-method="getResourceAsStream">
  263.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  264. </bean>
  265. <bean id="mySqlSessionFactoryBuilder" />
  266. <bean id="mySqlSessionFactory"
  267.       factory-bean="mySqlSessionFactoryBuilder"
  268.       factory-method="build">
  269.     <constructor-arg ref="myInputStream"/>
  270. </bean>currentRegistryProcessors.clear();
  271.     }
  272.    
  273. }
复制代码
回到我们的问题案例,MapperScannerConfigurer便是在上述环节被创建出来并触发的。这里,细心的读者可能会有疑问:如果我们在使用XML声明这个Mybatis的处理器时,对其中的某些属性也使用了配置项占位符,那么Spring在创建它时,是否也会遇到同样的解析问题?MapperScannerConfigurer的作者显然是考虑到了这一点 —— 处理器被触发后,支持首先尝试对它的属性字段进行配置项的解析和替换。其具体的实现方式,是构造一个新的bean工厂,将自身的bean定义注册其中,然后借助PropertySourcesPlaceholderConfigurer等处理器,对这个bean工厂执行配置项的后置处理操作;最后,用bean定义中的被解析后的属性值,替换自身实例中原有的属性值。这在一定程度上相当于模拟了Spring的bean工厂后置处理环节。其具体代码如下:
  1. /*
  2. * BeanDefinitionRegistries are called early in application startup, before
  3. * BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
  4. * loaded and any property substitution of this class' properties will fail. To avoid this, find
  5. * any PropertyResourceConfigurers defined in the context and run them on this class' bean
  6. * definition. Then update the values.
  7. */
  8. // 上面这段英文注释体现了作者的考虑,即文中描述的情况
  9. private void processPropertyPlaceHolders() {
  10.     // 获取配置项处理器实例,即PropertySourcesPlaceholderConfigurer处理器
  11.   Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
  12.     if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
  13. <bean id="myInputStream"
  14.       factory-method="getResourceAsStream">
  15.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  16. </bean>
  17. <bean id="mySqlSessionFactoryBuilder" />
  18. <bean id="mySqlSessionFactory"
  19.       factory-bean="mySqlSessionFactoryBuilder"
  20.       factory-method="build">
  21.     <constructor-arg ref="myInputStream"/>
  22. </bean>BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)
  23. <bean id="myInputStream"
  24.       factory-method="getResourceAsStream">
  25.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  26. </bean>
  27. <bean id="mySqlSessionFactoryBuilder" />
  28. <bean id="mySqlSessionFactory"
  29.       factory-bean="mySqlSessionFactoryBuilder"
  30.       factory-method="build">
  31.     <constructor-arg ref="myInputStream"/>
  32. </bean>  .getBeanFactory().getBeanDefinition(beanName);
  33. <bean id="myInputStream"
  34.       factory-method="getResourceAsStream">
  35.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  36. </bean>
  37. <bean id="mySqlSessionFactoryBuilder" />
  38. <bean id="mySqlSessionFactory"
  39.       factory-bean="mySqlSessionFactoryBuilder"
  40.       factory-method="build">
  41.     <constructor-arg ref="myInputStream"/>
  42. </bean>
  43. <bean id="myInputStream"
  44.       factory-method="getResourceAsStream">
  45.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  46. </bean>
  47. <bean id="mySqlSessionFactoryBuilder" />
  48. <bean id="mySqlSessionFactory"
  49.       factory-bean="mySqlSessionFactoryBuilder"
  50.       factory-method="build">
  51.     <constructor-arg ref="myInputStream"/>
  52. </bean>// 构造一个新的bean工厂
  53. <bean id="myInputStream"
  54.       factory-method="getResourceAsStream">
  55.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  56. </bean>
  57. <bean id="mySqlSessionFactoryBuilder" />
  58. <bean id="mySqlSessionFactory"
  59.       factory-bean="mySqlSessionFactoryBuilder"
  60.       factory-method="build">
  61.     <constructor-arg ref="myInputStream"/>
  62. </bean>DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
  63. <bean id="myInputStream"
  64.       factory-method="getResourceAsStream">
  65.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  66. </bean>
  67. <bean id="mySqlSessionFactoryBuilder" />
  68. <bean id="mySqlSessionFactory"
  69.       factory-bean="mySqlSessionFactoryBuilder"
  70.       factory-method="build">
  71.     <constructor-arg ref="myInputStream"/>
  72. </bean>// 将自身的bean定义注册到这个bean工厂中
  73. <bean id="myInputStream"
  74.       factory-method="getResourceAsStream">
  75.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  76. </bean>
  77. <bean id="mySqlSessionFactoryBuilder" />
  78. <bean id="mySqlSessionFactory"
  79.       factory-bean="mySqlSessionFactoryBuilder"
  80.       factory-method="build">
  81.     <constructor-arg ref="myInputStream"/>
  82. </bean>factory.registerBeanDefinition(beanName, mapperScannerBean);
  83. <bean id="myInputStream"
  84.       factory-method="getResourceAsStream">
  85.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  86. </bean>
  87. <bean id="mySqlSessionFactoryBuilder" />
  88. <bean id="mySqlSessionFactory"
  89.       factory-bean="mySqlSessionFactoryBuilder"
  90.       factory-method="build">
  91.     <constructor-arg ref="myInputStream"/>
  92. </bean>// * 对这个bean工厂执行配置项后置处理操作
  93. <bean id="myInputStream"
  94.       factory-method="getResourceAsStream">
  95.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  96. </bean>
  97. <bean id="mySqlSessionFactoryBuilder" />
  98. <bean id="mySqlSessionFactory"
  99.       factory-bean="mySqlSessionFactoryBuilder"
  100.       factory-method="build">
  101.     <constructor-arg ref="myInputStream"/>
  102. </bean>for (PropertyResourceConfigurer prc : prcs.values()) {
  103. <bean id="myInputStream"
  104.       factory-method="getResourceAsStream">
  105.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  106. </bean>
  107. <bean id="mySqlSessionFactoryBuilder" />
  108. <bean id="mySqlSessionFactory"
  109.       factory-bean="mySqlSessionFactoryBuilder"
  110.       factory-method="build">
  111.     <constructor-arg ref="myInputStream"/>
  112. </bean>  prc.postProcessBeanFactory(factory);
  113. <bean id="myInputStream"
  114.       factory-method="getResourceAsStream">
  115.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  116. </bean>
  117. <bean id="mySqlSessionFactoryBuilder" />
  118. <bean id="mySqlSessionFactory"
  119.       factory-bean="mySqlSessionFactoryBuilder"
  120.       factory-method="build">
  121.     <constructor-arg ref="myInputStream"/>
  122. </bean>}
  123. <bean id="myInputStream"
  124.       factory-method="getResourceAsStream">
  125.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  126. </bean>
  127. <bean id="mySqlSessionFactoryBuilder" />
  128. <bean id="mySqlSessionFactory"
  129.       factory-bean="mySqlSessionFactoryBuilder"
  130.       factory-method="build">
  131.     <constructor-arg ref="myInputStream"/>
  132. </bean>
  133. <bean id="myInputStream"
  134.       factory-method="getResourceAsStream">
  135.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  136. </bean>
  137. <bean id="mySqlSessionFactoryBuilder" />
  138. <bean id="mySqlSessionFactory"
  139.       factory-bean="mySqlSessionFactoryBuilder"
  140.       factory-method="build">
  141.     <constructor-arg ref="myInputStream"/>
  142. </bean>PropertyValues values = mapperScannerBean.getPropertyValues();
  143. <bean id="myInputStream"
  144.       factory-method="getResourceAsStream">
  145.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  146. </bean>
  147. <bean id="mySqlSessionFactoryBuilder" />
  148. <bean id="mySqlSessionFactory"
  149.       factory-bean="mySqlSessionFactoryBuilder"
  150.       factory-method="build">
  151.     <constructor-arg ref="myInputStream"/>
  152. </bean>// 使用被解析处理过的值更新原有的值
  153. <bean id="myInputStream"
  154.       factory-method="getResourceAsStream">
  155.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  156. </bean>
  157. <bean id="mySqlSessionFactoryBuilder" />
  158. <bean id="mySqlSessionFactory"
  159.       factory-bean="mySqlSessionFactoryBuilder"
  160.       factory-method="build">
  161.     <constructor-arg ref="myInputStream"/>
  162. </bean>this.basePackage = updatePropertyValue("basePackage", values);
  163. <bean id="myInputStream"
  164.       factory-method="getResourceAsStream">
  165.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  166. </bean>
  167. <bean id="mySqlSessionFactoryBuilder" />
  168. <bean id="mySqlSessionFactory"
  169.       factory-bean="mySqlSessionFactoryBuilder"
  170.       factory-method="build">
  171.     <constructor-arg ref="myInputStream"/>
  172. </bean>this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
  173. <bean id="myInputStream"
  174.       factory-method="getResourceAsStream">
  175.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  176. </bean>
  177. <bean id="mySqlSessionFactoryBuilder" />
  178. <bean id="mySqlSessionFactory"
  179.       factory-bean="mySqlSessionFactoryBuilder"
  180.       factory-method="build">
  181.     <constructor-arg ref="myInputStream"/>
  182. </bean>this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
  183.     }
  184. }
复制代码
最后,值得一提的是,对于ConfigurationClassPostProcessor的bean定义本身,则是在Spring应用上下文(ApplicationContext)初始化的过程中,通过硬编码的形式被注册到bean工厂中的(注6)。这里同时被注册的还有诸如AutowiredAnnotationBeanPostProcessor等bean后置处理器,我们将在后文对此作相应介绍。 
bean工厂后置处理
当bean定义注册表后置处理环节完成后,基本上(注7)所有的bean定义都已经被注册至bean工厂中了。随后,Spring会找出所有的bean工厂后置处理器,按照一定的顺序实例化并触发它们的处理动作(优先执行实现了PriorityOrdered接口的,其次执行实现了Ordered接口的,最后执行没有实现前两个接口的)。这类处理器一般会遍历bean工厂中所有的bean定义,执行一些特定的操作。我们在前文提到的PropertySourcesPlaceholderConfigurer这个bean工厂后置处理器,便是在此时被触发的。而在这个所有bean定义都已经准备就绪的阶段,统一进行配置项占位符的解析和替换,其时机总体上也是恰当合理的。其他的比较典型的Spring内置bean工厂后置处理器还有ConfigurationBeanFactoryMetaData。这个处理器执行的动作比较简单:它会遍历bean工厂中的bean定义,记录其中的工厂方法等元数据信息。其核心代码如下所示。而这份记录的作用,我们将在后文说明。
  1. public class ConfigurationBeanFactoryMetaData implements BeanFactoryPostProcessor {
  2.    
  3.     private Map<String, MetaData> beans = new HashMap<String, MetaData>();
  4.    
  5.     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  6. <bean id="myInputStream"
  7.       factory-method="getResourceAsStream">
  8.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  9. </bean>
  10. <bean id="mySqlSessionFactoryBuilder" />
  11. <bean id="mySqlSessionFactory"
  12.       factory-bean="mySqlSessionFactoryBuilder"
  13.       factory-method="build">
  14.     <constructor-arg ref="myInputStream"/>
  15. </bean>this.beanFactory = beanFactory;
  16. <bean id="myInputStream"
  17.       factory-method="getResourceAsStream">
  18.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  19. </bean>
  20. <bean id="mySqlSessionFactoryBuilder" />
  21. <bean id="mySqlSessionFactory"
  22.       factory-bean="mySqlSessionFactoryBuilder"
  23.       factory-method="build">
  24.     <constructor-arg ref="myInputStream"/>
  25. </bean>// 遍历bean工厂中的bean定义
  26. <bean id="myInputStream"
  27.       factory-method="getResourceAsStream">
  28.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  29. </bean>
  30. <bean id="mySqlSessionFactoryBuilder" />
  31. <bean id="mySqlSessionFactory"
  32.       factory-bean="mySqlSessionFactoryBuilder"
  33.       factory-method="build">
  34.     <constructor-arg ref="myInputStream"/>
  35. </bean>for (String name : beanFactory.getBeanDefinitionNames()) {
  36. <bean id="myInputStream"
  37.       factory-method="getResourceAsStream">
  38.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  39. </bean>
  40. <bean id="mySqlSessionFactoryBuilder" />
  41. <bean id="mySqlSessionFactory"
  42.       factory-bean="mySqlSessionFactoryBuilder"
  43.       factory-method="build">
  44.     <constructor-arg ref="myInputStream"/>
  45. </bean>    BeanDefinition definition = beanFactory.getBeanDefinition(name);
  46. <bean id="myInputStream"
  47.       factory-method="getResourceAsStream">
  48.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  49. </bean>
  50. <bean id="mySqlSessionFactoryBuilder" />
  51. <bean id="mySqlSessionFactory"
  52.       factory-bean="mySqlSessionFactoryBuilder"
  53.       factory-method="build">
  54.     <constructor-arg ref="myInputStream"/>
  55. </bean>    String method = definition.getFactoryMethodName();
  56. <bean id="myInputStream"
  57.       factory-method="getResourceAsStream">
  58.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  59. </bean>
  60. <bean id="mySqlSessionFactoryBuilder" />
  61. <bean id="mySqlSessionFactory"
  62.       factory-bean="mySqlSessionFactoryBuilder"
  63.       factory-method="build">
  64.     <constructor-arg ref="myInputStream"/>
  65. </bean>    String bean = definition.getFactoryBeanName();
  66. <bean id="myInputStream"
  67.       factory-method="getResourceAsStream">
  68.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  69. </bean>
  70. <bean id="mySqlSessionFactoryBuilder" />
  71. <bean id="mySqlSessionFactory"
  72.       factory-bean="mySqlSessionFactoryBuilder"
  73.       factory-method="build">
  74.     <constructor-arg ref="myInputStream"/>
  75. </bean>    // 如果存在工厂方法元数据(如通过@Bean注解声明的bean),则将相关信息记录下来
  76. <bean id="myInputStream"
  77.       factory-method="getResourceAsStream">
  78.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  79. </bean>
  80. <bean id="mySqlSessionFactoryBuilder" />
  81. <bean id="mySqlSessionFactory"
  82.       factory-bean="mySqlSessionFactoryBuilder"
  83.       factory-method="build">
  84.     <constructor-arg ref="myInputStream"/>
  85. </bean>    if (method != null && bean != null) {
  86. <bean id="myInputStream"
  87.       factory-method="getResourceAsStream">
  88.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  89. </bean>
  90. <bean id="mySqlSessionFactoryBuilder" />
  91. <bean id="mySqlSessionFactory"
  92.       factory-bean="mySqlSessionFactoryBuilder"
  93.       factory-method="build">
  94.     <constructor-arg ref="myInputStream"/>
  95. </bean><bean id="myInputStream"
  96.       factory-method="getResourceAsStream">
  97.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  98. </bean>
  99. <bean id="mySqlSessionFactoryBuilder" />
  100. <bean id="mySqlSessionFactory"
  101.       factory-bean="mySqlSessionFactoryBuilder"
  102.       factory-method="build">
  103.     <constructor-arg ref="myInputStream"/>
  104. </bean>this.beans.put(name, new MetaData(bean, method));
  105. <bean id="myInputStream"
  106.       factory-method="getResourceAsStream">
  107.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  108. </bean>
  109. <bean id="mySqlSessionFactoryBuilder" />
  110. <bean id="mySqlSessionFactory"
  111.       factory-bean="mySqlSessionFactoryBuilder"
  112.       factory-method="build">
  113.     <constructor-arg ref="myInputStream"/>
  114. </bean>    }
  115. <bean id="myInputStream"
  116.       factory-method="getResourceAsStream">
  117.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  118. </bean>
  119. <bean id="mySqlSessionFactoryBuilder" />
  120. <bean id="mySqlSessionFactory"
  121.       factory-bean="mySqlSessionFactoryBuilder"
  122.       factory-method="build">
  123.     <constructor-arg ref="myInputStream"/>
  124. </bean>}
  125.     }
  126. }
复制代码
最后,我们来看另一个和我们的XML配置项解析问题相关的处理器。在问题背景介绍章节中我们提到,当把myHsfClient的bean声明改写为由@HSFConsumer注解修饰的形式后,问题得到了解决。而这背后则是HsfConsumerPostProcessor这个bean工厂后置处理器在发挥作用:对于每一个bean定义,如果它的类属性字段上存在@HSFConsumer注解,处理器会动态生成并注册一个类型为HSFSpringConsumerBean的工厂bean定义。虽然由于PropertySourcesPlaceholderConfigurer处理器实现了PriorityOrdered接口,在此之前已经被优先执行过了,但是HsfConsumerPostProcessor考虑到了这一点 —— 在生成工厂bean定义的过程中,会主动尝试解析相关属性的配置项占位符,因此规避了我们在使用XML方式进行工厂bean声明时遇到的问题。 
工厂bean
前面我们提到,HSFSpringConsumerBean是一个工厂bean。不仅如此,我们详细讨论的Mybatis的MapperScannerConfigurer处理器,对于其基于@Mapper注解扫描到的映射接口,也会将其bean定义改写为MapperFactoryBean这个工厂bean类型。此外,在Spring中,用于创建Mybatis的SqlSession对象的SqlSessionFactory,也是由一个名为SqlSessionFactoryBean的工厂bean生成的。那么,什么是工厂bean,它的作用又是什么呢?工厂bean,即FactoryBean,其基于工厂模式,创建我们最终需要的bean实例。根据Spring文档中的介绍(注8),如果某个bean的初始化逻辑较为复杂,不适合使用XML的方式表达,那么我们可以通过使用工厂bean,以Java语言的方式完成目标bean的初始化。工厂bean的概念早在Spring 0.9版本(注9)就已经被引入,在Spring框架中的使用是比较普遍的,至今为止仅其自带的实现就有50多个。下面我们就文中的案例展开介绍。
  1. // MyBatis配置文件路径。配置文件包含数据源、映射器等信息
  2. String resource = "org/mybatis/example/mybatis-config.xml";
  3. // 创建配置文件输入流
  4. InputStream inputStream = Resources.getResourceAsStream(resource);
  5. // 创建SqlSessionFactory实例
  6. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  7. // 创建SqlSession实例
  8. try (SqlSession session = sqlSessionFactory.openSession()) {
  9.   // 获取BlogMapper映射器
  10.     BlogMapper mapper = session.getMapper(BlogMapper.class);
  11.     // 执行查询语句
  12.     Blog blog = mapper.selectBlog(101);
  13. }
复制代码
在介绍Mybatis与Spring整合时使用的两个工厂bean之前,我们先来看下相关功能单纯基于Mybatis本身实现时的代码。代码片段摘自Mybatis官网,如上所示。可以看到,其中SqlSessionFactory实例是由SqlSessionFactoryBuilder创建的;而用于执行查询语句的映射器实例,则是由SqlSession实例的getMapper方法创建的。与之相对的,如果阅读源码可以发现,在Mybatis-Spring中,用于创建SqlSessionFactory实例的SqlSessionFactoryBean和映射器实例的MapperFactoryBean这两个工厂bean,在一定程度上可以看作是对上述代码封装和扩展。
  1. <bean id="myInputStream"
  2.       factory-method="getResourceAsStream">
  3.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  4. </bean>
  5. <bean id="mySqlSessionFactoryBuilder" />
  6. <bean id="mySqlSessionFactory"
  7.       factory-bean="mySqlSessionFactoryBuilder"
  8.       factory-method="build">
  9.     <constructor-arg ref="myInputStream"/>
  10. </bean>
复制代码
可以看到,虽然借助如上所示的factory-bean和factory-method标签属性,我们也能通过XML完成对SqlSessionFactory的声明,但这种通过XML刻画bean初始化过程的方式,与我们在问题背景介绍章节看到的基于工厂bean的声明方式相比,不免显得有些繁琐了。不过,随着Spring 3.0带来的基于@Configuration的Java注解配置特性,工厂bean在这方面的优势也变得不再那么明显了。
  1. public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {    // 映射器接口类型    private Class mapperInterface;    // 通过该方法获取我们实际需要的映射器实例    @Override    public T getObject() throws Exception {<bean id="myInputStream"
  2.       factory-method="getResourceAsStream">
  3.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  4. </bean>
  5. <bean id="mySqlSessionFactoryBuilder" />
  6. <bean id="mySqlSessionFactory"
  7.       factory-bean="mySqlSessionFactoryBuilder"
  8.       factory-method="build">
  9.     <constructor-arg ref="myInputStream"/>
  10. </bean>return getSqlSession().getMapper(this.mapperInterface);    }    // 获取实际的bean的类型,即映射器接口类型    @Override    public Class getObjectType() {      return this.mapperInterface;    }    }
复制代码
不过,当我们考察如上MapperFactoryBean的源码时,会发现它的bean初始化逻辑很简单,与单纯基于MyBatis的代码实现如出一辙。其中仅有的不同是,这里getMapper方法的映射器类型入参,使用的是工厂bean中的mapperInterface属性。前面我们提到,MapperScannerConfigurer在扫描被@Mapper注解标注的映射器接口时,会为每个接口生成一个对应的bean定义,并将bean定义的类型属性改写为工厂bean类型。而对于bean定义中mapperInterface属性的设置,也是在此时完成的(属性的值即为映射器接口的全限定名)。随后,在bean的实例化环节,Spring便可以基于这些bean定义,为每个映射器接口生成一个对应的工厂bean,以此服务于我们开发中常用的映射器实例依赖注入场景。对此,如果通过Java注解配置或是XML声明的方式实现,则会显得有些大费周章 —— 对于每一个Mybatis映射器接口,我们都需要作一次对应的声明;而如果一个项目中包含数十个映射器接口(这个量级在中大型项目中应属常见),则需要做数十次大同小异的声明。对于HSFSpringConsumerBean这个工厂bean来说,其作用也是类似。这类bean注入方式的共性是:基于注解(或接口)扫描以及一些相关的配置信息,为每个被标注的接口生成一个对应的工厂bean;而当工厂bean通过getObject方法输出我们最终需要的bean时,往往是基于配置信息为接口生成一个动态代理,供实际使用。这种做法常见于Spring与其他框架集成的场景。就我们文中分析的例子而言,在数据库持久化领域,除了Mybatis外,Hibernate借助JpaRepositoryFactoryBean这个工厂bean生成其Repository接口的实例;在远程调用领域,除了HSF外,Spring Cloud中的Feign通过FeignClientFactoryBean为标注有@FeignClient注解的客户端接口生成动态代理。由于篇幅所限,这里仅以MyBatis为例,展示其类结构关系(见下图)。对于其他的案例,我们不再一一展开分析,感兴趣的读者可以阅读相关源码作进一步了解。回到我们文章中探讨的配置项解析问题,可以看到,虽然工厂bean能为Spring与其他框架整合提供很多便利,但如果使用不慎,则可能导致一些隐蔽的问题。其实,在2015年,MyBatis的MapperFactoryBean也遇到了类似的与类型推断相关的问题(详见github - mybatis-spring issue #58及pull request #59),而社区对此的解决方式是:利用Spring对bean进行实例化时,会首先尝试匹配有参构造函数的特性,在MapperFactoryBean中新增一个以映射器类型为入参的构造函数;并在处理工厂bean定义的阶段,将映射器类型作为构造函数参数,放入bean定义中(如下图所示)。如此,在前文提到的「简单创建」后,Spring便可以通过调用getObjectType方法获取到当前MapperFactoryBean实例所代表的映射器接口类型了。最后,回到本次问题的关键点之一:HSFSpringConsumerBean。在使用XML声明的方式时,虽然我们在工厂bean的interfaceName字段指定了客户端接口类型,但Spring在尝试对其进行「简单创建」以做类型推断时,并不会为实例中的属性字段赋值。这导致我们无法通过调用该实例的getObjectType方法得到它所代表的客户端接口类型,并最终导致该工厂bean被「正式创建」了出来。虽然通过@HSFConsumer注解声明的形式,我们得以规避了配置项解析问题,但HSF作者可以考虑参考MapperFactoryBean的方式,增加一个以客户端接口类型为入参的构造函数,来更好地兼容基于XML的声明方式。基于注解的配置项解析
上文主要围绕基于XML声明的配置项解析进行了分析探讨,其实,自Spring引入基于Java注解的bean声明能力以来,我们使用得更多的是基于注解的配置项解析特性。而对此特性的支持主要是通过Spring的bean后置处理器(BeanPostProcessor)完成的。绝大部分bean的后置处理是在bean的创建环节被触发的:bean工厂首先对bean进行实例化,然后使用bean后置处理器对它们进行相应的处理操作。下面我们进行简单的介绍。前面我们提到,Spring会以硬编码的形式将AutowiredAnnotationBeanPostProcessor这个bean后置处理器注册到bean工厂中。从字面上看,这个处理器是负责@Autowired注解的,其实,@Value注解也在它的处理范围之内。处理器会在bean实例化后的属性赋值步骤(注10)被触发,对@Value注解中的配置项占位符进行解析,并将属性值赋给被注解标注的字段。而其使用的配置项解析器,其中之一就是通过PropertySourcesPlaceholderConfigurer这个bean工厂后置处理器添加的(详见XML配置项解析章节)。另一个我们常见的配置项相关的注解是@ConfigurationProperties。该注解由ConfigurationPropertiesBindingPostProcessor这个bean后置处理器处理,处理动作在bean实例化后的初始化步骤(注11)被触发。除了我们熟知的作用于类的使用方式外,@ConfigurationProperties还可以作用于被@Bean注解标注的方法 —— 这主要是针对我们无法直接将注解加在第三方外部类上的情况。而这里对于方法级别的注解解析,处理器便是借助我们之前提到的ConfigurationBeanFactoryMetaData的工厂方法记录完成的(详见bean工厂后置处理小节)。具体代码如下所示:
  1. @Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {    ConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(),<bean id="myInputStream"
  2.       factory-method="getResourceAsStream">
  3.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  4. </bean>
  5. <bean id="mySqlSessionFactoryBuilder" />
  6. <bean id="mySqlSessionFactory"
  7.       factory-bean="mySqlSessionFactoryBuilder"
  8.       factory-method="build">
  9.     <constructor-arg ref="myInputStream"/>
  10. </bean>    ConfigurationProperties.class);    if (annotation != null) {<bean id="myInputStream"
  11.       factory-method="getResourceAsStream">
  12.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  13. </bean>
  14. <bean id="mySqlSessionFactoryBuilder" />
  15. <bean id="mySqlSessionFactory"
  16.       factory-bean="mySqlSessionFactoryBuilder"
  17.       factory-method="build">
  18.     <constructor-arg ref="myInputStream"/>
  19. </bean>postProcessBeforeInitialization(bean, beanName, annotation);    }    // 处理方法级别的@ConfigurationProperties注解    // 这里的this.beans即为ConfigurationBeanFactoryMetaData实例    annotation = this.beans.findFactoryAnnotation(beanName, ConfigurationProperties.class);    if (annotation != null) {<bean id="myInputStream"
  20.       factory-method="getResourceAsStream">
  21.     <constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
  22. </bean>
  23. <bean id="mySqlSessionFactoryBuilder" />
  24. <bean id="mySqlSessionFactory"
  25.       factory-bean="mySqlSessionFactoryBuilder"
  26.       factory-method="build">
  27.     <constructor-arg ref="myInputStream"/>
  28. </bean>postProcessBeforeInitialization(bean, beanName, annotation);    }    return bean;}
复制代码
思考与总结
我们可以看到,随着Spring从基于XML的bean声明到基于Java注解的bean声明能力的演化,对配置项的解析方式也在发生着变化。其中牵涉到bean工厂后置处理、bean后置处理等环节,而它们彼此之间又存在一定的关联。同时,如果某些bean(如工厂bean)由于某些原因,在Spring启动的早期阶段(如bean定义注册表后置处理环节)被提前创建了出来,则可能导致其中的配置项解析失败。对此,我们一方面可以尝试寻找规避手段,另一方面也可以从该bean本身的设计探究原因。
Spring后置处理器处理器类型说明
ConfigurationClassPostProcessorbean定义注册表后置处理器在Spring Boot中,从包含了@SpringBootApplication注解的引导类开始,扫描并注册bean定义至bean工厂。在本文案例中,MapperScannerConfigurer的bean定义便是在此时被注册的。
MapperScannerConfigurerbean定义注册表后置处理器扫描@Mapper注解标注的映射器接口,生成并注册对应的MapperFactoryBean工厂bean定义。支持使用PropertySourcesPlaceholderConfigurer等处理器对自身的属性字段进行配置项解析。
PropertySourcesPlaceholderConfigurerbean工厂后置处理器遍历bean定义,解析其中的配置项占位符。
ConfigurationBeanFactoryMetaDatabean工厂后置处理器遍历bean定义,记录工厂方法等信息。
HsfConsumerPostProcessorbean工厂后置处理器遍历bean定义,对于被@HsfConsumer注解标注的属性字段,生成并注册对应的HSFSpringConsumerBean工厂bean定义。
AutowiredAnnotationBeanPostProcessorbean后置处理器解析@Value注解中的配置项占位符。解析器之一由PropertySourcesPlaceholderConfigurer提供。
ConfigurationPropertiesBindingPostProcessorbean后置处理器解析@ConfigurationProperties注解中的配置项。对@Bean方法级别的注解解析借助ConfigurationBeanFactoryMetaData中的bean工厂方法记录完成。
为了方便读者理解,以上表格整理了文中提到的各类Spring后置处理器,以及它们彼此的关联。可以看到,Spring框架在给我们提供了很多开发便利的同时,其整体的设计还是较为复杂的。在日常开发中,我们可能时不时会遇到一些「疑难杂症」,而此时对框架的深入理解能帮助我们高效地解决问题。此外,善用对Spring代码的调试,也能帮助我们在纷繁的思路或线索中定位到问题原因。最后,由于写作时间仓促,且Spring不同版本间可能存在一定的行为差异,文中如有错漏之处还请读者包涵指正。注释:


  • 除了被@Configuration注解标注的类外,被@Component等注解标注的类也被Spring视为配置类,不过是轻量级(lite)配置类,参见《Spring Core Technologies》1.12章节 - Java-based Container Configuration。
  • 参见《Spring实战》6.1.1小节 - 理解Spring的环境抽象。
  • 对于匹配到多个bean的情况,会优先取包含@Primary注解或者优先级高的bean,如果无法判断,则会抛出NoUniqueBeanDefinitionException异常;对于没有匹配到bean的情况,抛出NoSuchBeanDefinitionException异常。
  • 具体代码详见AbstractAutowireCapableBeanFactory#getTypeForFactoryBean方法。
  • 即调用BeanDefinitionRegistryPostProcessor接口中定义的postProcessBeanDefinitionRegistry方法。
  • 具体代码详见AnnotationConfigUtils#registerAnnotationConfigProcessors方法。
  • 某些bean工厂后置处理器也会向bean工厂中添加新的bean定义,比如我们后文将讨论的HsfConsumerPostProcessor处理器。
  • 参见《Spring Core Technologies》1.8.3小节 - Customizing Instantiation Logic with a FactoryBean:If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.
  • 在FactoryBean的代码注释中,我们可以看到,该类是在2003年3月份被创建的。而根据《History of Spring Framework and Spring Boot》一文,Spring 0.9的发布时间为2003年6月。
  • 具体代码详见AbstractAutowireCapableBeanFactory#populateBean方法。
  • 具体代码详见AbstractAutowireCapableBeanFactory#initializeBean方法。
参考资料:


  • 《Spring Core Technologies》:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html
  • 《Spring实战》:https://book.douban.com/subject/36142064/
  • 《MyBatis 3源码深度解析》:https://book.douban.com/subject/34836563/
  • 《History of Spring Framework and Spring Boot》:https://www.quickprogrammingtips.com/spring-boot/history-of-spring-framework-and-spring-boot.html
  • 《SpringCloud、Nginx高并发核心编程》:https://book.douban.com/subject/35396578/
  • mybatis - getting started:https://mybatis.org/mybatis-3/getting-started.html
  • github - mybatis-spring issue #58:https://github.com/mybatis/spring/issues/58
  • github - mybatis-spring pull request #59:https://github.com/mybatis/spring/pull/59
 作者|杨天逸(在田) 
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表