您的位置:首页 > 编程语言 > Java开发

Spring之IOC思想的理解和简单实现

2014-09-14 15:53 537 查看
Spring之IOC思想的理解和简单实现

所谓IOC,其全称为“Inversion Of Control”,较常见的中文翻译是“控制反转”,或许有些朋友会觉得看英文名称更容易理解,其实都无所谓,重要的是我们要理解其思想。通俗一点来讲就是:将bean对象的控制权(包括对象的创建和维护)由应用代码转交到外部容器去。这里,也许会有朋友不理解什么是bean对象,其实,对于mvc三层结构的web应用程序中,模型层中所包含的对象都可以理解为是bean对象。形象点来说,一个个模型对象可以想象为一个个bean(豆子),而管理这些bean对象的容器(譬如Spring容器)则可以理解为现实生活中的一个器皿。像dao对象、vo对象无疑都是可以称为bean对象的。好了,现在言归正传,我们继续来讨论IOC思想,“将bean对象的控制权由应用代码转交到外部容器”这句话听起来或许不是那么的通俗易懂。简单一点来说,对于一个面向对象的应用程序,它必定是由若干个类组成,类其实是个静态的概念,而当程序运行起来的时候,我们就应该说程序是由若干个对象组成的,或者更准确地说,程序的运作是由若干个对象之间的相互作用而实现的。那么,对象之间是如何相互作用的呢?实际上,对象间的相互作用无非就是通过对象间的相互调用来进行的。而一个对象要想调用(或者称之为访问)另一个对象,那么调用者对象就必须能够获得被调用者对象的一个引用,这里的获得其实可以通过对象间的关联关系或者依赖关系来完成(简单的说,关联关系就是一个对象作为另一个对象的属性存在,而依赖关系就是一个对象作为另一个对象的方法参数或者返回值存在)。我想大家都知道的是,一个引用对象必须要指向一个实例化对象,才是有意义的,就好像一个地址必须指向一个实实在在的地点才是有意义的。那么,如何才能让一个引用指向一个实例化对象呢?实际上,从某个角度来分的话,将一个引用指向一个实例化对象有两种实现方式,一种是在应用代码中进行,也就是说将对象的实例化工作放在应用代码中进行;另一种则是将对象的实例化工作交给底层框架(例如Spring框架)来做,然后当程序运行时再将实例化了的对象动态地绑定到其引用对象上去。对于第二种实现方式,实际上就是我们的IOC思想的体现,也就是将bean对象的控制权由应用代码转交到外部容器中去。

接着,我们来分析一下IOC思想的具体实现。实际上,IOC思想遵循着java程序设计中的依赖倒转原则,也就是说我们在编程时应尽量用抽象和接口来编程,而不要用具体的类来编程(或者说程序的实现要依赖于抽象的接口而不依赖于具体的实现类)。那么,IOC思想具体是怎么实现的呢?这里,需要引入的一个概念就是,工厂方法模式。我想,有过编程经验的朋友,对于这个模式应该是不会陌生的,例如JDBC中获取connection对象的方式实际上就用了这个模式,大家可以回忆一下,是不是直接通过一个getConnection方法(在工厂方法模式中我们称之为工厂方法)就可以获得一个connection对象呢?是的,我们要做的就只是往该方法中传入三个String类型的参数(包括String
url, String user, String password)。那么,传统的工厂方法模式是如何实现的呢?实际上该模式的实现需要用到java中的反射机制,在该模式中,一个具体的工厂对应着一种具体的产品(或者说一个具体的工厂生产一种具体的产品),所有的具体工厂都必须实现一个抽象的工厂接口,所有的具体产品都必须实现同一个抽象的产品接口。这样,我们在代码中就可以直接使用抽象的工厂和产品来编程了(也就是面向抽象编程),而不需要用到具体的工厂和产品,然后,我们可以把具体的工厂类的路径信息写到xml配置文件中去。在应用代码中,我们可以借用dom解析技术来获取配置文件中配置好的具体的工厂类的路径信息,然后通过反射机制,来载入具体的工厂类并实例化对象(这里,我们称之为注入),当我们需要改变客户端的具体产品对象时,我们不需要更改客户端的任何一行代码,而只需要改变配置文件中的具体工厂类的路径信息就可以了。对于工厂方法模式,这里我不再做详细解释,感兴趣的朋友可以自己去查看相关文档,下面是工厂方法模式的类图:



上面提到了java中反射机制的概念,那么,究竟什么是反射呢?所谓反射机制,简单点来说,就是如果我们想要在程序中获得一个类的对象,不需要直接去实例化,只需要让类的加载器帮我们去加载我们需要的类的模板对象(所谓类的模板对象,也就是Class类型的对象),然后通过调用类模板对象自身的实例化方法就可以得到我们想要的实例化对象了。这个过程中,我们只需要做的一件事就是向类的加载器传入我们想要加载的类的路径信息。

