容器背后的秘密

核心提示:4.4 容器背后的秘密 子曰:学而不思则罔。除了了解 Spring 的 IoC 容器如何使用,了解 Spring的 IoC 容器都提供了哪些功能,我们也应该想一下, Spring 的 IoC 容器内部到底是如何来实现这些的呢?虽然我们不太可能重新发明轮子,但是,如图 4-7 (该图摘自

4.4 容器背后的秘密

子曰:学而不思则罔。除了了解Spring的IoC容器如何使用,了解Spring的IoC容器都提供了哪些功能,我们也应该想一下,Spring的IoC容器内部到底是如何来实现这些的呢?虽然我们不太可能“重新发明轮子”,但是,如图4-7(该图摘自Spring官方参考文档)所示的那样,只告诉你“Magic Happens Here”,你是否就能心满意足呢? 

 

<!--EndFragment-->

<!--EndFragment-->图4-7 即将揭示的奥秘所在

好,如果你的答案是“不”(我当然认为你说的是“不想一直被蒙在鼓里”),那么就随我一起来探索一下这个“黑匣子”里面到底有些什么……

4.4.1 “战略性观望” 

Spring的IoC容器所起的作用,就像图4-7所展示的那样,它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。

Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段,如图4-8所示。 

Spring的IoC容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了相应的容器扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑。

图4-8 容器功能实现的各个阶段

1. 容器启动阶段

容器启动伊始,首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。图4-9演示了这个阶段的主要工作。

图4-9 XML配置信息到BeanDefinition的映射

总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。

2. Bean实例化阶段

经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefini- tionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。

该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。

4.4.2 插手“容器的启动”

Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。

如果要自定义实现BeanFactoryPostProcessor,通常我们需要实现org.springframework. beans.factory.config.BeanFactoryPostProcessor接口。同时,因为一个容器可能拥有多个Bean- FactoryPostProcessor,这个时候可能需要实现类同时实现Spring的org.springframework.core. Ordered接口,以保证各个BeanFactoryPostProcessor可以按照预先设定的顺序执行(如果顺序紧要的话)。但是,因为Spring已经提供了几个现成的BeanFactoryPostProcessor实现类,所以,大多时候,我们很少自己去实现某个BeanFactoryPostProcessor。其中,org.springframework.beans. factory.config.PropertyPlaceholderConfigurer和org.springframework.beans.factory. config.Property OverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。另外,为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring还允许我们通过org.springframework.beans.factory.config.CustomEditorConfigurer来注册自定义的Pro- pertyEditor以补助容器中默认的PropertyEditor。可以参考BeanFactoryPostProcessor的Javadoc来了解更多其实现子类的情况。

我们可以通过两种方式来应用BeanFactoryPostProcessor,分别针对基本的IoC容器BeanFactory和较为先进的容器ApplicationContext。

对于BeanFactory来说,我们需要用手动方式应用所有的BeanFactoryPostProcessor,代码清单4-41演示了具体的做法。

代码清单4-41 手动装配BeanFactory使用的BeanFactoryPostProcessor

// 声明将被后处理的BeanFactory实例

ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));

// 声明要使用的BeanFactoryPostProcessor

PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();

propertyPostProcessor.setLocation(new ClassPathResource("..."));

// 执行后处理操作

propertyPostProcessor.postProcessBeanFactory(beanFactory);

                                  

如果拥有多个BeanFactoryPostProcessor,我们可以添加更多类似的代码来应用所有的这些BeanFactoryPostProcessor。

对于ApplicationContext来说,情况看起来要好得多。因为ApplicationContext会自动识别配置文件中的BeanFactoryPostProcessor并应用它,所以,相对于BeanFactory,在ApplicationContext中加载并应用BeanFactoryPostProcessor,仅需要在XML配置文件中将这些BeanFactoryPost- Processor简单配置一下即可。只要如代码清单4-42所示,将相应BeanFactoryPostProcessor实现类添加到配置文件,ApplicationContext将自动识别并应用它。

代码清单4-42 通过ApplicationContext使用BeanFactoryPostProcessor

...

<beans>

  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <property name="locations">

      <list>

        <value>conf/jdbc.properties</value>

        <value>conf/mail.properties</value>

      </list>

    </property>

  </bean>

  ...

</beans>

                                  

下面让我们看一下Spring提供的这几个BeanFactoryPostProcessor实现都可以完成什么功能。

1. PropertyPlaceholderConfigurer

通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接信息、邮件服务器等相关信息单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单properties配置文件即可。

PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符(PlaceHolder),并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。以数据源的配置为例,使用了PropertyPlaceholderConfigurer之后(这里沿用代码清单4-42的配置内容),可以在XML配置文件中按照代码清单4-43所示的方式配置数据源,而不用将连接地址、用户名和密码等都配置到XML中。

代码清单4-43 使用了占位符的数据源配置

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

  <property name="url">

    <value>${jdbc.url}</value>

  </property>

  <property name="driverClassName">

    <value>${jdbc.driver}</value>

  </property>

  <property name="username">

    <value>${jdbc.username}</value>

  </property>

  <property name="password">

    <value>${jdbc.password}</value>

  </property>

  <property name="testOnBorrow">

    <value>true</value>

  </property>

  <property name="testOnReturn">

    <value>true</value>

  </property>

  <property name="testWhileIdle">

    <value>true</value>

  </property>

  <property name="minEvictableIdleTimeMillis">

    <value>180000</value>

  </property>

  <property name="timeBetweenEvictionRunsMillis">

    <value>360000</value>

  </property>

  <property name="validationQuery">

    <value>SELECT 1</value>

  </property>

  <property name="maxActive">

    <value>100</value>

  </property>

</bean>

                                         

如果你使用过Ant或者Velocity等工具,就会发现${property}之类的表达很熟悉。现在,所有这些占位符所代表的资源,都放到了jdbc.properties文件中,如下所示: 

 

jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932& 

failOverReadOnly=false&roundRobinLoadBalance=true

jdbc.driver=com.mysql.jdbc.Driver

jdbc.username=your username

jdbc.password=your password

 

基本机制就是之前所说的那样。当BeanFactory在第一阶段加载完成所有配置信息时,BeanFac- tory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}、${jdbc.driver}。当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时,bean定义中的属性值就是最终替换完成的了。

PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查Java的System类中的Properties,可以通过setSystemPropertiesMode()或者setSystemProper- tiesModeName()来控制是否加载或者覆盖System相应Properties的行为。PropertyPlaceholder- Configurer提供了SYSTEM_PROPERTIES_MODE_FALLBACK、SYSTEM_PROPERTIES_MODE_NEVER和SYSTEM_ PROPERTIES_MODE_OVERRIDE三种模式。默认采用的是SYSTEM_PROPERTIES_ MODE_FALLBACK,即如果properties文件中找不到相应配置项,则到System的Properties中查找,我们还可以选择不检查System的Properties或者覆盖它。更多信息请参照PropertyPlaceholderConfigurer的Javadoc文档。

2. PropertyOverrideConfigurer

PropertyPlaceholderConfigurer可以通过占位符,来明确表明bean定义中的property与properties文件中的各配置项之间的对应关系。如果说PropertyPlaceholderConfigurer做的这些是“明事”的话,那相对来说,PropertyOverrideConfigurer所做的可能就有点儿“神不知鬼不觉”了。

可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。这听起来比较抽象,我们还是给个例子吧!比如之前的dataSource定义中,maxActive的值为100,如果我们觉得100不合适,那么可以通过PropertyOverrideConfigurer在其相应的properties文件中做如下所示配置,把100这个值给覆盖掉,如将其配置为200:

 

dataSource.maxActive=200

 

这样,当容器实例化对象的时候,该dataSource对象对应的maxActive值就是200,而不是原来XML配置中的100。也就是说,PropertyOverrideConfigurer的properties文件中的配置项,覆盖掉了原来XML中的bean定义的property信息。但这样的活动,只看XML配置的话,你根本看不出哪个bean定义的哪个property会被覆盖替换掉,只有查看PropertyOverrideConfigurer指定的properties配置文件才会了解。基本上,这种覆盖替换对于bean定义来说是透明的。

如果要对容器中的某些bean定义的property信息进行覆盖,我们需要按照如下规则提供一个PropertyOverrideConfigurer使用的配置文件:

 

beanName.propertyName=value

 

也就是说,properties文件中的键是以XML中配置的bean定义的beanName为标志开始的(通常就是id指定的值),后面跟着相应被覆盖的property的名称,比如上面的maxActive。

下面是针对dataSource定义给出的PropertyOverrideConfigurer的propeties文件配置信息:

 

# pool-adjustment.properties

dataSource.minEvictableIdleTimeMillis=1000

dataSource.maxActive=50

 

这样,当按照如下代码,将PropertyOverrideConfigurer加载到容器之后,dataSource原来定义的默认值就会被pool-adjustment.properties文件中的信息所覆盖: 

 

<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">

  <property name="location" value="pool-adjustment.properties"/>

</bean>

 

pool-adjustment.properties中没有提供的配置项将继续使用原来XML配置中的默认值。

当容器中配置的多个PropertyOverrideConfigurer对同一个bean定义的同一个property值进行处理的时候,最后一个将会生效。

