- 任何一个成功的应用都是由为了实现某一目标任务而相互协作的组件构成的。这些组件必须彼此了解并相互协作来完成工作。
- 而创建应用对象之间关联关系的传统方法(通过构造器或者查找)通常会导致结构复杂的代码,这些代码很难复用,也很难进行单元测试。最好的情况是,这些对象所做的超出了它应该做的;最坏的情况是,这些对象之间彼此之间高度耦合,难以复用和测试。
- 在Spring中,对象无需自己负责查找或创建与其相关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。创建应用对象之间协作关系的行为通常被称为装配,这也是依赖注入的本质。
声明Bean
场景是一个选秀大赛,首先需要一些参赛者,即定义Performer接口:
public interface Performer{
void Perform() throws PerformanceException;//这个异常需要自己定义
}
声明一个简单Bean
一个杂技师(Juggler)
public class Juggler implements Performer {
private int beanBags = 3;//抛出的豆袋子数量
public Juggler() {
}
public Juggler(int beanBags){
this.beanBags = beanBags;
}
@Override
public void Perform() throws PerformanceException {
System.out.println("Juggling " + beanBags + " beanbags");
}
}
创建Spring配置
接着,来配置Spring,告诉容器它需要加载哪些Bean和如何装配这些Bean没这样才能确保他们能够彼此协作。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="duke" class="com.chen.Juggler" />
</beans>
接着主函数:
package com.chen;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) throws PerformanceException {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/chen/applicationContext.xml");
Performer performer = (Performer)ctx.getBean("duke");
performer.perform();
}
}
运行结果:
2016-6-18 9:25:27 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@481adc30: startup date [Sat Jun 18 09:25:27 GMT+08:00 2016]; root of context hierarchy
2016-6-18 9:25:27 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [com/chen/applicationContext.xml]
Juggling 3 beanbags
这里可以看出,duke只能抛出3个豆袋子,但是这还不足以得到冠军,所以他必须抛出更多的豆袋子。
通过构造器注入
之前的duke Bean是完全合法的,但是它使用了Juggler类的默认构造方法,该构造方法限制了Duke只能同时抛3个豆袋子。为了使duke打破纪录,我们需要用另一个构造方法。对原来的修改如下
...
<!-- <bean id="duke" class="com.chen.Juggler" /> -->
<bean id="duke" class="com.chen.Juggler" >
<constructor-arg value = "15" />
</bean>
...
这样,就可以抛出15个豆袋子了。
或许这还远远不够,duke表演的时候,他不仅可以表演杂技,还可以朗诵诗歌!
我们重新定义一个新的Juggler类型:
package com.chen;
public class PoeticJuggler extends Juggler{
private Poem poem;
public PoeticJuggler(Poem poem) {
super();
this.poem = poem;
}
public PoeticJuggler(int beanBags,Poem poem){
super(beanBags);
this.poem = poem;
}
@Override
public void perform() throws PerformanceException {
super.perform();
System.out.println("While reciting...");
poem.recite();
}
}
其中的Poem接口:
package com.chen;
public interface Poem {
void recite();
}
一首LovePoem诗:
package com.chen;
public class LovePoem implements Poem {
private static String[] LINES = {
"I love U",
"My baby",
"Bye~"
};
public LovePoem() {
}
@Override
public void recite() {
for (String string : LINES) {
System.out.println(string);
}
}
}
接下来进行配置:
<bean id = "lovePoem" class = "com.chen.LovePoem" />
<bean id="PoeticDuke" class="com.chen.PoeticJuggler" >
<constructor-arg value = "15" />
<constructor-arg ref = "lovePoem"/>
</bean>
这里,我们不能使用value属性为第二个构造参数赋值,因为Poem不是简单类型。我们使用ref属性来将ID为lovePoem的Bean的引用传递给构造器。
测试:
public class Test {
public static void main(String[] args) throws PerformanceException {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/chen/applicationContext.xml");
Performer performer = (Performer)ctx.getBean("PoeticDuke");
performer.perform();
}
}
运行结果:
...
Juggling 15 beanbags
While reciting...
I love U
My baby
Bye~
通过构造器注入来创建Bean是挺不错的,但是如果你想声明的Bean没有一个公开的构造方法,那咋办?那就是以下的如何装配通过工厂方法创建的Bean。
通过工厂方法创建Bean
有时候静态工厂方法是实例化对象的唯一方法,Spring支持通过< bean >元素的factory-method属性来装配工厂创建的Bean。
考虑在Spring中将一个单例类配置为Bean。一般来说,单例类的实例只能通过静态工厂方法来创建。
如下的选秀使用的Stage类是一个典型的单例类。因为我们希望只有一个舞台供参赛者使用。
package com.chen;
public class Stage {
private Stage(){//私有的构造方法!保证只能通过工厂方法创建实例
}
private static class StageSingletonHolder{ //内部类
static Stage instance = new Stage(); //延迟加载实例
}//内部类的静态变量只会被加载一次,所以保证了线程安全。
//同时,在不使用getInstance()方法的时候,是不会创建Stage实例的,起到了延迟加载的作用。
public static Stage getInstance(){
return StageSingletonHolder.instance; //返回实例
}
}
Stage没有公开的构造方法。而getInstance()方法每次被调用时,都返回相同的实例。
< bean >元素的factory-method属性允许我们调用一个指定的静态方法,从而代替构造方法来创建一个类的实例。
<bean id="theStage"
class="com.chen.Stage"
factory-method="getInstance"
></bean>
如果装配的对象需要通过静态方法来创建,那么这种配置方式可以适用于任何场景。
Bean的作用域
所有的Spring Bean默认都是单例。有时候我们需要每次请求的时候获得不同的实例。
在Spring中配置< bean >元素时,可以为Bean声明一个作用域。为了每次请求都产生一个新的实例,我们可以配置Bean的scope属性为prototype即可。
例如,把演出门票声明为Spring Bean:
<bean id = "ticket"
class = "com.chen.Ticket"
scope="prototype"
></bean>
除了prototype,还有其他的作用域选项。
- singleton:在每一个Spring容器中,一个Bean定义只有一个对象实例(默认都是这个)
- prototype: 允许Bean的定义可以被实例化任意次,每次调用的都创建一个实例
- request: 在一次HTTP请求中,每个Bean定义对应一个实例。该作用域仅在基于Web的Spring上下文(例如Spring MVC)中才有效。
- session: 在一个HTTP Session 中,每个Bean定义对应一个实例。该作用域仅在基于Web的Spring上下文(例如Spring MVC)中才有效。
- global-session : 在一个全局HTTP Session 中,每个Bean定义对应一个实例。该作用域仅在Portlet上下文中才有效。
Spring有关单例的概念仅限于Spring的上下文的范围内。不像真正的单例在每个类加载器中保证只有一个实例。Spring的单例Bean只能保证在每个应用上下文中只有一个Bean实例。没有人可以阻止你使用传统的方式实例化同一个Bean,或者你甚至可以定义几个< bean > 声明来实例化同一个Bean。
初始化和销毁Bean
当实例化一个Bean时,可能需要一些初始化操作来确保该Bean处于可用状态。同样的,当不在需要Bean,将其从容器中移除时,我们可能还需要按顺序执行一些清除工作。为了满足初始化和销毁Bean的需求,Spring提供了Bean生命周期的钩子方法。
为Bean定义初始化和销毁工作,只需要使用init-method和destroy-method参数来配置< bean > 元素。分别指定了在初始化Bean时要调用的方法和从容器中移除之前要调用的方法。
创建一个表演大厅Auditorium类,选秀比赛将在这里举行。这里我们只关心它要做的事情中的两件:
- 在表演前开灯
- 在表演结束后关灯
package com.chen;
public class Auditorium {
public void turnOnLights(){
System.out.println("表演大厅开灯...");
}
public void turnOffLights(){
System.out.println("表演大厅关灯...");
}
}
我们可以使用init-method和destroy-method属性来声明auditorium Bean:
<bean id = "auditorium"
class="com.chen.Auditorium"
init-method="turnOnLights"
destroy-method="turnOffLights"
></bean>
注意:这里只看到了初始方法的输出,没看到移除方法的输出,why?
默认的init-method 和 destory-method
如果在上下文中定义的很多Bean都拥有相同名字的初始化方法和销毁方法,这时候就没有必要为每一个Bean声明init-method和destory-method属性。我们可以使用< beans >元素的default-init-method和default-destroy-method属性:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
default-init-method="turnOnLights"
default-destroy-method="turnOffLights">
default-init-method为应用上下文中所有的Bean设置了共同的的初始化方法。
default-destroy-method为应用上下文中所有的Bean设置了一个共同的销毁方法。
在这里,我们可以要求Spring在初始化应用上下文中的Bean时调用turnOnLights(),在销毁Bean时调用turnOffLights()。如果这些方法不存在的话,就什么也不会发生。
注入Bean属性
通常JavaBean的属性是私有的,同时拥有一组存取器的方法,以setXXX和getXXX形式存在。Spring可以借助属性的set方法来配置属性的值,以实现setter方式的注入。
又有一个参赛者来到舞台,Kenny是一个很有天赋的乐器演奏家,由Instrumentalist类定义:
package com.chen;
public class Instrumentalist implements Performer {
private String song;
private Instrument instrument;
public Instrumentalist() {
}
@Override
public void perform() throws PerformanceException {
System.out.print("Playing " + song + ":");
instrument.play();
}
public String getSong() {
return song;
}
public void setSong(String song) { //注入歌曲
this.song = song;
}
public Instrument getInstrument() {
return instrument;
}
public void setInstrument(Instrument instrument) { //注入乐器
this.instrument = instrument;
}
}
Instrument接口:
package com.chen;
public interface Instrument {
public void play();
}
因为Instrumentalist类拥有一个默认的构造方法,所以Kenny可以在Spring中采用下面的XML声明为一个< bean >:
<bean
id = "kenny"
class = "com.chen.Instrumentalist"
></bean>
虽然Spring把kenny实例化一个Instrumentalist类型的对象没有任何问题,但是Kenny没有song和instrument的话,是无法演奏的。