通过上面的描述,我想大家对工厂方法模式和反射都或多或少有些了解了。接着,我们就继续来分析IOC思想的实现,还记得上面我们谈到的“将bean对象的控制权由应用代码转交给外部容器”这个IOC思想的解释吧?其实,这个外部的容器(这里称之为IOC容器)就可以理解为是一个大工厂,而IOC思想则可理解为是工厂方法模式的升华。因为工厂方法模式中,具体产品的实例化操作是写死在具体工厂的工厂方法中的,而IOC容器中则是利用反射机制将具体工厂和具体产品解耦(也就是具体工厂的实现不再直接依赖具体产品,因为具体的产品被定义在配置文件中了。)。另外,工厂方法模式中,一个具体的产品必须对应一个具体的工厂,而IOC实现则是只用一个大的工厂来负责生产所有的产品,这大大降低了类的数量。此外,由于IOC容器要负责那些bean对象的管理工作,所以,IOC容器中必须要有一个集合来保存这些已经实例化了的且生命期还没结束的bean对象。这里我们用的是Map<String,
Object>数据结构,这样我们就可以通过bean对象的名称来从IOC容器获取相应的bean对象了。我想,大家看了上面的文字描述可能仍会感觉很迷糊,呵呵,或许是本人的文笔水品有限吧。下面我们来看一下,对于IOC思想的一个简单实现的例子:

1. 编写IOC容器要用到的配置文件(用来配置bean对象)——beans.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans">

<bean id="userDao" class="mySpringTest.UserDao">

</bean>

</beans>

2. 编写bean配置类,用来获取配置文件中所配置的bean对象的信息(即将配置文件中的配置信息封装为一个对象,这实际上用了面向对象的思想)——BeanIndicator.java

package mySpringTest;

/**

* bean对象的配置信息类

* @author Administrator

*

*/

public class BeanIndicator

{

//定义bean类的ID和名称

private String id;

private String className;

public BeanIndicator(String id, String className)

{

this.id=id;

this.className=className;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public String getClassName() {

return className;

}

public void setClassName(String className) {

this.className = className;

}

}

3. 编写自定义IOC容器,负责管理配置文件中所配置的bean对象——SpringContainer.java

package mySpringTest;

import org.dom4j.Document;

import org.dom4j.Element;

import org.dom4j.XPath;

import org.dom4j.io.SAXReader;

/**

* 自定义的IOC容器

* 剖析spring管理bean的原理

* 使用dom4j解析XML时,要快速获取某个节点的数据,使用XPath是个不错的方法,

* 要使用这个方法则需要导入两个包:dom4j-1.6.1.jar-306 KB、jaxen-1.1-beta-6.jar-238 KB

* @author Administrator

*

*/

public class SpringContainer

{

//定义bean队列、bean的map集合(不能有重复的对象)

private java.util.List<BeanIndicator> beanIndicators=new java.util.ArrayList<BeanIndicator>();

private java.util.Map<String,Object> singletons=new java.util.HashMap<String,Object>();

public SpringContainer(String fileName)

{

this.readXML(fileName);

this.instanceBean();

}

/**

* 解析配置好的xml文件,读取bean指示对象,并添加到队列中

* @param fileName

*/

private void readXML(String fileName)

{

//创建一个文件xml文件读取器

SAXReader saxReader=new SAXReader();

Document document=null;

try

{

//取得类的类装载器,通过类装载器,取得类路径下的文件

java.net.URL xmlPath=this.getClass().getClassLoader().getResource(fileName);

//读取文件内容

System.out.println("xmlPath="+xmlPath);

document=saxReader.read(xmlPath);

//创建一个map对象

java.util.Map<String, String> nameSpaceMap=new java.util.HashMap<String,String>();

//加入命名空间

nameSpaceMap.put("nameSpace", "http://www.springframework.org/schema/beans");

//创建beans/bean查询路径

XPath xpath=document.createXPath("nameSpace:beans/nameSpace:bean");

//设置命名空间

xpath.setNamespaceURIs(nameSpaceMap);

//获取文档下所有bean节点

java.util.List<Element> beans=xpath.selectNodes(document);

System.out.println("bean对象的个数为:"+beans.size());

for(int i=0; i<beans.size(); i++)

{

Element element=beans.get(i);

//获取bean对象的id属性

String id=element.attributeValue("id");

System.out.println("bean对象的id为:"+id);

//获取bean对象的className属性

String className=element.attributeValue("class");

//创建bean指示对象

BeanIndicator bi=new BeanIndicator(id,className);

beanIndicators.add(bi);

}

}

catch(Exception e)

{

e.printStackTrace();

}

}

/**

* 实例化bean对象

*

*/

private void instanceBean()

{

for(int i=0; i<beanIndicators.size(); i++)

{

BeanIndicator bi=beanIndicators.get(i);

try

{

if(bi.getClassName()!=null&&!"".equals(bi.getClassName().trim()))

{

//通过反射机制实例化xml配置文件中配置的bean对象

Object obj=Class.forName(bi.getClassName()).newInstance();

//将实例化的bean对象放到spring容器所保存的bean集合中进行管理

singletons.put(bi.getId(), obj);

}

}

catch(Exception e)

{

e.printStackTrace();

}

}

}

public Object getBean(String beanName)

{

return singletons.get(beanName);//通过配置文件中配置的bean对象的id来取bean对象

}

}

4. 编写一个dao类,用以做测试——UserDao.java

package mySpringTest;

public class UserDao

{

//模拟保存方法

public void save()

{

System.out.println("user对象保存成功!");

}

}

5.编写测试类——IocTest.java

package mySpringTest;

public class IocTest

{

public static void main(String[] args)

{

SpringContainer sc=new SpringContainer("mySpringTest/beans.xml");

UserDao userDao=(UserDao)sc.getBean("userDao");

userDao.save();

}

}

6.测试结果如下:

xmlPath=file:/F:/myeclipse6_workspace/netjava_web_project/WebRoot/WEB-INF/classes/mySpringTest/beans.xml

bean对象的个数为:1

bean对象的id为:userDao

user对象保存成功!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: