《Spring揭秘》学习摘要 Part2

Spring的IoC容器是整个Spring框架的核心和基础。


IoC它的全称为Inversion of Contral,中文通常翻译“控制反转”,它还有一个别名叫做依赖注入(Dependency Injection)。好莱坞原则“Don't call us,we will call you”恰如其分的表达了“反转”的意味。


不管是通过new构造对象,还是通过ServiceLocator(Java EE核心模式的一种,主要通过引入中间代理者消除对象间复杂的耦合关系,并统一管理分散的复杂耦合关系)解决直接的依赖耦合,有一个共同点需要我们关注,那就是,我们都是自己主动地去获取依赖的对象。

通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间是通过IoC Service Provider来打交道,所有的被注入对象和被依赖对象现在有IoC Service Provider统一管理。被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的。从被注入对象的角度看,与之前的直接寻找依赖对象相比,依赖对象的获取方式方式了反转,控制也从被注入对象转移到了Ioc Service Provider那里。

Ioc模式最权威的总结和解释,应该是Martin Fowler的那篇文章“Inversion of Control Containers and the Dependency Injection pattern”,其中提到了三种依赖注入的方式,即构造方法注入(constructor injection)、setter方法注入(setter injection)以及接口注入(interface injection)。

相对于前两种依赖注入方式,接口注入比较死板和烦琐。如果需要注入依赖对象,被注入的对象就必须声明和实现另外的接口。

从注入方式的使用来说,接口注入是现在不提倡的一种方式,因为它强制被注入对象实现不必要的接口,带有侵入性。

构造方法注入的优点是,对象在构造完成之后,即已进入就绪状态,可以马上使用;缺点是,当依赖对象比较多时,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难。对于非必要的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上不便。

setter方法注入的缺点就是对象无法在构造完成后马上进入就绪状态。

构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式;而接口注入因为侵入性较强,近年来已经不流行了。


从主动获取依赖关系的方式转向IoC方式,不只是一个方向上的改变,简单的转变背后实际上蕴藏正更多的玄机——不会对业务对象构成很强的侵入性,使用IoC后,对象具有更好的可测试性、可重用性和可扩展性。对软件开发来说,设计开发可测试性良好的业务对象是至关重要的。而IoC模式可以让我们更容易达到这个目的。

IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。


IoC Service Provider的职责主要有两个——业务对象的构建管理和业务对象间的依赖关系;

IoC Service Provider需要将业务对象的构建逻辑从客户端对象那里剥离出来(A对象要么使用B对象,要么构建B对象,但不要同时做两者),以免这部分逻辑污染业务对象的实现;

IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

IoC Service Provider只是为了简化概念而提出的一个一般性的概念。


Spring提供了两种容器类型:BeanFactory和ApplicationContext。BeanFactory是基础类型的IoC容器,提供完整的IoC服务支持。ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级的特性,比如事件发布、国际化信息支持等。ApplicationContext间接继承自BeanFactory,所以说它是构建于BeanFactory之上的IoC容器。

作为Spring提供的基本的IoC容器,BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象将依赖关系的确定。

每一个受管对象,在容器中都会有一个BeanDefinition的实例与之对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。

通常情况下,需要根据不同的外部配置文件格式,给出相应的BeanDefinitionReader实现类,有BeanDefinitionReader的相应实现类负责将配置文件的内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注册到一个BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注册和加载。

Spring 2.x之前,XML配置文件采用DTD(Document Type Definition)实现文档的格式约束。2.x之后,引入了基于XSD(XML Schema Definition)的约束方式。不过,原来的基于DTD的方式依然有效,因为从DTD转向XSD只是“形式”上的转变。

对象存在多个构造方法,当参数列表数目相同而类型不同的时候,容器无法区分应该使用哪个构造方法来实例化对象,后者构造方法可能同时传入最少两个类型相同的对象。index属性的取值从0开始,与一般的数组下标取值相同。

<property>有一个name属性,用来指定该<property>将会注入的对象所对应的实例变量名称。如果只是使用<property>进行依赖注入的话,请确保对象提供了默认的构造方法。使用<property>的setter方法注入和使用<constructor-arg>的构造方法注入并不是水火不容的。

可以通过在<property>和<constructor-arg>这两个元素内部嵌套<value>或者<ref>,来指定将为当前对象注入的简单数据类型或者某个对象的引用。Spring还提供了其他元素供我们使用,这包括bean、ref、idref、value、null、list、set、map、props。

