转自:http://blog.csdn.net/newjueqi/archive/2009/06/03/4238602.aspx
【文章标题】用反射和内省技术实现简单 SpringIOC
【文章作者】曾健生
【作者邮箱】 zengjiansheng1@126.com
【作者 QQ 】 190678908
【作者博客】 http://blog.csdn.net/newjueqi
【作者声明】欢迎转载文章,但转载请保留文章的完整性以及注明文章的出处。
*******************************************************************************
我们知道, spring 是个开源的控制反转( Inversion of Control , IoC )和面向切片( AOP )的容器框架。所谓控制反转就是应用本身不负责依赖对象的创建和维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到外部容器,控制权的转移就是所谓的反转。
我们把需要创建的对象信息写在一个 XML 文件中,每次需要维护对象时只需要修改 XML 文件中的配置信息,不需要重新更改和编译代码,为项目开发带来了极大的方便。
反射
在以前的文章《 JDK5.0 新特性( 2 )——反射》 ( http://blog.csdn.net/newjueqi , http://newjueqi.javaeye.com/) 中就已经介绍过反射技术,有了反射技术,就能方便地根据 XML 的配置创建对象。那对象的维护应该怎么实现呢?这就需要内省技术出马了 ^-^
内省
内省是 Java 中 Bean 类属性的一种缺省的处理方式。例如,有一个类 Person ,其中有个 age 属性,可通过 setAge ()和 getAge ()方法获取 / 设置 age 的值。通过 setAge() /getAge ()获取 / 设置 age 的值是 Java 中的默认的处理规则, Java 提供了一套 API 来访问某个属性的 setAge () /getAge ()。
通用的用法通过类 Introspector 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器( PropertyDescriptor ),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后通过反射机制来调用这些方法
实现简单 SpringIOC
通过反射技术,就能动态地创建对象;通过内省技术,就能方便地维护对象。通过以上的两个技术基础,就能实现简单的 SpringIOC 。
譬如, XML 文件中有如下的配置信息
< bean id = "personDAO" class = "org.petrelsky5.dao.impl.PersonDAOImpl" />
< bean id = "personService" class = "org.petrelsky5.service.impl.PersonServiceImpl" >
< property name = "personDAO" ref = "personDAO" />
< property name = "name" value = "Tom12345" ></ property >
< property name = "age" value = "12" ></ property >
</ bean >
其中 SpringTest.java 文件中 instanceSpring() 有如下测试代码:
ClassPathXmlApplicationContext ctx = null ;
ctx = new ClassPathXmlApplicationContext( "beans.xml" );
PersonDAO personService = (PersonDAO) ctx
.getBean( "personDAO" );
personService.add();
其中的调用关系如下图 1 :
图 1
在 SpringTest.java 文件中 instanceSpring2() 有如下测试代码:
ClassPathXmlApplicationContext ctx = null ;
ctx = new ClassPathXmlApplicationContext( "beans.xml" );
PersonService personService = (PersonService) ctx
.getBean( "personService" );
personService.save();
其中的调用关系如下图 2 :
图 2
由此可知道:
(1) 类 ClassPathXmlApplicationContext 相当于一个容器。
(2) 在 XML 文件的节点 bean 中,各部分的内容解释如下:
A. id :类的标识。
B. class :类的所在路径。
C. property :属性信息
① name :属性名称。
② value: 如果属性是值类型的,那么 value 是相应的属性值。
③ ref :如果属性是引用类型(譬如 PersonServiceImpl 类中的属性 personDAO ,指向类 PersonDAO ),那么 ref 是所指向的对象。
我们根据在 XML 文件信息和 类 ClassPathXmlApplicationContext 的功能,讨论一下相关的数据怎么存储(即数据结构)。
( 1 )在 类 ClassPathXmlApplicationContext 中有个方法 getBean ,可根据 id 值获取一个实际的对象,即 id 和对象是一一对应的关系,所以定义一个 Map 集合:
Map<String,Object>
其中, 键为 id, 值 Object 为对应的对象。
( 2 ) 虽然现在编写的 XMl 文件中只有两个 Bean 节点,但我们都必须用面向对象的思想把 Bean 节点对象化,每个 bean 有以下三个属性:
1. id
2. class
3. bean 节点(数量不确定)
由于 bean 节点的数量是不确定,对于数量不确定的数据的存储,自然地想到了用集合。
而每个 bean 又有三个属性:
1. name :属性名称
2. value :属性的数据值(可能有)
3. ref :属性所指向的引用值(可能有)
所有的数据结构关系如图 3 所示:
图 3
现在数据的相互关系已经清楚了,接下来就是程序的流程。
我们在类 ClassPathXmlApplicationContext 初始化时只传入了 XML 文件的名称,以后只是在有需要时用 getBean ()获取 id 制定的对象,所以应该在类初始化时就把所有的对象创建完毕。
根据如上的描述,可以把初始化分为以下三步:
1. 把 XML 的信息保存到相关的类中
2. 初始化每个 Bean , 把 id 值和 class 对应的对象保存在 Map 集合中
3. 设置 XMl 文件中每个 Bean 指定的值
这三步的代码如下:
1. 把 XML 的信息保存到相关的类中
// 把 XML 的信息保存到相关的类中
public void initXML( String fileName )
{
URL url= null ;
Document doc= null ;
// 获取 XML 文件的 url
url= this .getClass().getClassLoader().getResource( fileName );
SAXReader saxreader= new SAXReader();
try {
// 获取 XML 的文档对象
doc=saxreader.read( url);
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ;
}
// 获取根节点
List<Element> beans=doc.getRootElement().elements( "bean" ) ;
// 遍历所有的 Bean 节点,把 class , id , property 保存起来
for ( Element ele: beans )
{
// 创建一个 BeanInfo 对象
BeanInfomation beanInfo= null ;
beanInfo= new BeanInfomation( ele.attributeValue( "class" ),
ele.attributeValue( "id" ) );
// 获取一个 Bean 中的所有 property
List<Element> properties=ele.elements( "property" ) ;
// 遍历所有的 property 信息
for ( Element pro: properties )
{
Property property= null ;
// 把 property 信息保存
property= new Property( pro.attributeValue( "name" ),
pro.attributeValue( "value" ),
pro.attributeValue( "ref" ) );
beanInfo.addProperty( property );
}
beanList .add( beanInfo );
}
}
2. 初始化每个 Bean ,把 id 值和 class 对应的对象保存在 Map 集合中
public void initBean ()
{
for ( BeanInfomation beaninfo: beanList )
{
String id=beaninfo.getId();
String className=beaninfo.getClassName();
Object obj= null ;
try {
// 用反射获取一个类的对象
obj=Class.forName ( className ).newInstance();
} catch ( Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 把 id 值和 class 对应的对象保存在 Map 集合中
objectMap .put( id, obj );
}
}
3. 设置 XMl 文件中每个 Bean 指定的值
public void initBeanProperty()
{
List<BeanInfomation> beanList=getBeanList();
List<Property> proList= null ;
// 遍历每个 Bean 类,读取其中的信息
for ( BeanInfomation beaninfo: beanList)
{
String id=beaninfo.getId();
// 根据 ID 值从 Map 集合中获取对象
Object obj= objectMap .get(id);
BeanInfo beanInfos= null ;
// 获取 bean 中的属性值集合
proList=beaninfo.getPerprotyList();
for ( Property pro: proList )
{
String name=pro.getName();
String ref=pro.getRef();
Object value= null ;
// 判断 ref 是否为空
if ( ref!= null )
{
value= objectMap .get( ref );
}
else
{
value=pro.getValue();
}
Class clazz=obj.getClass();
try {
beanInfos = Introspector.getBeanInfo (clazz);
} catch (IntrospectionException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
PropertyDescriptor[] propertyDescriptes=
beanInfos.getPropertyDescriptors();
for ( PropertyDescriptor propertyDescript:propertyDescriptes )
{
if ( name!= null && name.equals( propertyDescript.getName()))
{
try {
// 设置指定属性的值
propertyDescript.getWriteMethod().invoke( obj,value );
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break ;
}
}
}
}
}
最后就是完成 getBean 函数
// 获取对象
public Object getBean( String beanName )
{
Object obj= null ;
obj= objectMap .get(beanName);
return obj;
}
测试:
1. 运行测试代码:
public void instanceSpring() {
ClassPathXmlApplicationContext ctx = null ;
ctx = new ClassPathXmlApplicationContext( "beans.xml" );
PersonDAO personService = (PersonDAO) ctx
.getBean( "personDAO" );
personService.add();
}
输出结果如图 4 :
图 4
2. 运行测试代码:
public void instanceSpring2() {
ClassPathXmlApplicationContext ctx = null ;
ctx = new ClassPathXmlApplicationContext( "beans.xml" );
PersonService personService = (PersonService) ctx
.getBean( "personService" );
personService.save();
}
运行结果如图 5 :
图 5
3. 运行测试代码:
public void instanceSpring3(){
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );
PersonServiceImpl ps = (PersonServiceImpl) ctx.getBean( "personService" );
ps.save();
String name = ps.getName();
System. out .println( "name: " + name);
System. out .println( "age: " + ps.getAge());
}
运行结果如图 6 :
图 6
^-^ 结果证明代码编写正确!!!
源码下载地址:https://docs.google.com/leaf?id=0B3cFfe7PkmkxYzUwMGNkYzItMTUwNS00ZTAyLTg1YTctZmIxZGViZTgwZWJm&sort=name&layout=list&num=50