Spring揭秘 学习笔记一 (Spring的IoC容器 二)

来源:互联网 发布:淘宝钻石展位有效果吗 编辑:程序博客网 时间:2024/05/22 10:49

4.3.5 bean的 scope

scope可理解为“作用域”。
scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。

我们可以通过使用的singleton或者scope属性来指定相应对象的scope,其中,scope属性只能在XSD格式的文档声明中使用,类似于如下代码所演示的形式:

DTD:<bean id="mockObject1" class="...MockBusinessObject" singleton="false"/>XSD:<bean id="mockObject2" class="...MockBusinessObject" scope="prototype"/>

容器提供的这几个scope:
①singleton
标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,也就是说,它与IoC容器“几乎”拥有相同的“寿命”。

图4-5是Spring参考文档中所给出的singleton的bean的实例化和注入语意演示图例
这里写图片描述

可以从两个方面来看待singleton的bean所具有的特性
①对象实例数量。
singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。
②对象存活时间。
singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。

通常情况下,如果你不指定bean的scope,singleton便是容器默认的scope

② prototype
针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。

对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。这样,每个请求方可以得到自己对应的一个对象实例。

你可以再次了解一下拥有prototype scope的bean定义,在实例化对象并注入依赖的时候,它的具体语意是个什么样子
这里写图片描述

③ request、session和global session
为它们只适用于Web应用程序,通常是与XmlWebApplicationContext共同使用

request
Spring容器,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request-Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。从不是很严格的意义上说,request可以看作prototype的一种特例

session
Spring容器会为每个独立的session创建属于它们自己的全新的UserPreferences对象实例。与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。

global session
global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通session类型的scope对待。

4.3.6 工厂方法与FactoryBean
有时,我们需要依赖第三方库,需要实例化并使用第三方库中的相关类,这时,接口与实现类的耦合性需要其他方式来避免

通常的做法是通过使用工厂方法(Factory Method)模式,提供一个工厂类来实例化具体的接口实现类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动

// 代码清单4-31 使用了工厂方法模式的Foo类可能定义public class Foo{  private BarInterface barInterface;   public Foo()  {     barInterface = BarInterfaceFactory.getInstance();    // 或者    // barInterface = new BarInterfaceFactory().getInstance();   // 我们应该避免这样做   // instance = new BarInterfaceImpl();  } ...}

针对使用工厂方法模式实例化对象的方式,Spring的IoC容器同样提供了对应的集成支持。我们所要做的,只是将工厂类所返回的具体的接口实现类注入给主体对象(这里是Foo)。

1. 静态工厂方法(Static Factory Method)
假设某个第三方库发布了BarInterface,为了向使用该接口的客户端对象屏蔽以后可能对BarInterface实现类的变动,同时还提供了一个静态的工厂方法实现类StaticBarInterfaceFactory,代码如下:

public class StaticBarInterfaceFactory{  public static BarInterface getInstance()  {    return new BarInterfaceImpl();  }}

为了将该静态工厂方法类返回的实现注入Foo,我们使用以下方式进行配置(通过setter方法注入方式为Foo注入BarInterface的实例):

<bean id="foo" class="...Foo">  <property name="barInterface">    <ref bean="bar"/>  </property></bean><bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>

class指定静态方法工厂类,factory-method指定工厂方法名称,然后,容器调用该静态方法工厂类的指定工厂方法(getInstance),并返回方法调用后的结果,即BarInterfaceImpl的实例。也就是说,为foo注入的bar实际上是BarInterfaceImpl的实例,即方法调用后的结果,而不是静态工厂方法类(StaticBarInterfaceFactory)。

某些时候,有的工厂类的工厂方法可能需要参数来返回相应实例,而不一定非要像我们的getInstance()这样没有任何参数。对于这种情况,可以通过<constructor-arg>来指定工厂方法需要的参数

public class StaticBarInterfaceFactory{ public static BarInterface getInstance(Foobar foobar) {  return new BarInterfaceImpl(foobar); }}
<bean id="foo" class="...Foo">  <property name="barInterface">    <ref bean="bar"/>   </property></bean><bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance">  <constructor-arg>    <ref bean="foobar"/>  </constructor-arg></bean><bean id="foobar" class="...FooBar"/>