local只能指定与当前配置的对象在同一个配置文件的对象定义名称;parent则只能指定位于当前容器的父容器中定义的对象引用。

BeanFactory可以分层次(通过实现HierarchicalBeanFactory接口),容器A在初始化的时候,可以首先加载容器B中的所有对象定义,然后再加载自身的对象定义,这样,容器B就成了容器A的父容器,容器A可以引用容器B中定义的所有对象定义。

bean则基本上通吃。

使用idref,容器在解析配置文件的时候就可以帮忙检查这个beanName到底是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在。

<list>对应注入对象类型为java.util.List及其子类或者数组类型的依赖对象。

<set> 如果说<list>可以帮忙有序的注入一系列依赖的话,那么<set>就是无序的,<set>对应注入Java Collection中类型为java.util.Set或者其子类的依赖对象。

<map>与列表使用数字下标来识别元素不同,映射map可以通过指定的key来获取对应的value。<map>与<list>和<set>的相同点在于,都是为主体对象注入Collection类型的依赖,不同点在于它对应于java.util.Map或者其子类类型的依赖对象。

对于<map>来说,它可以内嵌任意多个<entry>,每一个<entry>都需要为其指定一个key和一个value,就跟真正的java.util.Map所要求的一样。指定entry的键,可以使用<value>的属性——key或者key-ref来指定键,也可以使用<entry>的内嵌元素<key>来指定键。

<props>是简化后了的<map>,或者说是特殊化的map,该元素对应配置类型为java.util.Properties的对象依赖。因为Properties只能指定String类型的key和value。每个<props>可以嵌套多个<prop>,每个<prop>通过其key属性来指定键,在<prop>内部直接指定其所对应的值。<prop>内部没有任何元素可以使用,只能指定字符串,这个是由java.util.Properties的语义决定的。


byName和byType类型的自动绑定模式是针对property的自动绑定,而constructor类型则是针对构造方法参数的类型而进行的自动绑定,它同样是byType类型的绑定模式。不过,constructor是匹配构造方法的参数类型,而不是实例属性的类型。

<beans>有一个default-autowire属性,它可以帮助省去为多个<bean>独立设置autowire属性的麻烦,default-autowire的默认值为no,即不进行自动绑定。


与BeanFactory不同,ApplicationContext在容器启动的时候,就会马上对所有的“singleton的定义”进行实例化操作。可以通过<bean>的lazy-init属性来控制这种初始化行为,ApplicationContext容器在启动的时候,只会默认实例化not-lazy-init-bean而不会实例化lazy-init-bean。仅指定lazy-init-bean的lazy为true,并不意味着容器就一定会延迟初始化该bean的实例。如果某个非延迟初始化的bean定义依赖于lazy-init-bean,那么毫无疑问,按照依赖的顺序,容器还是会首先初始化lazy-init-bean,然后在实例化后者。如果我们真想保证lazy-init-bean一定会被延迟初始化的话,就需要保证依赖于该bean定义的其他bean也同样设置为延迟初始化。


bean定义通过abstract属性声明为true,说明这个bean定义不需要实例化。这就是可以不指定class属性的少数场景之一(当然,同时指定class和abstract=“true”也是可以的)。这样,bean定义只是一个配置模板,不对应任何对象。当多个bean定义拥有多个默认属性配置的时候,会发现这种方式可以带来很大的便利。

容器在初始化对象实例的时候,不会关注将abstract属性声明为true的bean定义。如果不想容器在初始化的时候实例化某些对象,那么就可以将其abstract属性赋值为true,以避免容器将其实例化。对于ApplicationContext容器尤其如此,因为在默认情况下,ApplicationContext会在容器启动的时候就对其管理的所有bean进行实例化,只有标志为abstract的bean除外。


BeanFactory除了拥有作为IoC Service Provider的职责,作为一个轻量级容器,它还有着其他一些职责,其中就包括生命周期的管理。

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

Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另外三种scope类型,即request、session、global session类型。不过只有在支持Web应用的ApplicationContext中使用这三个scope才是合理的。

根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决定。标记为拥有singletion scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。它与IoC容器“几乎”拥有相同的“寿命”。

singleton配置:

DTD:

<bean id="mockObject1" class="...MockBusinessObject" singleton="true" />

XSD:

<bean id="mockObject2" class="...MockBusinessObject" scope="singleton" />

