Spring入门教程Word文件下载.docx
《Spring入门教程Word文件下载.docx》由会员分享,可在线阅读,更多相关《Spring入门教程Word文件下载.docx(22页珍藏版)》请在冰豆网上搜索。
并且Spring所需的处理开销也是微不足道的。
此外,Spring是非侵入式的:
典型地,Spring应用中的对象不依赖于Spring的特定类。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。
当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务()管理)进行内聚性的开发。
应用对象只实现它们应该做的——完成业务逻辑——仅此而已。
它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。
然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
框架——Spring可以将简单的组件配置、组合成为复杂的应用。
在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。
Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。
它们也为Spring中的各种模块提供了基础支持。
(2)Spring的历史
Spring的基础架构起源于2000年早期,它是RodJohnson在一些成功的商业项目中构建的基础设施。
在2002后期,RodJohnson发布了《ExpertOne-on-OneJ2EEDesignandDevelopment》一书,并随书提供了一个初步的开发框架实现——interface21开发包,interface21就是书中阐述的思想的具体实现。
后来,RodJohnson在interface21开发包的基础之上,进行了进一步的改造和扩充,使其发展为一个更加开放、清晰、全面、高效的开发框架——Spring。
2003年2月Spring框架正式成为一个开源项目,并发布于SourceForge中。
(3)Spring的使命
J2EE应该更加容易使用。
面向对象的设计比任何实现技术(比如J2EE)都重要。
面向接口编程,而不是针对类编程。
Spring将使用接口的复杂度降低到零。
(面向接口编程有哪些复杂度?
)
代码应该易于测试。
Spring框架会帮助你,使代码的测试更加简单。
JavaBean提供了应用程序配置的最好方法。
在Java中,已检查异常(Checkedexception)被过度使用。
框架不应该迫使你捕获不能恢复的异常。
2.控制反转IoC
IoC全名InversionofControl,如果中文硬要翻译过来的话,就是「控制反转」。
初看IoC,从字面上不容易了解其意义,我觉得要了解IoC,最好先从DependencyInversion开始了解,也就是依赖关系的反转。
DependencyInversion在面向对象的设计原则之依赖倒置原则(DIP,DependenceInversionPrinciple)中有着清楚的解釋。
简单的说,在模块设计时,高层的抽象模块通常是与业务相关的模块,它应该具有重用性,而不依赖于低层的实现模块,例如如果低层模块原先是软盘存取模式,而高层模块是个存盘备份的需求,如果高层模块直接叫用低层模块的函式,则就对其产生了依赖关系。
请看下面的例子:
voidCopy(){
intc;
while((c=ReadKeyboard())!
=EOF)
WritePrinter(c);
}
这是僵化和不易改动的例子,为什么呢?
很显然,如果我还要将内容输出到磁盘上(如下图所示),那么我们必须改动Copy的内容,并进行重新的测试和编译。
改动后的程序如下所示:
enumOutputDevice{printer,disk};
voidCopy(OutputDevicedev){
while((c=ReadKeyboard())!
if(dev==printer)
else
WriteDisk(c);
如果要继续添加别的输入或输出方式,该程序还是无法重用,要对此程序进行修改才能继续使用。
利用依赖倒置原则(DIP),可以解决这个问题。
DIP原则,可以从2点来解读:
第1点:
高层模块不依赖底层模块,两者都依赖抽象。
即:
高层和底层模块都应该依赖抽象。
第2点:
抽象不应该依赖于细节,细节应该依赖于抽象
上面所讲的例子如果用DIP原则,结果如下
classReader{
public:
virtualintread()=0;
};
classWriter{
virtualvoidwrite(int)=0;
voidCopy(Reader&
r,Writer&
w){
while((c=r.read())!
=EOF)
w.write(c);
这样一来,如果要添加新的输入或输出设备时只要改动相应的类(classReader,Writer,利用多态来解决上面的问题)就可以了,而其它的程序都不用改动。
这就是依赖倒置原则的基本内涵。
在软件设计和构建中我们要遵循“高内聚、低偶合”的原则。
那么,依赖对于我们来说究竟是好事还是坏事呢?
首先应该明白的是,类之间如果是零偶合的状态是不能够构建应用程序的,只能构建类库。
但是由于人类的理解力和可控制的范围有限,大多数人难以理解和把握过于复杂的系统。
把软件系统划分成多个模块,可以有效控制模块的复杂度,使每个模块都易于理解和维护。
但在这种情况下,模块之间就必须以某种方式交换信息,也就是必然要发生某种耦合关系。
如果某个模块和其它模块没有任何关联(哪怕只是潜在的或隐含的依赖关系),我们就几乎可以断定,该模块不属于此软件系统,应该从系统中剔除。
如果所有模块之间都没有任何耦合关系,其结果必然是:
整个软件不过是多个互不相干的系统的简单堆积,对每个系统而言,所有功能还是要在一个模块中实现,这等于没有做任何模块的分解。
因此,模块之间必定会有这样或那样的依赖关系,永远不要幻想消除所有依赖。
但是,过强的耦合关系(如一个模块的变化会造成一个或多个其他模块也同时发生变化的依赖关系)会对软件系统的质量造成很大的危害。
特别是当需求发生变化时,代码的维护成本将非常高。
所以,我们必须想尽办法来控制和消解不必要的耦合,特别是那种会导致其它模块发生不可控变化的依赖关系。
依赖倒置、控制反转就是人们在和依赖关系进行艰苦卓绝的斗争过程中不断产生和发展起来的。
我们下面来继续一步一步的说明这个问题。
看下面的程序:
#include<
floppy.h>
....
voidsave(){
....
saveToFloppy()
}
由于save()程序依赖于saveToFloppy(),如果今天要更换低层的存储模块为Usb碟,则这个程序没有办法重用,必须加以修改才行,低层模块的更动造成了高层模块也必须跟着更动,这不是一个好的设计方式,我们希望模块都依赖于模块的抽象,这样才可以重用高层的业务设计。
如果以面向对象的方式来设计,依赖倒置(DependencyInversion)的解释变为程序不应依赖实现,而是依赖于抽象,实现必须依赖于抽象。
我们来看看下面这个Java程序:
BusinessObject.java
publicclassBusinessObject{
privateFloppyWriterwriter=newFloppyWriter();
publicvoidsave(){
...
writer.saveToFloppy();
publicclassFloppyWriter{
....//相应的写盘的代码
在这个程序中,BusinessObject的存盘依赖于实际的FloppyWriter,如果今天我们想要将存盘改为存至Usb碟,我们必须修改或继承BusinessObject进行扩展,而无法直接使用BusinessObject。
浩劫只是刚刚开始,这时,你一定和我一样在期待着救世主的早日降临——接口(Interface)。
你一定会说,面向对象的设计原则已经告诉我们了啊,“要针对接口编程”,你为什么不用呢?
好,我们采用这个原则进行编程:
什么是接口?
接口定义了行为的协议,这些行为在继承接口的类中实现。
接口定义了很多方法,但是没有实现它们。
类履行接口协议并实现所有定义在接口中的方法。
接口是一种只有声明没有实现的特殊类。
使用接口的优点:
Client不必知道其使用对象的具体所属类。
一个对象可以很容易地被(实现了相同接口的)的另一个对象所替换。
对象间的连接不必硬绑定(hardwire)到一个具体类的对象上,因此增加了灵活性。
松散藕合(loosenscoupling)。
增加了重用的可能性。
通过面向接口编程,可以改進此一情況,例如:
publicinterfaceIDeviceWriter{
publicvoidsaveToDevice();
privateIDeviceWriterwriter;
publicvoidsetDeviceWriter(IDeviceWriterwriter){
this.writer=writer;
writer.saveToDevice();
这样一来,BusinessObject就是可重用的,如果今天我有存储至Floppy或Usb碟的需求,我只要实现IDeviceWriter即可,而不用修改BusinessObject:
publicclassFloppyWriterimplementIDeviceWriter{
publicvoidsaveToDevice(){
//实际储存至Floppy的程序代码
publicclassUsbDiskWriterimplementIDeviceWriter{
//实际储存至UsbDisk的程序代码
从这个角度来看,DependencyInversion的意思即是程序不依赖于实现,而是程序与实现都要依赖于抽象。
哦,这样一来,一切都OK了吗?
还没有没有问题呢,你可能会有疑问,我要根据不同的情况来使用软盘、USB碟或者其它的存储设备,怎么办呢?
在程序包中,BusinessObject不是还是和FloppyWriter或者UsbDiskWriter绑定吗,如果系统发布后,要将FloppyWriter替换为UsbDiskWriter不是还要去修改IDeviceWriter的实现吗?
修改就意味着可能会带来错误,就要带来修改代码、进行测试、编译、维护等的工作量。
还有更好的方法吗?
IoC/DI就是解决之道。
IoC的Control是控制的意思,其实其背后的意义也是一种依赖关系的转移,如果A依赖于B,其意义即是B拥有控制权,我们要转移这种关系,所以依赖关系的反转即是控制关系的反转,藉由控制关系的转移,我们可以获得组件的可重用性,在上面的Java程序中,整个控制权从实际的FloppyWriter转移至抽象的IDeviceWriter接口上,使得BusinessObject、FloppyWriter、UsbDiskWriter这几个实现依赖于抽象的IDeviceWriter接口。
使用IoC,对象是被动的接受依赖类,而不是自己主动的去找。
容器在实例化的时候主动将它的依赖类注入给它。
可以这样理解:
控制反转将类的主动权转移到接口上,依赖注入通过xml配置文件在类实例化时将其依赖类注入。
“控制反转(InversionofControl)与依赖倒置原则(DependencyInversionPrinciple)是一个同义原则。
虽然“依赖倒置”和“控制反转”在设计层面上都是消解模块耦合的有效方法,也都是试图令具体的、易变的模块依赖于抽象的、稳定的模块的基本原则,但二者在使用语境和关注点上存在差异:
“依赖倒置”强调的是对于传统的、源于面向过程设计思想的层次概念的“倒置”,而“控制反转”强调的是对程序流程控制权的反转。
“依赖倒置”的使用范围更为宽泛一些。
IoC在容器的角度,可以用这么一句好莱坞名言来代表(著名的好莱坞原则):
"
Don'
tcallme,I'
llcallyou."
(不要打电话给我们,我们会通知你的)。
好莱坞的演员门(包括大牌)都会在好莱坞进行登记,他们不能够直接打电话给制片人或者导演要求演出摸个角色或者参加某个片子的演出,而是由好莱坞根据需要去通知他们(前提是他们已经在好莱坞做过登记)。
在这里好莱坞就相当于容器。
以程序的术语来说的话,就是「不要向容器要求您所需要的(对象)资源,容器会自动将这些对象给您!
」。
IoC要求的是容器不侵入应用程序本身,应用程序本身提供好接口,容器可以透过这些接口将所需的资源注至程序中,应用程序不向容器主动要求资源,故而不会依赖于容器的组件,应用程序本身不会意识到正被容器使用,可以随时从容器中脱离转移而不用作任何的修改,而这个特性正是一些业务逻辑中间件最需要的。
3.依賴注入DI
IoC模式基本上是一个高层的概念,在MartinFowler的InversionofControlContainersandtheDependencyInjectionpattern中谈到,实现IoC有两种方式:
DependencyInjection与ServiceLocator。
Spring所采用的是DependencyInjection来实现IoC(多数容器都是采取这种方式的),中文翻译为依赖注入,依赖注入的意义是:
保留抽象接口,让组件依赖于抽象接口,当组件要与其它实际的对象发生依赖关系时,藉过抽象接口来注入依赖的实际对象。
回锅头来再仔细研读一下我们在上面给出的例子:
//實際儲存至Floppy的程式碼
//實際儲存至UsbDisk的程式碼
为了让BusinessObject获得重用性,我们不让BusinessObject依赖于实际的FloppyWriter,而是依赖于抽象的接口。
在此代码中,首先IDeviceWriter的变量writer可以接收任何IDeviceWriter的实例,另外,FloppyWriter或UsbDiskWrite的实例不是通过BusinessObject来获得,而是通过setter(也可以用构造器)来由外部传给它。
这似乎跟我们往常的代码没什么不同(回想一下Javabean的setter/getter),但这已经是一个良好的设计。
现在的关键问题是:
FloppyWriter或UsbDiskWrite的实例如何从外部注入给BusinessObject呢?
这就要通过xml来实现了(相当于演员们在好莱坞登记)。
Spring的配置文件applicationContext.xml代码片断如下:
<
beans>
<
beanid="
FloppyWriter"
class="
iocfirst.business.write"
/>
BusinessObject"
iocfirst.business.BusinessObject"
/>
propertyname="
FloppyWriter"
>
refbean="
FloppyWriter"
/property>
/bean>
/beans>
如果什么时候想将UsbDiskWrite注入,则修改applicationContext.xm即可。
单有了这个xml文件还不够,加载该xml文件呢?
spring提供了现成的API,在加载上面的xml的时候,就进行了如下工作:
实例化FloppyWriter或UsbDiskWrite类,实例化BusinessObject类,并将FloppyWriter或UsbDiskWrite的实例作为参数赋给了BusinessObject实例的setDeviceWriter方法。
BusinessObject依赖于抽象接口,在需要建立依赖关系时,我们就是通过抽象接口注入依赖的实际对象。
依赖注入在MartinFowler的文章中谈到了三种实现方式:
interfaceinjection、setterinjection与constructorinjection。
并分别称其为type1IoC、type2IoC与type3IoC。
Type1-接口注入(InterfaceInjection)
它是在一个接口中定义需要注入的信息,并通过接口完成注入。
ApacheAvalon是一个较为典型的Type1型IOC容器,WebWork框架的IoC容器也是Type1型。
Type2-设值方法注入(SetterInjection)
在各种类型的依赖注入模式中,设值注入模式在实际开发中得到了最广泛的应用(其中很大一部分得力于Spring框架的影响)。
基于设置模式的依赖注入机制更加直观、也更加自然。
上面的BusinessObject所实现的是type2IoC,透过setter注入依赖关系。
Type3-构造子注入(ConstructorInjection)
构造子注入,即通过构造函数完成依赖关系的设定。
目前我们只要了解到有这个3种方式就可以了,具体情况将在以后的章节中进行介绍。
4.开始spring之旅
任何需要交给spring管理的对象,都必须在配置文件中注册,这个过程被称为wiring,下面做一个最简单的演示,
第一步:
下载Spring,Spring官方网站是http:
//www.springframework.org/我们这里下载的是2.0.1版,spring-framework-2