1:
Spring IoC容器的实例化非常简单,如下面的例子:
Resource resource = new FileSystemResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
... 或...
ClassPathResource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
... 或...
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
// of course, an ApplicationContext
is just a BeanFactory
BeanFactory factory = (BeanFactory) context;
2:
2.1. 用构造器来实例化
当采用构造器来创建bean实例时,Spring对class并没有特殊的要求,我们通常使用的class都适用。也就是说,被创建的类并不需要实现任何特定的接口,或以特定的方式编码,只要指定bean的class属性即可。不过根据所采用的IoC类型,class可能需要一个默认的空构造器。
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
2.2. 使用 静态
工厂方法实例化
当采用静态工厂方法创建bean时,除了需要指定class
属性外,还需要通过factory-method
属性来指定创建bean实例的工厂方法。Spring将调用此方法(其可选参数接下来介绍)返回实例对象,就此而言,跟通过普通构造器创建类实例没什么两样。
下面的bean定义展示了如何通过工厂方法来创建bean实例。注意,此定义并未指定返回对象的类型,仅指定该类包含的工厂方法。在此例中, createInstance()
必须是一个static方法。
<bean id="exampleBean"
class="examples.ExampleBean2"
factory-method="createInstance"/>
2.3. 使用实例工厂方法实例化
与使用静态工厂方法实例化类似,用来进行实例化的实例工厂方法位于另外一个已有的bean中,容器将调用该bean的工厂方法来创建一个新的bean实例
为使用此机制,class
属性必须为空,而factory-bean
属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,而该工厂bean的工厂方法本身必须通过factory-method
属性来设定(参看以下的例子)。
<!-- the factory bean, which contains a method called createInstance()
-->
<bean id="myFactoryBean" class="...">
...
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="exampleBean"
factory-bean="myFactoryBean"
factory-method="createInstance"/>
3.3.1. 注入依赖
依赖注入(DI)背后的基本原理是对象之间的依赖关系(即一起工作的其它对象)只会通过以下几种方式来实现:构造器的参数、工厂方法的参数,或给由构造函数或者工厂方法创建的对象设置属性。
DI主要有两种注入方式,即Setter注入和 构造器注入。3.3.1.1. Setter注入
通过调用无参构造器或无参static
工厂方法实例化bean之后,调用该bean的setter方法,即可实现基于setter的DI。
public class SimpleMovieLister {
// the SimpleMovieLister
has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can 'inject' a MovieFinder
public void setMoveFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually 'uses' the injected MovieFinder
is omitted...
}
3.3.1.2. 构造器注入
基于构造器的DI通过调用带参数的构造器来实现,每个参数代表着一个协作者。此外,还可通过给静态
工厂方法传参数来构造bean。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can 'inject' a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually 'uses' the injected MovieFinder is omitted...
}最后,我们还要提到的一点就是,当协作bean被注入到依赖bean时,协作bean必须在依赖bean之前完全配置好。例如bean A对bean B存在依赖关系,那么Spring IoC容器在调用bean A的setter方法之前,bean B必须被完全配置,这里所谓完全配置的意思就是bean将被实例化(如果不是采用提前实例化的singleton模式),相关的依赖也将被设置好,而且所有相关的lifecycle方法(如IntializingBean的init方法以及callback方法)也将被调用。
3.3.2. 构造器参数的解析
构造器参数将根据类型来进行匹配。如果bean定义中的构造器参数类型明确,那么bean定义中的参数顺序就是对应构造器参数的顺序。考虑以下的类...
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
这里的参数类型非常明确(当然前提是假定类Bar
与 Baz
在继承层次上并无任何关系)。因此下面的配置将会很好地工作,且无须显式地指定构造器参数索引及其类型。
<beans>
<bean name="foo" class="x.y.Foo">
<constructor-arg>
<bean class="x.y.Bar"/>
</constructor-arg>
<constructor-arg>
<bean class="x.y.Baz"/>
</constructor-arg>
</bean>
</beans>
当引用的bean类型已知,则匹配没有问题(如上述的例子)。但是当使用象<value>true<value>
这样的简单类型时,Spring将无法决定该值的类型,因而仅仅根据类型是无法进行匹配的。考虑以下将在下面两节使用的类:
package examples;
public class ExampleBean {
// No. of years to the calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
3.3.2.1. 构造器参数类型匹配
针对上面的这种情况,我们可以在构造器参数定义中使用type
属性来显式的指定参数所对应的简单类型。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
3.3.2.2. 构造器参数的索引
通过使用index
属性可以显式的指定构造器参数出现顺序。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
使用index属性除了可以解决多个简单类型构造参数造成的模棱两可的问题之外,还可以用来解决两个构造参数类型相同造成的麻烦。注意:index属性值从0开始。
提示
指定构造器参数索引是使用构造器IoC首选的方式。
3.3.3.1.1. idref
元素
idref
元素用来将容器内其它bean的id传给<constructor-arg/>
或 <property/>
元素,同时提供错误验证功能。
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean" />
</property>
</bean>
上述bean定义片段完全地等同于(在运行时)以下的片段:
<bean id="theTargetBean" class="..."/>
<bean id="client" class="...">
<property name="targetName">
<value>theTargetBean</value>
</property>
</bean>
第一种形式比第二种更可取的主要原因是,使用idref
标记允许容器在部署时 验证所被引用的bean是否存在。而第二种方式中,传给client
bean的targetName
属性值并没有被验证。任何的输入错误仅在client
bean实际实例化时才会被发现(可能伴随着致命的错误)。如果client
bean 是prototype类型的bean,则此输入错误(及由此导致的异常)可能在容器部署很久以后才会被发现。
此外,如果被引用的bean在同一XML文件内,且bean名字就是bean id,那么可以使用local
属性,此属性允许XML解析器在解析XML文件时来对引用的bean进行验证。
<property name="targetName">
<!-- a bean with an id of 'theTargetBean' must exist, else an XML exception will be thrown -->
<idref local="theTargetBean"/>
</property>
3.3.3.2. 引用其它的bean(协作者)
在<constructor-arg/>
或<property/>
元素内部还可以使用ref
元素。该元素用来将bean中指定属性的值设置为对容器中的另外一个bean的引用。如前所述,该引用bean将被作为依赖注入,而且在注入之前会被初始化(如果是singleton bean则已被容器初始化)。
第一种形式也是最常见的形式是通过使用<ref/>
标记指定bean
属性的目标bean,通过该标签可以引用同一容器或父容器内的任何bean(无论是否在同一XML文件中)。
<ref bean="someBean"/>
第二种形式是使用ref的local
属性指定目标bean,它可以利用XML解析器来验证所引用的bean是否存在同一文件中。local
属性值必须是目标bean的id属性值。如果在同一配置文件中没有找到引用的bean,XML解析器将抛出一个例外。如果目标bean是在同一文件内,使用local方式就是最好的选择(为了尽早地发现错误)。
<ref local="someBean"/>
第三种方式是通过使用ref的parent
属性来引用当前容器的父容器中的bean。parent
属性值既可以是目标bean的id
值,也可以是name
属性值。而且目标bean必须在当前容器的父容器中。使用parent属性的主要用途是为了用某个与父容器中的bean同名的代理来包装父容器中的一个bean
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <-- notice that the name of this bean is the same as the name of the 'parent'
bean
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <-- notice how we refer to the parent bean
</property>
<!-- insert other configuration and dependencies as required as here -->
</bean>
('parent'
属性的使用并不常见。)
3.3.3.5. Nulls
<null/>
用于处理null
值。Spring会把属性的空参数当作空字符串
处理。以下的xml片断将email属性设为空字符串
。
<bean class="ExampleBean">
<property name="email"><value></value></property>
</bean>
这等同于Java代码: exampleBean.setEmail("")
。而null
值则可以使用<null>
元素可用来表示。例如:
<bean class="ExampleBean">
<property name="email"><null/></property>
</bean>
上述的配置等同于Java代码:exampleBean.setEmail(null)
。
3.3.4. 使用depends-on
多数情况下,一个bean对另一个bean的依赖最简单的做法就是将一个bean设置为另外一个bean的属性。在xml配置文件中最常见的就是使用<ref/>
元素。有时候它还有另外一种变体,如果一个bean能感知IoC容器,只要给出它所依赖的id,那么就可以通过编程的方式从容器中取得它所依赖的对象。无论采用哪一种方法,被依赖bean将在依赖bean之前被适当的初始化。
在少数情况下,有时候bean之间的依赖关系并不是那么的直接(例如,当类中的静态块的初始化被时,如数据库驱动的注册)。depends-on
属性可以用于当前bean初始化之前显式地强制一个或多个bean被初始化。下面的例子中使用了depends-on
属性来指定一个bean的依赖。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
若需要表达对多个bean的依赖,可以在'depends-on'
中将指定的多个bean名字用分隔符进行分隔,分隔符可以是逗号、空格及分号等。下面的例子中使用了'depends-on'
来表达对多个bean的依赖。
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
3.3.5. 延迟初始化bean
ApplicationContext
实现的默认行为就是在启动时将所有singleton
bean提前进行实例化。提前实例化意味着作为初始化过程的一部分,ApplicationContext
实例会创建并配置所有的singleton bean。通常情况下这是件好事,因为这样在配置中的任何错误就会即刻被发现(否则的话可能要花几个小时甚至几天)。
有时候这种默认处理可能并不是你想要的。如果你不想让一个singleton bean在ApplicationContext
实现在初始化时被提前实例化,那么可以将bean设置为延迟实例化。一个延迟初始化bean将告诉IoC 容器是在启动时还是在第一次被用到时实例化。
在XML配置文件中,延迟初始化将通过<bean/>
元素中的lazy-init
属性来进行控制。例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true">
<!-- various properties here... -->
</bean>
<bean name="not.lazy" class="com.foo.AnotherBean">
<!-- various properties here... -->
</bean>
当ApplicationContext
实现加载上述配置时,设置为lazy
的bean将不会在ApplicationContext
启动时提前被实例化,而not.lazy
却会被提前实例化。
3.3.6. 自动装配(autowire)协作者
模式
|
说明
|
no
|
不使用自动装配。必须通过ref元素指定依赖,这是默认设置。由于显式指定协作者可以使配置更灵活、更清晰,因此对于较大的部署配置,推荐采用该设置。而且在某种程度上,它也是系统架构的一种文档形式。
|
byName
|
根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。例如,在bean定义中将autowire设置为by name,而该bean包含master属性(同时提供setMaster(..)方法),Spring就会查找名为master的bean定义,并用它来装配给master属性。
|
byType
|
如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,那么将会抛出异常,并指出不能使用byType方式进行自动装配。若没有找到相匹配的bean,则什么事都不发生,属性也不会被设置。如果你不希望这样,那么可以通过设置dependency-check="objects"让Spring抛出异常。
|
constructor
|
与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
|
autodetect
|
通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式。
|
3.3.7. 依赖检查
当需要确保bean的所有属性值(或者属性类型)被正确设置的时候,那么这个功能会非常有用。当然,在很多情况下,bean类的某些属性会具有默认值,或者有些属性并不会在所有场景下使用,因此这项功能会存在一定的局限性。就像自动装配一样,依赖检查也可以针对每一个bean进行设置。依赖检查默认为not,
模式
|
说明
|
none
|
没有依赖检查,如果bean的属性没有值的话可以不用设置。
|
simple
|
对于原始类型及集合(除协作者外的一切东西)执行依赖检查
|
object
|
仅对协作者执行依赖检查
|
all
|
对协作者,原始类型及集合执行依赖检查
|
3.3.8. 方法注入
在大部分情况下,容器中的bean都是singleton类型的。如果一个singleton bean要引用另外一个singleton bean,或者一个非singleton bean要引用另外一个非singleton bean时,通常情况下将一个bean定义为另一个bean的property值就可以了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个singleton类型bean A的某个方法时,需要引用另一个非singleton(prototype)类型的bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A提供一个新的的bean B实例。
3.3.8.1. Lookup方法注入
这究竟是不是方法注入……
Lookup方法注入利用了容器的覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。在上述场景中,Lookup方法注入适用于原型bean(尽管它也适用于singleton bean,但在那种情况下直接注入一个实例就够了)。Lookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码功能,通过动态创建Lookup方法bean的子类而达到复写Lookup方法的目的。
package fiona.apple;
// no more Spring imports!
public class CommandManager {
public Object process(Object command) {
// grab a new instance of the appropriate Command
interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command
instance
command.setState(commandState);
return command.execute();
}
// mmm, but where is the implementation of this method?
protected abstract CommandHelper createHelper();
}
在包含被注入方法的客户类中(此处是CommandManager
),此方法的定义必须按以下形式进行:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象
的,动态生成的子类会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。让我们来看个例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor
uses statefulCommandHelper
-->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
在上面的例子中,标识为commandManager的bean在需要一个新的command bean实例时,会调用createCommand
方法。重要的一点是,必须将command
部署为原型。当然也可以指定为singleton,如果是这样的话,那么每次将返回相同的command
bean实例!
Lookup方法注入既可以结合构造器注入,也可以与setter注入相结合。
请注意,为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里。 另外,Spring容器要子类化的类不能是final
的,要覆盖的方法也不能是final
的。同样的,要测试一个包含抽象
方法的类也稍微有些不同,你需要自己编写它的子类提供该抽象
方法的桩实现。 最后,作为方法注入目标的bean不能是序列化的(serialized)。