唯一需要注意的就是,针对静态工厂方法实现类的bean定义,使用传入的是工厂方法的参数,而不是静态工厂方法实现类的构造方法的参数。(况且,静态工厂方法实现类也没有提供显式的构造方法。)

2. 非静态工厂方法(Instance Factory Method)
因为工厂方法为非静态的,我们只能通过某个容器来调用该方法

<bean id="foo" class="...Foo">   <property name="barInterface">   <ref bean="bar"/>  </property> </bean> <bean id="barFactory" class="...NonStaticBarInterfaceFactory"/><bean id="bar" factory-bean="barFactory" factory-method="getInstance"/>

NonStaticBarInterfaceFactory是作为正常的bean注册到容器的,使用factory-bean属性来指定工厂方法所在的工厂类实例,而不是通过class属性来指定工厂方法所在类的类型。指定工厂方法名则相同,都是通过factory-method属性进行的。

3. FactoryBean
FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口。它本身与其他注册到容器的对象一样,只是一个Bean而已,只不过,这种类型的Bean本身就是生产对象的工厂。

当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实org.springframework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。

FactoryBean只定义了三个方法

public interface FactoryBean {  Object getObject() throws Exception;  Class getObjectType();  boolean isSingleton();}

getObject()方法会返回该FactoryBean“生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑;
getObjectType()方法仅返回getObject()方法所返回的对象的类型,如果预先无法确定,则返回null;isSingleton()方法返回结果用于表明,工厂方法(getObject())所“生产”的对象是否要以singleton形式存在于容器中。如果以singleton形式存在,则返回true,否则返回false;

如果我们想每次得到的日期都是第二天

import org.joda.time.DateTime;import org.springframework.beans.factory.FactoryBean;public class NextDayDateFactoryBean implements FactoryBean { public Object getObject() throws Exception {  return new DateTime().plusDays(1); } public Class getObjectType() {  return DateTime.class; } public boolean isSingleton() {  return false; }}

要使用NextDayDateFactoryBean,只需要如下这样将其注册到容器即可:

<bean id="nextDayDateDisplayer" class="...NextDayDateDisplayer"> <property name="dateOfNextDay">  <ref bean="nextDayDate"/> </property></bean><bean id="nextDayDate" class="...NextDayDateFactoryBean"></bean>

NextDayDateDisplayer的定义如下:

public class NextDayDateDisplayer{  private DateTime dateOfNextDay;  // 相应的setter方法  // ...}

NextDayDateDisplayer所声明的依赖dateOfNextDay的类型为DateTime,而不是NextDayDateFactoryBean。也就是说FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所“生产”的对象类型,而非FactoryBean实现本身。
如果一定要取得FactoryBean本身的话,可以通过在bean定义的id之前加前缀&来达到目的。

Object nextDayDate = container.getBean("nextDayDate"); assertTrue(nextDayDate instanceof DateTime); Object factoryBean = container.getBean("&nextDayDate");assertTrue(factoryBean instanceof FactoryBean);assertTrue(factoryBean instanceof NextDayDateFactoryBean);Object factoryValue = ((FactoryBean)factoryBean).getObject();assertTrue(factoryValue instanceof DateTime); assertNotSame(nextDayDate, factoryValue);assertEquals(((DateTime)nextDayDate).getDayOfYear(),((DateTime)factoryValue).getDayOfYear());

4.3.7

我们直接将FX News系统中的FXNewsBean定义注册到容器中,并将其scope设置为prototype。因为它是有状态的类型,每条新闻都应该是新的独立个体;同时,我们给出MockNewsPersister类,使其实现IFXNewsPersister接口,以模拟注入FXNewsBean实例后的情况

public class MockNewsPersister implements IFXNewsPersister { private FXNewsBean newsBean; public void persistNews(FXNewsBean bean) {    persistNewes();  } public void persistNews() {   System.out.println("persist bean:"+getNewsBean()); } public FXNewsBean getNewsBean() { return newsBean;  } public void setNewsBean(FXNewsBean newsBean) {  this.newsBean = newsBean; }}

配置为

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false"></bean><bean id="mockPersister" class="..impl.MockNewsPersister"> <property name="newsBean">   <ref bean="newsBean"/> </property></bean>

当多次调用MockNewsPersister的persistNews时,会发现结果都是一样的

BeanFactory container = new XmlBeanFactory(new ClassPathResource(".."));MockNewsPersister persister = (MockNewsPersister)container.getBean("mockPersister");persister.persistNews();persister.persistNews();

这是因为第一个实例注入后,MockNewsPersister再也没有重新向容器申请新的实例。所以,容器也不会重新为其注入新的FXNewsBean类型的实例。

解决问题的关键在于保证getNewsBean()方法每次从容器中取得新的FXNewsBean实例,而不是每次都返回其持有的单一实例。

1. 方法注入(Method Injection)
只要让getNewsBean方法声明符合规定的格式,并在配置文件中通知容器,当该方法被调用的时候,每次返回指定类型的对象实例即可。

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

也就是说,该方法必须能够被子类实现或者覆写,因为容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象。

配置内容

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false"></bean><bean id="mockPersister" class="..impl.MockNewsPersister">  <lookup-method name="getNewsBean" bean="newsBean"/></bean>

通过<lookup-method>的name属性指定需要注入的方法名,bean属性指定需要注入的对象,当getNewsBean方法被调用的时候,容器可以每次返回一个新的FXNewsBean类型的实例。

使用BeanFactoryAware接口
即使没有方法注入,只要在实现getNewsBean()方法的时候,能够保证每次调用BeanFactory的getBean(“newsBean”),就同样可以每次都取得新的FXNewsBean对象实例。现在,我们唯一需要的,就是让MockNewsPersister拥有一个BeanFactory的引用。

Spring框架提供了一个BeanFactoryAware接口,容器在实例化实现了该接口的bean定义的过程中,会自动将容器本身注入该bean。这样,该bean就持有了它所处的BeanFactory的引用

public interface BeanFactoryAware {  void setBeanFactory(BeanFactory beanFactory) throws BeansException; }

让MockNewsPersister实现该接口以持有其所处的BeanFactory的引用

public class MockNewsPersister implements IFXNewsPersister,BeanFactoryAware {  private BeanFactory beanFactory;  public void setBeanFactory(BeanFactory bf) throws BeansException   {   this.beanFactory = bf;  }  public void persistNews(FXNewsBean bean) {     persistNews();  }  public void persistNews() {    System.out.println("persist bean:"+getNewsBean());  }  public FXNewsBean getNewsBean() {     return beanFactory.getBean("newsBean");  }}

配置为

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false"></bean><bean id="mockPersister" class="..impl.MockNewsPersister"></bean>

使用ObjectFactoryCreatingFactoryBean
ObjectFactoryCreatingFactoryBean是Spring 提供的一个FactoryBean实现,它返回一个ObjectFactory实例。从ObjectFactoryCreatingFactoryBean返回的这个ObjectFactory实例可以为我们返回容器管理的相关对象。实际上, ObjectFactoryCreatingFactoryBean 实现了BeanFactoryAware接口,它返回的ObjectFactory实例只是特定于与Spring容器进行交互的一个实现而已。使用它的好处就是,隔离了客户端对象对BeanFactory的直接引用。

public class MockNewsPersister implements IFXNewsPersister {  private ObjectFactory newsBeanFactory;  public void persistNews(FXNewsBean bean) {    persistNews();  }  public void persistNews()  {    System.out.println("persist bean:"+getNewsBean());  }  public FXNewsBean getNewsBean() {    return newsBeanFactory.getObject();  }  public void setNewsBeanFactory(ObjectFactory newsBeanFactory) {    this.newsBeanFactory = newsBeanFactory;  }}
<bean id="newsBean" class="..domain.FXNewsBean" singleton="false"></bean><bean id="newsBeanFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">  <property name="targetBeanName">    <idref bean="newsBean"/>  </property></bean><bean id="mockPersister" class="..impl.MockNewsPersister">  <property name="newsBeanFactory">    <ref bean="newsBeanFactory"/>  </property></bean>

3. 方法替换
方法替换更多体现在方法的实现层面上,它可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑。

想替换掉FXNewsProvider的getAndPersistNews方法默认逻辑,这时,我就可以用方法替换将它的原有逻辑给替换掉
首先,我们需要给出MethodReplacer的实现类,在这个类中实现将要替换的方法逻辑。

public class FXNewsProviderMethodReplacer implements MethodReplacer {  private static final transient Log logger =    LogFactory.getLog(FXNewsProviderMethodReplacer.class); public Object reimplement(Object target, Method method, Object[] args)  throws Throwable {  logger.info("before executing method["+method.getName()+"] on  Object["+target.getClass().getName()+"].");   System.out.println("sorry,We will do nothing this time.");  logger.info("end of executing method["+method.getName()+"] on Object["+target.getClass().getName()+"].");  return null; } }

把这个逻辑通过<replaced-method>配置到FXNewsProvider的bean定义中,使其生效

<bean id="djNewsProvider" class="..FXNewsProvider"> <constructor-arg index="0">   <ref bean="djNewsListener"/> </constructor-arg> <constructor-arg index="1">   <ref bean="djNewsPersister"/> </constructor-arg> <replaced-method name="getAndPersistNews" replacer="providerReplacer"> </replaced-method></bean><bean id="providerReplacer" class="..FXNewsProviderMethodReplacer"></bean>

4.4 容器背后的秘密

这里写图片描述

4.4.1 “战略性观望”

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

Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段
这里写图片描述

1. 容器启动阶段
首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。
这里写图片描述
总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。

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

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

4.4.2 插手“容器的启动”

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

PropertyPlaceholderConfigurer和PropertyOverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。

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

对于BeanFactory来说,我们需要用手动方式应用所有的BeanFactoryPostProcessor

// 声明将被后处理的BeanFactory实例ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));// 声明要使用的BeanFactoryPostProcessorPropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();propertyPostProcessor.setLocation(new ClassPathResource("..."));// 执行后处理操作 propertyPostProcessor.postProcessBeanFactory(beanFactory);

对于ApplicationContext来说因为ApplicationContext会自动识别配置文件中的BeanFactoryPostProcessor并应用它,所以,相对于BeanFactory,在ApplicationContext中加载并应用BeanFactoryPostProcessor,仅需要在XML配置文件中将这些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
PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符(PlaceHolder),并将这些占位符所代表的资源单独配置到简单的properties文件中来加载

以数据源的配置为例,使用了PropertyPlaceholderConfigurer之后可以在XML配置文件中按照代码清单4-43所示的方式配置数据源,而不用将连接地址、用户名和密码等都配置到XML中。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" estroy-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="maxActive">   <value>100</value> </property></bean>

现在,所有这些占位符所代表的资源,都放到了jdbc.properties文件中,如下所示:

jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932&failOverReadOnly=false&roundRobinLoadBalance=truejdbc.driver=com.mysql.jdbc.Driverjdbc.username=your usernamejdbc.password=your password

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

PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查Java的System类中的Properties,可以通过setSystemPropertiesMode()或者setSystemPropertiesModeName()来控制是否加载或者覆盖System相应Properties的行为。

2. PropertyOverrideConfigurer
可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。

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

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

# pool-adjustment.properties dataSource.minEvictableIdleTimeMillis=1000dataSource.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值进行处理的时候,最后一个将会生效

3. CustomEditorConfigurer
CustomEditorConfigurer是另一种类型的BeanFactoryPostProcessor实现,它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition没有做任何变动。

XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换,都需要这种转换规则相关的信息,而CustomEditorConfigurer就是帮助我们传达类似信息的。

Spring容器内部在做具体的类型转换的时候,会采用JavaBean框架内默认的PropertyEditor搜寻逻辑,从而继承了对原生类型以及java.lang.String.java.awt.Color和java.awt.Font等类型的转换支持。同时,Spring框架还提供了自身实现的一些PropertyEditor,这些PropertyEditor大部分都位于org.springframework. beans.propertyeditors包下。

自定义PropertyEditor
假设需要对yyyy/MM/dd形式的日期格式转换提供支持。通常情况下,我们可以直接继java.beans.PropertyEditorSupport类以避免实现java.beans.PropertyEditor接口的所有方法

public class DatePropertyEditor extends PropertyEditors{ private String datePattern; @Override public void setAsText(String text) throws IllegalArgumentException {   DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(getDatePattern());   Date dateValue = dateTimeFormatter.parseDateTime(text).toDate();   setValue(dateValue); } public String getDatePattern(){   return datePattern; } public void setDatePattern(String datePattern){   this.dataPattern=datePattern; }}

通过CustomEditorConfigurer注册自定义的PropertyEditor
默认情况下,Spring容器找不到合适的PropertyEditor将字符串“2007/10/16”转换成对象所声明的java.util.Date类型。所以,我们通过CustomEditorConfigurer将刚实现的DatePropertyEditor注册到容器,以告知容器按照DatePropertyEditor的形式进行String到Date类型的转换工作

如果使用的容器是BeanFactory的实现,比如XmlBeanFactory,就需要通过编码手动应用CustomEditorConfigurer到容器,类似如下形式:

XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));//CustomEditorConfigurer ceConfigurer = new CustomEditorConfigurer();Map customerEditors = new HashMap();customerEditors.put(java.util.Date.class, new DatePropertyEditor());ceConfigurer.setCustomEditors(customerEditors);//ceConfigurer.postProcessBeanFactory(beanFactory);

但如果使用的是ApplicationContext相应实现,因为ApplicationContext会自动识别BeanFactoryPostProcessor并应用,所以只需要在相应配置文件中配置一下

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors">  <map>   <entry key="java.util.Date">     <ref bean="datePropertyEditor"/>   </entry>  </map> </property></bean><bean id="datePropertyEditor" class="...DatePropertyEditor">  <property name="datePattern">    <value>yyyy/MM/dd</value>  </property></bean>

提倡使用propertyEditorRegistrars属性来指定自定义的PropertyEditor。不过,这样我们就需要再多做一步工作,就是给出一个org.springframePropertyEditorRegistrar的实现。

public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar { private PropertyEditor propertyEditor; public void registerCustomEditors(PropertyEditorRegistry peRegistry) {   peRegistry.registerCustomEditor(java.util.Date.class, getPropertyEditor()); } public PropertyEditor getPropertyEditor() {   return propertyEditor; } public void setPropertyEditor(PropertyEditor propertyEditor) {   this.propertyEditor = propertyEditor; }} 

配置方式

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars">  <list>    <ref bean="datePropertyEditorRegistrar"/>  </list> </property></bean><bean id="datePropertyEditorRegistrar" class="...DatePropertyEditorRegistrar"> <property name="propertyEditor">   <ref bean="datePropertyEditor"/> </property></bean><bean id="datePropertyEditor" class="...DatePropertyEditor">  <property name="datePattern">     <value>yyyy/MM/dd</value>  </property></bean>

4.4.3 了解bean的一生

在已经可以借助于BeanFactoryPostProcessor来干预Magic实现的第一个阶段动之后,我们就可以开始探索下一个阶段,即bean实例化阶段的实现逻辑了。

只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。BeanFactory的getBean法可以被客户端对象显式调用,也可以在容器内部隐式地被调用。隐式调用有如下两种情况:
①对于BeanFactory来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化对象A所依赖的对象。
②ApplicationContext启动之后会实例化所有的bean定义它会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法getBean()。

只有当对应某个bean定义的getBean()方法第一次被调用时,不管是显式的还是隐式的,Bean实例化阶段的活动才会被触发,第二次被调用则会直接返回容器缓存的第一次实例化完的对象实例(prototype类型bean除外)。
这里写图片描述

1. Bean的实例化与BeanWrapper
容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。

InstantiationStrategy定义是实例化策略的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象实例,但不支持方法注入方式的对象实例化。CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过CGLIB的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象实例化需求。默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。

容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。

BeanWrapper接口通常在Spring框架内部使用,它有一个实现类BeanWrapperImpl。其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。而在第一步结束后返回BeanWrapper实例而不是原先的对象实例,就是为了第二步“设置对象属性”

BeanWrapper定义继承了PropertyAccessor接口,可以以统一的方式对对象属性进行访问;BeanWrapper定义同时又直接或者间接继承了PropertyEditorRegistry和TypeConverter接口。在第一步构造完成对象之后,Spring会根据对象实例构造一个BeanWrapperImpl实例,然后将之前CustomEditorConfigurer注册的PropertyEditor复制一份给BeanWrapperImpl实例(这就是BeanWrapper同时又是PropertyEditorRegistry的原因)。

