疯狂java实例第14章 自己开发IoC容器.docx

上传人:b****5 文档编号:8381321 上传时间:2023-01-30 格式:DOCX 页数:65 大小:252.77KB
下载 相关 举报
疯狂java实例第14章 自己开发IoC容器.docx_第1页
第1页 / 共65页
疯狂java实例第14章 自己开发IoC容器.docx_第2页
第2页 / 共65页
疯狂java实例第14章 自己开发IoC容器.docx_第3页
第3页 / 共65页
疯狂java实例第14章 自己开发IoC容器.docx_第4页
第4页 / 共65页
疯狂java实例第14章 自己开发IoC容器.docx_第5页
第5页 / 共65页
点击查看更多>>
下载资源
资源描述

疯狂java实例第14章 自己开发IoC容器.docx

《疯狂java实例第14章 自己开发IoC容器.docx》由会员分享,可在线阅读,更多相关《疯狂java实例第14章 自己开发IoC容器.docx(65页珍藏版)》请在冰豆网上搜索。

疯狂java实例第14章 自己开发IoC容器.docx

疯狂java实例第14章自己开发IoC容器

第14章自己开发IoC容器

14.1IoC简介

在平时的开发中,当我们正在编写的某一个类需要用到另外的一个类(组件)的时候,我们都需要通过new的关键来创建该类的实例,那么有没有更好的方式,可以不在我们的代码中直接去new这个类,就可以得到该实例呢?

IoC的概念很好的帮助我们解决了这个问题,当我们需要在一个类中使用另外的类时,可以通过一些配置来得到该类的实现。

IoC是InversionofControl简称,又称控制反转,一个类需要另外一个类的实例,可以通过某个容器获得,而不在类的内部创建,得到什么样的实例,由容器去决定,而不是由该类决定。

搞清楚了控制反转的概念后,我们再来了解什么叫依赖注入。

依赖注入(DependencyInjection)与控制反转是同一个概念,我们创建某个类的实例由IoC容器完成,如果该类需要使用另外的类实例,那么可以在IoC容器中向该类注入需要被调用的实例。

无论是控制反转还是依赖注入,采用这种方式来创建类的实例,可以使得我们的代码更加清晰明了,将各个类之间的依赖关系反映到IoC容器中,可以更动态、灵活和透明的管理各个对象。

在J2EE领域中使用IoC的概念,可以将各个功能不同的组件统一放到IoC容器中,程序员只需要关注各个组件的实现,而不需要关注各个组件的依赖的关系。

常用的IoC容器有Spring的IoC容器、webwork的IoC容器、google-guice,apache的HiveMind等。

笔者面试过一些工作了几年的程序员,这些程序员很多都说自己精通Spring,精通IoC容器,但是如果一问IoC容器是如何帮我们创建类实例的,很多都不知道如何回答。

在下面章节中,我们将通过编写一个简单的IoC容器,来说明IoC容器是如何帮我们创建类的实例、如何实现依赖注入,最后并通过一个整合的例子,来说明IoC容器给我们带来什么样的好处。

14.2使用技术简介

在本章开发IoC容器时,所涉及的技术有如下几种:

❑Java的反射机制

❑dom4j

❑Junit

14.2.1Java反射简介

在Java运行时环境中,如果我们需要得到某一个类的具体信息,那么就可以使用Java的反射机制,该机制可以让我们动态的得到某个类型的属性、构造器和方法。

Java的反射机制可以在运行时构造某一个类的实例,在运行时调用任意一个对象的方法。

在本章中,我们需要在运行时去动态的加载配置文件,再根据这些配置文件去创建某一个类的实现,并为这些实例设置相应的属性,因此我们需要使用Java的反射机制。

14.2.2dom4j

dom4j是一个常用的XML解析项目,该项目是一个易用的开源项目,它应用于Java平台,支持使用Dom、SAX来解析XML文件。

Dom4j的使用非常简单,只需要使用该项目所提供的解析类,就可以轻松的读取到相应的XML文件,并可以得到这些xml的相关内容。

本章中我们采用了XML文件作为我们的IoC容器的配置文件,因此使用dom4j就非常的合适。

14.2.3Junit

Junit是一个单元测试框架,供Java程序员编写单元测试。