配置在properties文件中的信息通常都以明文表示,PropertyOverrideConfigurer的父类PropertyResourceConfigurer提供了一个protected类型的方法convertPropertyValue,允许子类覆盖这个方法对相应的配置项进行转换,如对加密后的字符串解密之后再覆盖到相应的bean定义中。当然,既然PropertyPlaceholderConfigurer也同样继承了PropertyResourceConfigurer,我们也可以针对PropertyPlaceholderConfigurer应用类似的功能。

3. CustomEditorConfigurer

其他两个BeanFactoryPostProcessor都是通过对BeanDefinition中的数据进行变更以达到某种目的。与它们有所不同,CustomEditorConfigurer是另一种类型的BeanFactoryPostProcessor实现,它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition没有做任何变动。

我们知道,不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过XML(或者properties甚至其他媒介)文件格式来配置这些对象类型。但XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer就是帮助我们传达类似信息的。

Spring内部通过JavaBean的PropertyEditor来帮助进行String类型到其他类型的转换工作。只要为每种对象类型提供一个PropertyEditor,就可以根据该对象类型取得与其相对应的PropertyEditor来做具体的类型转换。Spring容器内部在做具体的类型转换的时候,会采用JavaBean框架内默认的PropertyEditor搜寻逻辑,从而继承了对原生类型以及java.lang.String.java.awt. Color和java.awt.Font等类型的转换支持。同时,Spring框架还提供了自身实现的一些Property- Editor,这些PropertyEditor大部分都位于org.springframework. beans.propertyeditors包下。以下是这些Spring提供的部分PropertyEditor的简要说明。 

StringArrayPropertyEditor。该PropertyEditor会将符合CSV格式的字符串转换成String[]数组的形式,默认是以逗号(,)分隔的字符串,但可以指定自定义的字符串分隔符。ByteArrayPropertyEditor、CharArrayPropertyEditor等都属于类似功能的Property- Editor,参照Javadoc可以取得相应的详细信息。

ClassEditor。根据String类型的class名称,直接将其转换成相应的Class对象,相当于通过Class.forName(String)完成的功效。可以通过String[]数组的形式传入需转换的值,以达到与提供的ClassArrayEditor同样的目的。

FileEditor。Spring提供的对应java.io.File类型的PropertyEditor。同属于对资源进行定位的PropertyEditor还有InputStreamEditor、URLEditor等。

LocaleEditor。针对java.util.Locale类型的PropertyEditor,格式可以参照Local- eEditor和Locale的Javadoc说明。

PatternEditor。针对Java SE 1.4之后才引入的java.util.regex.Pattern的Property- Editor,格式可以参照java.util.regex.Pattern类的Javadoc。

以上这些PropertyEditor,容器通常会默认加载使用,所以,即使我们不告诉容器应该如何对这些类型进行转换,容器同样可以正确地完成工作。但当我们需要指定的类型没有包含在以上所提到的PropertyEditor之列的时候,就需要给出针对这种类型的PropertyEditor实现,并通过CustomEditorConfigurer告知容器,以便容器在适当的时机使用到适当的PropertyEditor。

自定义PropertyEditor

通常情况下,对于Date类型,不同的Locale、不同的系统在表现形式上存在不同的需求。如系统这个部分需要以yyyy-MM-dd的形式表现日期,系统那个部分可能又需要以yyyyMMdd的形式对日期进行转换。虽然可以使用Spring提供的CustomDateEditor,不过为了能够演示自定义PropertyEditor的详细流程,在此我们有必要“重新发明轮子”!

下面是对自定义PropertyEditor实现的简单介绍。

给出针对特定对象类型的PropertyEditor实现

假设需要对yyyy/MM/dd形式的日期格式转换提供支持。虽然可以直接让PropertyEditor实现类去实现java.beans.PropertyEditor接口,不过,通常情况下,我们可以直接继承java.beans.Property- EditorSupport类以避免实现java.beans.PropertyEditor接口的所有方法。就好像这次,我们仅仅让DatePropertyEditor完成从String到java.util.Date的转换,只需要实现setAsText(String)方法,而其他方法一概不管。该自定义PropertyEditor类定义如代码清单4-44所示。

代码清单4-44 DatePropertyEditor定义

public class DatePropertyEditor extends PropertyEditorSupport {

  private String datePattern;

 

  @Override

  public void setAsText(String text) throws IllegalArgumentException {

; color: #000000; fon

  • 大小: 13.2 KB
  • 大小: 7.7 KB
分享到:
评论

猜你喜欢

转载自aguang520.iteye.com/blog/1020565
今日推荐