代码清单4-49 使用BeanWrapper操作对象,可以免去直接使用Java反射API(Java Reflection API)操作对象实例的烦琐。

Object provider = Class.forName("package.name.FXNewsProvider").newInstance(); Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();BeanWrapper newsProvider = new BeanWrapperImpl(provider); newsProvider.setPropertyValue("newsListener", listener);newsProvider.setPropertyValue("newPersistener", persister);assertTrue(newsProvider.getWrappedInstance() instanceof FXNewsProvider);assertSame(provider, newsProvider.getWrappedInstance());assertSame(listener, newsProvider.getPropertyValue("newsListener")); assertSame(persister, newsProvider.getPropertyValue("newPersistener"));

直接使用Java反射API是如何实现的(忽略了异常处理相关代码)

Object provider = Class.forName("package.name.FXNewsProvider").newInstance(); Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();Class providerClazz = provider.getClass();Field listenerField = providerClazz.getField("newsListener");listenerField.set(provider, listener);Field persisterField = providerClazz.getField("newsListener");persisterField.set(provider, persister);assertSame(listener, listenerField.get(provider)); assertSame(persister, persisterField.get(provider));

2. 各色的Aware接口
当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。

针对BeanFactory类型的容器,Aware接口为如下几个:
BeanNameAware
如果Spring容器检测到当前对象实例实现了该接口,会将该对象实例的bean定义对应的beanName设置到当前对象实例。
BeanClassLoaderAware
如果容器检测到当前对象实例实现了该接口,会将对应加载当前bean的Classloader注入当前对象实例。默认会使用加载org.springframework.util.ClassUtils类的Classloader。
BeanFactoryAware
如果对象声明实现了BeanFactoryAware接口,BeanFactory容器会将自身设置到当前对象实例。这样,当前对象实例就拥有了一个BeanFactory容器的引用,并且可以对这个容器内允许访问的对象按照需要进行访问。

对于ApplicationContext类型容器,容器在这一步还会检查以下几个Aware接口并根据接口定义设置相关依赖:
ResourceLoaderAware
当容器检测到当前对象实例实现了ResourceLoaderAware接口之后,会将当前ApplicationContext自身设置到对象实例,这样当前对象实例就拥有了其所在ApplicationContext容器的一个引用。
ApplicationEventPublisherAware
当前ApplicationContext容器如果检测到当前实例化的对象实例声明了ApplicationEventPublisherAware接口,则会将自身注入当前对象。
③ApplicationContextAware
如果ApplicationContext容器检测到当前对象实现了ApplicationContextAware接口,则会将自身注入当前对象实例。

3. BeanPostProcessor
BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段。

该接口声明了两个方法,分别在两个不同的时机执行,见如下代码定义:

public interface BeanPostProcessor{Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }

postProcessBeforeInitialization()方法是图4-10中BeanPostProcessor前置处理这一步将会执行的方法,postProcessAfterInitialization()则是对应图4-10中BeanPostProcessor后置处理那一步将会执行的方法。BeanPostProcessor的两个方法中都传入了原来的对象实例的引用,这为我们扩展容器的对象实例化过程中的行为提供了极大的便利。

在图4-10的第三步中,ApplicationContext对应的那些Aware接口实际上就是通过BeanPostProcessor的方式进行处理的。当ApplicationContext中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,ApplicationContext容器会检测到之前注册到容器的ApplicationContextAwareProcessor这个BeanPostProcessor的实现类,然后就会调用其postProcessBeforeInitialization()方法,检查并设置Aware相关依赖

postProcessBeforeInitialization方法定义:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  if (bean instanceof ResourceLoaderAware) {     ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);  }  if (bean instanceof ApplicationEventPublisherAware) {    ((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(this.applicationContext);  }   if (bean instanceof MessageSourceAware) {    ((MessageSourceAware) bean).setMessageSource(this.applicationContext);  }  if (bean instanceof ApplicationContextAware) {   ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);  }return bean; }

除了检查标记接口以便应用自定义逻辑,还可以通过BeanPostProcessor对当前对象实例做更多的处理。比如替换当前对象实例或者字节码增强当前对象实例等