在本章中,我们每编写一个功能点,就需要对该功能进行单元测试,一来可以展示我们所编写程序的效果,二来可以保证我们的程序的质量。

如果站在XP(极限编程)的角度来讲,编写测试可以让我们的代码更加健壮,更不惧任何的变更,对我们项目的发展有长远的利益。

本章的重点是IoC容器,使用这个测试框架,目的是展示我们代码的效果,我们当前所使用的是Junit4。

14.3确定配置文件内容、编写DTD

在编写IoC容器之前,我们需要编写XML文件,这些XML文件用来定义人们IoC容器的一些配置,例如声明我们需要创建哪些对象(bean),为这些对象(bean)提供名字和类名,让我们的IoC容器根据这些信息去创建相应的bean。

除了定义bean的名字和类名外,还需要定义一些其他的属性,例如该bean是否为单态,是否需要延迟加载定。

确定了配置文件的内容后,我们开始着手编写DTD文件。

14.3.1声明bean

在整个IoC容器中,每一个bean代表具体的某个类,因此我们的根节点为beans,beans下有多个bean,可以让我们配置具体的某个类。

XML文件的具体配置如下:

定义好了bean节点好,我们需要为bean节点指定一个名字和类名,表示这个bean所对应的名称与类型,这些我们的IoC容器得到这些配置后,就可以帮我们创建这些类的实例。

具体的配置如下:

以上的bean配置表示我们在IoC容器中创建了一个myDate的bean,该bean的类型是java.util.Date。

14.3.2声明单态的bean

定义一个bean除了需要显式声明该bean的名称和类型外,还需要告诉我们的IoC容器,该bean是否为单态,如果该bean是单态的话,那么IoC容器在启动的时候,就只会为该bean创建一个实例,并将该实例缓存起来,如果该bean配置成非单态的话,那么就不进行缓存,每次请求这个bean的时候,就重新创建一个。

我们为bean的配置添加一个singleton属性,用来声明bean是否为单态。

以上配置的黑体部分,我们配置了myDate这个bean为非单态,在一般的情况下,我们可以不指定singleton属性,让singleton属性在不显式声明的情况下的值为true,给配置节点提供默认值,在14.4准备DTD文件中有详细说明。

14.3.3声明延迟加载

当IoC容器启动的时候,容器是否需要马上创建这个bean,我们可以为配置文件提供一个属性,让容器知道我们在容器初始化的时候,是否需要创建。

我们为bean提供一个lazy-init的属性:

以上的配置代码中,声明了myDate需要延迟加载,为bean节点加了这个属性后,beans节点也可以提供一个default-lazy-init的属性,表示这份配置文件下的所有bean,如果lazy-init属性的值为default的时候,就使用beans节点(根节点)的default-lazy-init所配置的值:

以上的配置,如果myDate的bean声明为default的话,那么就按照beans的default-lazy-init属性来决定是否需要延迟加载。

如果对bean的lazy-init属性不指定值的话,那么就为这个lazy-init属性指定为default,表示这个bean是否延迟加载取决于beans的配置,而beans的default-lazy-init属性不显式指定的话,可以使用false作为默认值。

14.3.4声明设值注入到bean的属性

IoC容器除了可以帮我们创建bean之外,还可以帮助我们将对应的属性设置到该bean的实例里面,我们需要为每个bean注入一些属性,让该bean的实例得到这些属性,这些属性有可能是一些普通的值,也有可能是另外的定义的bean。

在这里我们使用设值注入,设值注入,就是创建了bean的实例后,再获得该bean配置的一些属性,通过该bean里面的setter方法,设值到该bean的实例中。

-定义一个student的bean-->

-为Student类中的school属性注入school的bean-->

以上的配置中的黑体部分,我们为student的bean注入了一个school的bean,这样,school和student这两个bean就产生了依赖关系,这样的注入我们就叫依赖注入,以上的注入方式是依赖注入中的设值注入。

那么我们的配置文件中需要为bean加入一个property的子节点,该节点里面有一个name属性,property节点下面有一个ref子节点,ref子节点指向某一个容器中的bean。

除了注入另外定义的bean外,还可以向bean中注入一些普通的属性:

-为Student类中的school属性注入school的bean-->

18

以上的配置表示向student这个bean中注入了一个Integer的值,对应Student的age属性。