不要因为名字的原因而与GoF所提出的Singleton模式相混淆,二者的语意是不同的:标记为singleton的bean是由容器来保证这种类型的bean在同一个容器只存在一个共享实例:Singleton模式则是保证在同一个Classloader中只存在一个这种类型的实例。

singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。singleton类型的bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。通常情况下,不指定bean的scope,singleton便是容器默认的scope。


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

对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。通常,声明为property的scope的bean定义类型,都是一些有状态的。

property配置:

DTD:

<bean id="mockObject1" class="...MockBusinessObject" singleton="fales" />

XSD:

<bean id="mockObject2" class="...MockBusinessObject" scope="property" />


Spring容器,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的RequestProcessor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。从不是很严格的意义上说,request可以看作prototype的一种特例,除了场景更加具体之外,语意上差不多。

<bean id="requestProcessor" class="...RequestProcessor" scope="request" />


与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面没有差别。

<bean id="userPreferences" class="...UserPreferences" scope="session" />

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


工厂方法与FactoryBean

在强调“面向接口编程”的同时,有一点需要注意:虽然对象可以通过声明接口来避免对特定接口实现的过度耦合,但总归需要一种方式将声明依赖的对象与接口实现类关联起来。通常的做法是通过使用工厂方法(Factory Method)模式,提供一个工厂类来实例化具体的接口实现类。

针对使用工厂方法模式实例化对象的方式,Spring的IoC容器同样提供了对应的集成支持:

1、静态工厂方法(Static Factory Method)

<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance" />

class指定静态工厂方法类,fatory-method指定工厂方法名称。如果工厂类的工厂方法需要参数来返回相应的实例,可以通过<constructor-arg>来指定工厂方法需要的参数:

<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance">

<constructor-arg>

<value>arg0</value>

</constructor-arg>

</bean>

唯一需要注意的是,针对静态工厂方法实现类的bean定义,使用<constructor-arg>传入的是工厂方法的参数,而不是静态工厂方法实现类的构造方法的参数。

2、非静态工厂方法(Instance Factory Method)

<bean id="barFactory" class="...NotStaticBarInterfaceFactory" />

<bean id="bar" factory-bean="barFactory" factory-method="getInstance" />

使用factory-bean属性来指定工厂方法的工厂类实例,而不是通过class属性来指定工厂方法所在类的类型。指定工厂方法名称则相同,都是通过factory-method属性进行。

如果非静态工厂方法调用时也需要提供参数,那么处理方式与静态工厂方法相似,都可以通过<constructor-arg>来指定方法调用参数。

3、FactoryBean

FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean本身与其他注入到容器的对象一样,只是一个bean而已,只不过,这种类型的bean本身就是生产对象的工厂(Factory)。可以通过实现org.springframework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。Spring容器内部许多地方使用了FactoryBean,下面是一些比较常见的FactoryBean实现:

JndiObjectFactoryBean、LocalSessionFactoryBean、SqlMapClientFactoryBean、ProxyFactoryBean、TransactionProxyFactoryBean


如果一个对象的scope被定义为prototype,那么在使用该对象是,需要小心。如下的配置:

<bean id="newsBean" class="...NewsBean" singleton="false" />

<bean id="mockPersister" class="...MockPersister">

<property name="newsBean">

<ref bean="newsBean"/>

</property>

</bean>

那么每次在调用mockPersister对象的getNewsBean()方法时,返回的对象将是同一个对象。虽然newsBean对象被定义为prototype类型的scope,但当容器将一个newBean实例注入到mockPersister的之后,mockPersister就会一直持有这个newBean的实例,所以每次调用mockPersister对象的getNewsBean()方法时,返回的都是同一个newBean实例。解决该问题的办法是如下的配置:

<bean id="newsBean" class="...NewsBean" singleton="false" />

<bean id="mockPersister" class="...MockPersister">

<lookup-method name="getNewsBean" bean="newsBean" />

</bean>

这是Spring提供的一种叫做方法注入(Method Injection)的方式。该方法能够被子类实现或者覆写,容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象。这样,每次调用mockPersister对象的getNewsBean()方法时,返回newsBean对象都是不同的。

当然还有其他方法达到“每次调用都让容器返回新的对象实例”的目的,比如使用BeanFactoryAware接口或者使用ObjectFactoryCreatingFactoryBean等方式,详细使用方式可以查询相关资料。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值