自定义BeanPostProcessor
假设系统中所有的IFXNewsListener实现类需要从某个位置取得相应的服务器连接密码,而且系统中保存的密码是加密的,那么在IFXNewsListener发送这个密码给新闻服务器进行连接验证的时候,首先需要对系统中取得的密码进行解密,然后才能发送。我们将采用BeanPostProcessor技术,对所有的IFXNewsListener的实现类进行统一的解密操作。

(1) 标注需要进行解密的实现类
为了能够识别那些需要对服务器连接密码进行解密的IFXNewsListener实现,我们声明了接口PasswordDecodable,并要求相关IFXNewsListener实现类实现该接口。

public interface PasswordDecodable {  String getEncodedPassword();  void setDecodedPassword(String password);}public class DowJonesNewsListener implements IFXNewsListener,PasswordDecodable {   private String password;   public String[] getAvailableNewsIds() {      // 省略   }   public FXNewsBean getNewsByPK(String newsId) {      // 省略   }   public void postProcessIfNecessary(String newsId) {      // 省略   }   public String getEncodedPassword() {      return this.password;   }   public void setDecodedPassword(String password) {      this.password = password;   }}

(2) 实现相应的BeanPostProcessor对符合条件的Bean实例进行处理
我们通过PasswordDecodable接口声明来区分将要处理的对象实例(如果有其他方式可以区分将要处理的对象实例,那么声明类似的标记接口(Marker Interface)就不是必须的),当检查到当前对象实例实
现了该接口之后,就会从当前对象实例取得加密后的密码,并对其解密。然后将解密后的密码设置回
当前对象实例。之后,返回的对象实例所持有的就是解密后的密码

用于解密的自定义BeanPostProcessor实现类

public class PasswordDecodePostProcessor implements BeanPostProcessor {  public Object postProcessAfterInitialization(Object object, String beanName) throws BeansException {     return object;  }  public Object postProcessBeforeInitialization(Object object, String beanName)throws BeansException {   if(object instanceof PasswordDecodable)   {     String encodedPassword = ((PasswordDecodable)object).getEncodedPassword();     String decodedPassword = decodePassword(encodedPassword);     ((PasswordDecodable)object).setDecodedPassword(decodedPassword);   }   return object;  }  private String decodePassword(String encodedPassword) {   // 实现解码逻辑   return encodedPassword;  }}

(3) 将自定义的BeanPostProcessor注册到容器
对于BeanFactory类型的容器来说,我们需要通过手工编码的方式将相应的BeanPostProcessor注册到容器,也就是调用ConfigurableBeanFactory的addBeanPostProcessor()方法

ConfigurableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(...));beanFactory.addBeanPostProcessor(new PasswordDecodePostProcessor()); ... // getBean();

对于ApplicationContext容器来说直接将相应的BeanPostProcessor实现类通过通常的XML配置文件配置一下即可。ApplicationContext容器会自动识别并加载注册到容器的BeanPostProcessor

<beans>  <bean id="passwordDecodePostProcessor"  class="package.name.PasswordDecodePostProcessor">  <!--如果需要,注入必要的依赖-->  </bean>   ...</beans>

4. InitializingBean和init-method
InitializingBean是容器内部广泛使用的一个对象生命周期标识接口,其定义如下:

public interface InitializingBean {  void afterPropertiesSet() throws Exception;}

其作用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态。比如,在有些情况下,某个业务对象实例化完成后,还不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理。

Spring还提供了另一种方式来指定自定义的对象初始化操作,那就是在XML配置的时候,使用的init-method属性。

通过init-method,系统中业务对象的自定义初始化操作可以以任何方式命名,而不再受制于InitializingBean的afterPropertiesSet()。如果系统开发过程中规定:所有业务对象的自定义初始化操作都必须以init()命名,为了省去挨个的设置init-method这样的烦琐,我们还可以通过最顶层的的default-init-method统一指定这一init()方法名。

一般,我们是在集成第三方库,或者其他特殊的情况下,才会需要使用该特性。比如,ObjectLab提供了一个外汇系统交易日计算的开源实现——ObjectLabKit,系统在使用它提供的DateCalculator时,封装类会通过一个自定义的初始化方法来为这些DateCalculator提供计算交易日所需要排除的休息日信息

public class FXTradeDateCalculator {  ...  public void setupHolidays()  {    List holidays = getSystemHolidays();    if(!ListUtils.isEmpty(holidays))    {     for(int i=0,size=holidays.size();i<size;i++)     {       String holiday = (String)holidays.get(i);       LocalDate date = FRONT_DATE_FORMATTER.parseDateTime(holiday).toLocalDate();       holidaySet.add(date);     }    }    LocalDateKitCalculatorsFactory .getDefaultInstance().registerHolidays(holidayKey,holidaySet);   }    public DateCalculator<LocalDate> getForwardDateCalculator()  {     ...  }  public DateCalculator<LocalDate> getBackwardDateCalculator()  {    ...  }

为了保证getForwardDateCalculator()和getBackwardDateCalculator()方法返回的DateCalculator已经将休息日考虑进去,在这两个方法被调用之前,我们需要setupHolidays()首先被调用,以保证将休息日告知DateCalculator,使它能够在计算交易日的时候排除掉这些休息日的日期。因此,我们需要在配置文件中完成类似代码清单4-55所示的配置,以保证在对象可用之前,setupHolidays()方法会首先被调用。

<beans>   <bean id="tradeDateCalculator" class="FXTradeDateCalculator" init-method="setupHolidays">    <constructor-arg>      <ref bean="sqlMapClientTemplate"/>    </constructor-arg>  </bean>  <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">  ...  </bean>  ...</beans>

当然,我们也可以让FXTradeDateCalculator实现InitializingBean接口,然后将setupHolidays()方法的逻辑转移到afterPropertiesSet()方法。不过,相对来说还是采用init-method的方式比较灵活,并且没有那么强的侵入性。

5. DisposableBean与destroy-method
当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了DisposableBean接口。或者其对应的bean定义是否通过<bean>的destroy-method属性指定了自定义的对象销毁方法。如果是,就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。

与InitializingBean和init-method用于对象的自定义初始化相对应,DisposableBean和destroy-method为对象提供了执行自定义销毁逻辑的机会。

最常见到的该功能的使用场景就是在Spring容器中注册数据库连接池,在系统退出后,连接池应该关闭,以释放相应资源。

代码清单4-56演示了通常情况下使用destroy-method处理资源释放的数据源注册配置

<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>  ...</bean>

不过,这些自定义的对象销毁逻辑,在对象实例初始化完成并注册了相关的回调方法之后,并不会马上执行。所以,需要我们告知容器,在哪个时间点来执行对象的自定义销毁方法。

对于BeanFactory容器来说。我们需要在独立应用程序的主程序退出之前,或者其他被认为是合适的情况下(依照应用场景而定),如代码清单4-57所示,调用ConfigurableBeanFactory提供的destroySingletons()方法销毁容器中管理的所有singleton类型的对象实例。

public class ApplicationLauncher{  public static void main(String[] args) {     BasicConfigurator.configure();     BeanFactory container = new XmlBeanFactory(new ClassPathResource("..."));     BusinessObject bean = (BusinessObject)container.getBean("...");     bean.doSth();    ((ConfigurableListableBeanFactory)container).destroySingletons();     // 应用程序退出,容器关闭  }}

如果不能在合适的时机调用destroySingletons(),那么所有实现了DisposableBean接口的对象实例或者声明了destroy-method的bean定义对应的对象实例,它们的自定义对象销毁逻辑就形同虚设,因为根本就不会被执行!

AbstractApplicationContext为我们提供了registerShutdownHook()方法,该方法底层使用标准的Runtime类的addShutdownHook()方式来调用相应bean对象的销毁逻辑,从而保证在Java虚拟机退出之前,这些singtleton类型的bean对象实例的自定义销毁逻辑会被执行。当然AbstractApplicationContext注册的shutdownHook不只是调用对象实例的自定义销毁逻辑,也包括ApplicationContext相关的事件发布等

public class ApplicationLauncher{  public static void main(String[] args) {    BasicConfigurator.configure();    BeanFactory container = new ClassPathXmlApplicationContext("...");    ((AbstractApplicationContext)container).registerShutdownHook();   BusinessObject bean = (BusinessObject)container.getBean("...");   bean.doSth();   // 应用程序退出,容器关闭  }}
原创粉丝点击