因此,property节点下面可以出现ref节点和value节点,但是,每一个property只能出现一次ref节点或者value节点,因为我们一个property只会设置一个属性。

14.3.5声明构造注入到bean的属性

14.3.4小节中,我们定义配置文件中为bean提供property节点进行设值注入,本小节我们定义构造注入的节点。

构造注入,也就是通过bean中定义的构造参数,在创建这个bean的实例时,就将这些参数通过调用该bean的构造器,将这些配置的属性传递给该bean。

以上配置的黑体部分,声明了创建student这个bean的时候,我们需要为Student类提供一个构造器,构造器的参数为School类型,那么IoC容器在创建时,就会根据constructor-arg声明的属性,去调用Student的构造器。

与14.3.4中的设值注入一样,除了可以提供ref元素外,还可以提供value元素,构造注入一些另外的普通属性。

20

这样的配置,我们就可以为Student进行构造注入,并在创建bean的实例时,提供一个Integer类型的构造参数。

14.3.6自动装配

自动装配,就是不需要指定bean的属性,从IoC容器中查找bean所需要的属性,将这些查找到的bean以设值注入的方式依赖注入到目标的bean中。

以上的student提供了autowire属性,该属性值为byName,表示根据bean的名称自动注入到student中,如果Student类中有一个setSchool的setter方法,并且参数为Schoold对象,那么容器就需要自动的将School的bean通过设值注入设置到Student中。

与延迟加载一样,也可以为beans节点提供一个default-autowire属性,声明该beans根节点下面所有的bean节点,如果autowire的值为default,那么就可以进行自动装配。

在本例中autowire的值我们定义为只允许为no或者byName。

如果autowire的值为no的话,表示不需要自动装配。

bean的autowire属性默认值为default。

确定了IoC的配置文件需要配置的内容,那么接下来,我们就可以根据上述的配置内容,编写相关的约束文件(DTD)。

14.3.7准备DTD文件

文档定义类型(DTD)可以定义合法的XML文档构建模块,对我们所编写的一些XML文件进行约束,也就是说,当一份XML指定了某份DTD文件时,那么该XML文件就必须遵守该DTD文件的约束。

简单的说,DTD就是一种对XML文件定制的规范。

当我们需要为一份XML定义某份DTD规范的时候,在XML文件头中声明:

xmlversion="1.0"encoding="UTF-8"?

>

DOCTYPEbeansPUBLIC"-//CRAZYIT//DTDBEAN//EN"

"http:

//www.crazyit.org/beans.dtd">

以上的XML文件头,声明了该份XML由http:

//www.crazyit.org/beans.dtd这一份dtd来约束,每个元素都需要遵守该份DTD中所声明的文档结构。

根据14.3.1到14.3.6里面定义的配置文件的内容,我们可以开始编写DTD文件。

以下为我们的IoC容器配置文件的DTD文件:

ELEMENTbeans(

bean*

)>

ATTLISTbeansdefault-lazy-init(true|false)"false">

ATTLISTbeansdefault-autowire(no|byName)"no">

--指定bean元素的子元素-->

ELEMENTbean(

(constructor-arg|property)*

)>

--指定bean元素的属性值-->

ATTLISTbeanidCDATA#REQUIRED>

ATTLISTbeanclassCDATA#REQUIRED>

ATTLISTbeanlazy-init(true|false|default)"default">

ATTLISTbeansingleton(true|false)"true">

ATTLISTbeanautowire(no|byName|default)"default">

--声明constructor-arg子元素-->

ELEMENTconstructor-arg(

(ref|value|null)

)>

--声明property元素的子元素-->

ELEMENTproperty(

(ref|value|null)?

)>

--声明property的属性-->

ATTLISTpropertynameCDATA#REQUIRED>

--声明property的属性-->

ATTLISTvaluetypeCDATA#REQUIRED>

--声明ref元素-->

ELEMENTrefEMPTY>

--声明ref的属性-->

ATTLISTrefbeanCDATA#REQUIRED>

--声明value元素-->

ELEMENTvalue(#PCDATA)>

通过以上的代码,那么该份DTD的各个节点可以概括为如下几个:

❑beans节点:

根节点为beans,beans的属性有default-lazy-init和autowire,这两个属性的默认值(不需要显式提供)为false和no。

❑bean节点:

beans下面有多个bean节点,bean节点必须要显式提供id和class属性,可以不必显式提供lazy-init,singleton和autowire属性,lazy-init的默认值为default,表示该值由beans的default-lazy-init来决定,singleton的默认值为true,而且只允许有true和false两个值,autowire属性的默认值是default,由beans的default-autowire来决定。

❑construct-arg节点:

ref、value和null都可以作为该节点的子节点,该节点没有属性。

❑property节点:

ref、value和null都可以作为该节点的子节点,property节点有一个name属性,而且是必须指定的。

❑ref节点:

该节点没有子节点,只有一个必须指定的bean属性。

❑value节点:

value节点只有一个type属性,用于指定该值的类型。

编写完DTD文件后,那么我们可以在XML的文件头中声明DTD。

xmlversion="1.0"encoding="UTF-8"?

>

DOCTYPEbeansPUBLIC"-//CRAZYIT//DTDBEAN//EN"

“我们编写的DTD文件的绝对路径">

以上的XML文件头,表示我们使用某份DTD文件作为XML的约束,指定DTD文件路径时,我们暂时使用绝对路径,指向我们上面编写的那份DTD文件,在下面的章节中,将会讲解如何将绝对路径变成url。

14.4读取XML文件

在14.2中,我们已经确定了使用dom4j来读取XML文件,我们需要明白,当在创建一个IoC容器的时候,我们就开始读取XML文件,当取完之后,可以将XML中我们所需要的信息缓存起来,也就是进行一次读取即可,如果进行多次读取,那么将会影响性能。

在使用dom4j之前,我们需要下载dom4j的包,下载完包之后,可以将包放到项目的环境变量中。

dom4j的包可以从sourceforge中下到,下载的地址为:

14.4.1加载XML文件

在本小节开头,我们已经确定了只读取一次XML,那么我们就新建一个类,用于读取XML,并将读取到的Document对象缓存。

建立DocumentHolder接口。

代码清单:

code\IoC\main\org\crazyit\ioc\xml\DocumentHolder.java

publicinterfaceDocumentHolder{

//根据文件的路径返回文档对象

DocumentgetDocument(StringfilePath);

}

DocumentHolder接口只有一个方法,用于获得一个Document对象,参数是XML文件的路径。

为该接口新建一个实现类XmlDocumentHolder。

代码清单:

code\IoC\main\org\crazyit\ioc\xml\XmlDocumentHolder.java

publicclassXmlDocumentHolderimplementsDocumentHolder{

//新建一个Map对象,用于保存读取到的多份XML文件

privateMapdocs=newHashMap();

publicDocumentgetDocument(StringfilePath){

Documentdoc=this.docs.get(filePath);

if(doc==null){

this.docs.put(filePath,readDocument(filePath));

}

returnthis.docs.get(filePath);

}

//根据文件路径读取Document

privateDocumentreadDocument(StringfilePath){

try{

//使用SAXReader来读取xml文件

SAXReaderreader=newSAXReader(true);

//使用自己的EntityResolver

reader.setEntityResolver(newIoCEntityResolver());

FilexmlFile=newFile(filePath);

//读取文件并返回Document对象

Documentdoc=reader.read(xmlFile);

returndoc;

}catch(Exceptione){

e.printStackTrace();

thrownewDocumentException(e.getMessage());

}

}

}

以上代码中,我们使用了一个Map对象来保存多个Document对象,当外界使用getDocument方法的时候,直接从Map对象中获取该Document对象,如果取到就返回,如果取不到该对象,就通过私有方法readDocument来读取。

注意以上代码的黑体部分,我们使用了自定义的EntityResolver对象,IoCEntityResolver的代码如下。

代码清单:

code\IoC\main\org\crazyit\ioc\xml\IoCEntityResolver.java

publicclassIoCEntityResolverimplementsEntityResolver{

publicInputSourceresolveEntity(StringpublicId,StringsystemId)

throwsSAXException,IOException{

//先从本地寻找dtd

if("http:

//www.crazyit.org/beans.dtd".equals(systemId)){

InputStreamstream=IoCEntityResolver.class.

getResourceAsStream("/org/crazyit/ioc/beans/beans.dtd");

returnnewInputSo

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 工学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1