论坛首页 入门技术论坛

Spring IOC

浏览 2054 次
锁定老帖子 主题:Spring IOC
该帖已经被评为新手帖
作者 正文
   发表时间:2009-09-30  
学习Spring IOC之前我们先看看下面的例子:

首先我们有这样一个需求.
使用U盘来存储资料,这个需求很简单
我们可以使用下面代码实现:

public class SaveUtil {
	private Usb usb = new Usb();
	
	public void save() {
		usb.save();
	}
}


public class Usb {
	public void save() {
		System.out.println("using use to save!!");
	}
}


但是很快我们发现一个问题,SaveUtil这个类严重依赖usb这个对象
如果我们现在需要使用移动硬盘来存储资料,程序无法重用,
我们引入公用接口Isave并且对上面类做修改

public interface ISave {
	public void save();
}


然后我们的存储类都继承这个接口
public class Usb implements ISave {
	public void save() {
		System.out.println("using use to save!!");
	}
}


public class MobileDisk implements ISave {
	public void save() {
		System.out.println("using mobiledisk to save!!");
	}
}


public class SaveUtil {
	private Isave iSave;
	
	public SaveUtil(Isave iSave) {
		this.iSave = iSave;
	}
	
	public void save() {
		iSave.save();
	}
}


这样修改之后我们只需要在实例化SaveUtil的时候传入相应的存储类就可以实现需求上的变化
很好的降低了程序之间的依赖关系,这种就是简单的控制反转.
所谓控制反转就是应用本身不负责依赖对象的创建和维护,而是交由外部容器负责.这样控制权就转移到了外部容器
控制权的转移就是所谓的控制反转.

Spring是一个开源的IOC(控制反转)和AOP(面向切面)的容器框架,使用Spring可以简化我们的开发.

开发中需要的jar包有spring.jar 和 common-logging.jar
第一个Spring应用程序,仍然使用上面的存储资料的例子

导入spring.jar和common-logging.jar到工程中
在类路径下建立一个bean.xml文件,这个文件就是spring的配置文件,通过这个配置文件我们可以使用spring对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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
</beans>


首先我们配置好我们的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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean id="usb" class="com.royzhou.start.Usb"></bean>
    <bean id="MobileDisk" class="com.royzhou.start.MobileDisk"></bean>
</beans>


Spring提供了访问bean对象的一系列类,继承自ApplicationContext这个接口
程序中我们使用ClassPathXmlApplicationContext来访问我们的bean对象

public class Test {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		Usb usb = (Usb)ctx.getBean("usb");
		usb.save();
		MobileDisk md = (MobileDisk)ctx.getBean("MobileDisk");
		md.save();
	}
}


运行程序后发现输出结果为:
using usb to save!!!
using mobile disk to save!!!

这里有这样一个疑问:我们的类是什么时候实例化的呢.
原来在运行了下面这句代码之后
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
Spring容器就会根据bean.xml的配置信息,通过发射机制实例化我们的bean.

我们可以写一个程序来模拟Spring容器的操作:BeanDefinition.java是bean的定义类BeanContainer.java为容器类

package com.royzhou.start;

import java.util.ArrayList;
import java.util.List;

public class BeanDefinition {
	private String id;
	private String className;
	private List<PropertyDefinition> propertys = new ArrayList<PropertyDefinition>(); 

	public BeanDefinition(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;
	}

	public List<PropertyDefinition> getPropertys() {
		return propertys;
	}

	public void setPropertys(List<PropertyDefinition> propertys) {
		this.propertys = propertys;
	}

}


package com.royzhou.start;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

public class BeanContainer {
	private List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
	
	private Map<String, Object> singletons = new HashMap<String, Object>();

	public BeanContainer(String fileName) {
		readXML(fileName);
		instanceBeans();
	}
	
	/**
	 * 实例化bean对象并储存在Map中
	 *
	 */
	public void instanceBeans() {
		String id = null;
		String className = null;
		Object objectBean = null;
		for (BeanDefinition bd : beanDefinitions) {
			id = bd.getId();
			className = bd.getClassName();
			if (className != null && !"".equals(className.trim())) {
				try {
					objectBean = Class.forName(className).newInstance();
					singletons.put(id, objectBean);
				} catch(Exception e) {
					System.out.println("实例化bean:" + className + "出错!!!");
					e.printStackTrace();
				}
			}
		}
	}
	
	
	/**
	 * 读取配置文件
	 * @param fileName
	 */
	@SuppressWarnings("unchecked")
	public void readXML(String fileName) {
		URL url = null;
		SAXReader reader = null;
		Document doc;
		try {
			url = this.getClass().getClassLoader().getResource(fileName);
			reader = new SAXReader();
			doc = reader.read(url);

			Map<String, String> nsMap = new HashMap<String, String>();
			nsMap.put("ns", "http://www.springframework.org/schema/beans"); 
			XPath xPath = doc.createXPath("//ns:beans/ns:bean");	//创建查询路径//ns:beans/ns:bean
			xPath.setNamespaceURIs(nsMap);	//设置命名空间
			List<Element> elements = xPath.selectNodes(doc);	//查询doc的beans下的所有bean元素
			for (Element e : elements) {
				String id = e.attributeValue("id");
				String className = e.attributeValue("class");
				BeanDefinition bd = new BeanDefinition(id, className);
				beanDefinitions.add(bd);
			}
		} catch (DocumentException e1) {
			System.out.println("解析配置文件" + fileName + "出错!!!");
			e1.printStackTrace();
		}
	}
	
	/**
	 * 获取bean实例,单例
	 * @param id
	 * @return
	 */
	public Object getBean(String id) {
		return singletons.get(id);
	}
}


(默认情况下,如果配置了bean的属性如:lazy-init="true"等,则spring容器会在第一次调用getBean方法时实例化bean,
如果希望所有bean都延迟加载 则可以在beans标签上设置default-lazy-init="true")
Spring默认实例化出来的对象都是单例singleton的.

可以做如下测试:

public class Test {
	public static void main(String[] args) {
		BeanContainer ctx = new BeanContainer("bean.xml");
		Usb usb = (Usb)ctx.getBean("usb");
		Usb usb1 = (Usb)ctx.getBean("usb");
		System.out.println(usb == usb1);
	}
}


通过比较两次拿到的对象,比较他们的引用,发现返回true,说明两次获取到的是同一个对象

当然我们可以设置bean的scope属性使我们每次都得到新的对象.scope有两种值可选singleton和prototype
spring中默认为bean设置了singleton,所以每次拿到的都是同一个对象

修改我们的bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean id="usb" class="com.royzhou.start.Usb" scope="prototype"></bean>
    <bean id="MobileDisk" class="com.royzhou.start.MobileDisk"></bean>
</beans>


再次运行上面的Test类发现输出结果为false 说明我们的配置生效了. 具体使用哪种bean的生成方式根据实际情况而定,一般采取默认的singleton方式

spring实例化bean有三种方式
第一种使用类构造器:
<bean id="usb" class="com.royzhou.start.Usb"></bean>
第二种使用静态工厂方法:
<bean id="usb" class="com.royzhou.start.Usb" factory-method="createUsb"></bean>

public class Usb implements ISave {

	public void save() {
		System.out.println("using usb to save!!!");
	}
	
	public static Usb createUsb() {
		return new Usb();
	}
}


第三种使用实例工厂方法:
<bean id="usbFactory" class="com.royzhou.start.UsbFactory"></bean>
<bean id="usb" factory-bean="usbFactory" factory-method="createUsb"></bean>

public class UsbFactory {
	public Usb createUsb() {
		return new Usb();
	}
}



指定bean的初始化方法和销毁方法
可以通过指定bean标签的init-method和destroy-method来指定bean的初始化方法和销毁方法
<bean id="usb" class="com.royzhou.start.Usb" init-method="init" destroy-method="destroy"></bean>
bean的销毁我们可以通过AbstractApplicationContext提供的close()方法来关闭spring容器实现

package com.royzhou.start;

public class Usb implements ISave {
	
	public void init() {
		System.out.println("I am initialized!!!!");
	}
	
	public Usb() {
		System.out.println("I am constructor");
	}
	
	public void save() {
		System.out.println("using usb to save!!!");
	}
	
	public void destroy() {
		System.out.println("I am destoryed!!!!");
	}
}


package com.royzhou.test;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.royzhou.start.Usb;

public class Test {
	public static void main(String[] args) {
		AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		Usb usb = (Usb)ctx.getBean("usb");
		ctx.close();
	}
}


控制台输出结果:
I am constructor
I am initialized!!!!
I am destoryed!!!!
可以看出:使用类构造器最先执行实例化 然后执行init-method 最后执行destroy-method


注入依赖对象:
所谓依赖注入就是指在运行期间,由外部容器动态的将依赖对象注入到组件当中.

通过Spring把对象注入到我们的组件内部从而实现控制反转.
依然使用上面的存储资料的例子,修改SaveUtil类为save属性增加Setter方法:

public class Usb implements ISave {
	public void save() {
		System.out.println("using use to save!!");
	}
}


public class MobileDisk implements ISave {
	public void save() {
		System.out.println("using mobiledisk to save!!");
	}
}


public class SaveUtil {
	private Isave iSave;
	
	public SaveUtil(Isave iSave) {
		this.iSave = iSave;
	}
	
	public void setSave(ISave save) {
		this.save = save;
	}
	
	public void save() {
		iSave.save();
	}
}


然后修改我们的bean.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean id="usb" class="com.royzhou.start.Usb"></bean>
    <bean id="MobileDisk" class="com.royzhou.start.MobileDisk"></bean>
    <bean id="SaveUtil" class="com.royzhou.start.SaveUtil">
    	<property name="save" ref="MobileDisk"></property>
    </bean>
</beans>


其中SaveUtil在配置的时候我们为其属性save设置了一个ref值,指向MobileDisk
这里实际上就是我们通过spring容器将MobileDisk对象注入到SaveUtil这个类中
编写我们的测试类:

package com.royzhou.test;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.royzhou.start.SaveUtil;

public class Test {
	public static void main(String[] args) {
		AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		SaveUtil su = (SaveUtil)ctx.getBean("SaveUtil");
		su.save();
	}
}


输出结果为:
using mobile disk to save!!!
说明我们的MobileDisk对象成功注入到SaveUtil中
修改一下ref值为usb
重新运行测试类
输出结果为:
using usb to save!!!

可以看出,使用spring的依赖注入可以极大的降低程序之间的耦合,而一切只需要通过简单修改配置文件就可以实现

重新修改我们的BeanContainer.java类使其支持注入对象:
为此我们需要增加一个PropertyDefinition.java类,用来存放property属性:

package com.royzhou.start;

public class PropertyDefinition {
	private String name;

	private String ref;

	public PropertyDefinition(String name, String ref) {
		super();
		this.name = name;
		this.ref = ref;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getRef() {
		return ref;
	}

	public void setRef(String ref) {
		this.ref = ref;
	}
}


重新修改之后的BeanContainer.java类

package com.royzhou.start;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

public class BeanContainer {
	private List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
	
	private Map<String, Object> singletons = new HashMap<String, Object>();

	public BeanContainer(String fileName) {
		readXML(fileName);
		instanceBeans();
		injectBeans();
	}
	
	/**
	 * 实例化bean对象并储存在Map中
	 *
	 */
	public void instanceBeans() {
		String id = null;
		String className = null;
		Object objectBean = null;
		for (BeanDefinition bd : beanDefinitions) {
			id = bd.getId();
			className = bd.getClassName();
			if (className != null && !"".equals(className.trim())) {
				try {
					objectBean = Class.forName(className).newInstance();
					singletons.put(id, objectBean);
				} catch(Exception e) {
					System.out.println("实例化bean:" + className + "出错!!!");
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 注入依赖对象
	 *
	 */
	public void injectBeans() {
		Object objectBean = null;
		Object refBean = null;
		BeanInfo beanInfo = null;
		PropertyDescriptor[] propertyDescriptors = null;
		Method setter = null;
		for (BeanDefinition bd : beanDefinitions) {
			objectBean = singletons.get(bd.getId());
			if(objectBean!=null) {
				List<PropertyDefinition> propertys = bd.getPropertys();
				if(propertys!=null && propertys.size()>0) {
					try {
						beanInfo = Introspector.getBeanInfo(objectBean.getClass());	
						propertyDescriptors = beanInfo.getPropertyDescriptors();
						for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
							for(PropertyDefinition propertyDefinition : propertys) {
								if(propertyDefinition.getName().equals(propertyDescriptor.getName())) {
									setter = propertyDescriptor.getWriteMethod();	//获取setter方法
									if(setter!=null) {
										refBean = singletons.get(propertyDefinition.getRef());
										setter.setAccessible(true);	//避免setter方法是private的时候抛出不可访问的异常
										setter.invoke(objectBean,new Object[]{refBean}); //通过setter方法将对象注入到组件中
									}
									break;
								}
							}
						}
					} catch (Exception e) {
						System.out.println(bd.getClassName() + "注入bean对象出错!!!");
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * 读取配置文件
	 * @param fileName
	 */
	@SuppressWarnings("unchecked")
	public void readXML(String fileName) {
		URL url = null;
		SAXReader reader = null;
		Document doc;
		try {
			url = this.getClass().getClassLoader().getResource(fileName);
			reader = new SAXReader();
			doc = reader.read(url);

			Map<String, String> nsMap = new HashMap<String, String>();
			nsMap.put("ns", "http://www.springframework.org/schema/beans"); 
			XPath xPath = doc.createXPath("//ns:beans/ns:bean");	//创建查询路径//ns:beans/ns:bean
			xPath.setNamespaceURIs(nsMap);	//设置命名空间
			List<Element> elements = xPath.selectNodes(doc);	//查询doc的beans下的所有bean元素
			for (Element e : elements) {
				String id = e.attributeValue("id");
				String className = e.attributeValue("class");
				BeanDefinition bd = new BeanDefinition(id, className);
				XPath propertyPath = e.createXPath("ns:property");
				propertyPath.setNamespaceURIs(nsMap);
				List<Element> propertys = propertyPath.selectNodes(e);
				for (Element property : propertys) {
					String name = property.attributeValue("name");
					String ref = property.attributeValue("ref");
					PropertyDefinition pd = new PropertyDefinition(name, ref);
					bd.getPropertys().add(pd);
				}
				beanDefinitions.add(bd);
			}
		} catch (DocumentException e1) {
			System.out.println("解析配置文件" + fileName + "出错!!!");
			e1.printStackTrace();
		}
	}
	
	/**
	 * 获取bean实例,单例
	 * @param id
	 * @return
	 */
	public Object getBean(String id) {
		return singletons.get(id);
	}
}


继续运行我们的测试类Test.java

package com.royzhou.test;

import com.royzhou.start.BeanContainer;
import com.royzhou.start.SaveUtil;

public class Test {
	public static void main(String[] args) {
		BeanContainer ctx = new BeanContainer("bean.xml");
		SaveUtil su = (SaveUtil)ctx.getBean("SaveUtil");
		su.save();
	}
}


发现输出结果与之前使用Spring容器时一致......

Spring的依赖注入包括基本对象的注入以及其他bean的注入
基本对象的注入使用如下:

package com.royzhou.start;

public class SimpleInjectBean {
	private String id;

	private String name;
	
	public SimpleInjectBean(String name) {
		this.name = name;
	}
	
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String toString() {
		return this.id + ":" + this.name;
	}
}


在bean.xml中我们可以注入id和name
<bean id="simpleBean" class="com.royzhou.start.SimpleInjectBean">
<property name="id" value="000001"></property> //通过setter注入
<constructor-arg index="0" value="royzhou"></constructor-arg> //通过构造函数注入
</bean>

bean的注入有两种情况:
1:
<bean id="SaveUtil" class="com.royzhou.start.SaveUtil">
<property name="save" ref="MobileDisk"></property>
</bean>
通过property的ref属性注入其他bean
2:
使用内部bean,注意该Bean只能在这个bean中使用,不能被其他bean占用
<bean id="SaveUtil" class="com.royzhou.start.SaveUtil">
<property name="save">
<bean class="com.royzhou.start.MobileDisk" />
</property>
</bean>


继续完善我们的BeanContainer类使其支持基本属性的注入:
<bean id="simpleBean" class="com.royzhou.start.SimpleInjectBean">
<property name="id" value="000001"></property>
<property name="name" value="royzhou"></property>
</bean>

修改PropertyDefinition.java加入value属性

package com.royzhou.start;

public class PropertyDefinition {
	private String name;
	private String value;
	private String ref;

	public PropertyDefinition(String name, String value, String ref) {
		super();
		this.name = name;
		this.value = value;
		this.ref = ref;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getRef() {
		return ref;
	}

	public void setRef(String ref) {
		this.ref = ref;
	}

	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}
}


修改BeanContainer类:

package com.royzhou.start;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.ConvertUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

public class BeanContainer {
	private List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
	
	private Map<String, Object> singletons = new HashMap<String, Object>();

	public BeanContainer(String fileName) {
		readXML(fileName);
		instanceBeans();
		injectBeans();
	}
	
	/**
	 * 实例化bean对象并储存在Map中
	 *
	 */
	public void instanceBeans() {
		String id = null;
		String className = null;
		Object objectBean = null;
		for (BeanDefinition bd : beanDefinitions) {
			id = bd.getId();
			className = bd.getClassName();
			if (className != null && !"".equals(className.trim())) {
				try {
					objectBean = Class.forName(className).newInstance();
					singletons.put(id, objectBean);
				} catch(Exception e) {
					System.out.println("实例化bean:" + className + "出错!!!");
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 注入依赖对象
	 *
	 */
	public void injectBeans() {
		Object objectBean = null;
		Object refBean = null;
		BeanInfo beanInfo = null;
		PropertyDescriptor[] propertyDescriptors = null;
		Method setter = null;
		String ref = null;
		Object value = null;
		for (BeanDefinition bd : beanDefinitions) {
			objectBean = singletons.get(bd.getId());
			if(objectBean!=null) {
				List<PropertyDefinition> propertys = bd.getPropertys();
				if(propertys!=null && propertys.size()>0) {
					try {
						beanInfo = Introspector.getBeanInfo(objectBean.getClass());	
						propertyDescriptors = beanInfo.getPropertyDescriptors();
						for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
							for(PropertyDefinition propertyDefinition : propertys) {
								if(propertyDefinition.getName().equals(propertyDescriptor.getName())) {
									setter = propertyDescriptor.getWriteMethod();	//获取setter方法
									if(setter!=null) {
										ref = propertyDefinition.getRef();
										if(ref!=null && !"".equals(ref)) {
											refBean = singletons.get(ref);
											setter.setAccessible(true);	//避免setter方法是private的时候抛出不可访问的异常
											setter.invoke(objectBean,new Object[]{refBean}); //通过setter方法将对象注入到组件中
										} else {
											//使用common-beanutils包中的ConvertUtils将值转换成对应的类型
											value = ConvertUtils.convert(propertyDefinition.getValue(), propertyDescriptor.getPropertyType());
											setter.setAccessible(true);	//避免setter方法是private的时候抛出不可访问的异常
											setter.invoke(objectBean,new Object[]{value}); //通过setter方法将对象注入到组件中
										}
									}
									break;
								}
							}
						}
					} catch (Exception e) {
						System.out.println(bd.getClassName() + "注入bean对象出错!!!");
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * 读取配置文件
	 * @param fileName
	 */
	@SuppressWarnings("unchecked")
	public void readXML(String fileName) {
		URL url = null;
		SAXReader reader = null;
		Document doc;
		try {
			url = this.getClass().getClassLoader().getResource(fileName);
			reader = new SAXReader();
			doc = reader.read(url);

			Map<String, String> nsMap = new HashMap<String, String>();
			nsMap.put("ns", "http://www.springframework.org/schema/beans"); 
			XPath xPath = doc.createXPath("//ns:beans/ns:bean");	//创建查询路径//ns:beans/ns:bean
			xPath.setNamespaceURIs(nsMap);	//设置命名空间
			List<Element> elements = xPath.selectNodes(doc);	//查询doc的beans下的所有bean元素
			for (Element e : elements) {
				String id = e.attributeValue("id");
				String className = e.attributeValue("class");
				BeanDefinition bd = new BeanDefinition(id, className);
				XPath propertyPath = e.createXPath("ns:property");
				propertyPath.setNamespaceURIs(nsMap);
				List<Element> propertys = propertyPath.selectNodes(e);
				for (Element property : propertys) {
					String name = property.attributeValue("name");
					String value = property.attributeValue("value");
					String ref = property.attributeValue("ref");
					PropertyDefinition pd = new PropertyDefinition(name,value, ref);
					bd.getPropertys().add(pd);
				}
				beanDefinitions.add(bd);
			}
		} catch (DocumentException e1) {
			System.out.println("解析配置文件" + fileName + "出错!!!");
			e1.printStackTrace();
		}
	}
	
	/**
	 * 获取bean实例,单例
	 * @param id
	 * @return
	 */
	public Object getBean(String id) {
		return singletons.get(id);
	}
}


测试类如下:

package com.royzhou.start;

public class SimpleInjectBean {
	private String id;

	private String name;
	
	public SimpleInjectBean() {
		//由于BeanContainer在实例化时调用的是无参构造函数,故加入此方法避免异常
	}
	
	public SimpleInjectBean(String name) {
		this.name = name;
	}
	
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String toString() {
		return this.id + ":" + this.name;
	}
}


运行Test.java类

package com.royzhou.test;

import com.royzhou.start.BeanContainer;
import com.royzhou.start.SimpleInjectBean;

public class Test {
	public static void main(String[] args) {
		BeanContainer ctx = new BeanContainer("bean.xml");
		SimpleInjectBean sib = (SimpleInjectBean)ctx.getBean("simpleBean");
		System.out.println(sib);
	}
}


后台输出:
000001:royzhou
说明我们的BeanContainer很好的支持了基本对象的注入.

集合类型的注入(List/Set/Map/Properties)
集合注入类CollectionInject.java,包含List/Set/Map/Properties四种类型

package com.royzhou.start;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class CollectionInject {
	private List<String> list = new ArrayList<String>();

	private Set<String> set = new HashSet<String>();

	private Map<String, Object> map = new HashMap<String, Object>();

	private Properties properties = new Properties();

	public List<String> getList() {
		return list;
	}

	public void setList(List<String> list) {
		this.list = list;
	}

	public Map<String, Object> getMap() {
		return map;
	}

	public void setMap(Map<String, Object> map) {
		this.map = map;
	}

	public Properties getProperties() {
		return properties;
	}

	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	public Set<String> getSet() {
		return set;
	}

	public void setSet(Set<String> set) {
		this.set = set;
	}
}


在bean.xml中配置注入值:

<bean id="collectionInject" class="com.royzhou.start.CollectionInject">
	<property name="list">
		<list>
			<value>list1</value>
			<value>list2</value>
			<value>list3</value>
		</list>
	</property>
	<property name="set">
		<set>
			<value>set1</value>
			<value>set2</value>
			<value>set2</value>
			<value>set3</value>
		</set>
	</property>
	<property name="map">
		<map>
			<entry key="map1" value="mapValue1"></entry>
			<entry key="map2" value="mapValue2"></entry>
			<entry key="map3" value="mapValue3"></entry>
		</map>
	</property>
	<property name="properties">
		<props>
			<prop key="prop1">propValue1</prop>
			<prop key="prop2">propValue2</prop>
			<prop key="prop3">propValue3</prop>
		</props>
	</property>
</bean>


编写我们的测试类:

package com.royzhou.test;

import java.util.Set;
import java.util.Map.Entry;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.royzhou.start.CollectionInject;

public class Test {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		CollectionInject ci = (CollectionInject)ctx.getBean("collectionInject");
		System.out.println("================list===============");
		for(String s : ci.getList()) {
			System.out.println(s);
		}
		System.out.println("================set===============");
		for(String s : ci.getSet()) {
			System.out.println(s);
		}
		System.out.println("================map===============");
		Set<Entry<String,Object>> maps = ci.getMap().entrySet();
		for(Entry e : maps) {
			System.out.println(e.getKey() + ":=" + e.getValue());
		}
		System.out.println("================properties===============");
		Set<Entry<Object,Object>> props = ci.getProperties().entrySet();
		for(Entry e : props) {
			System.out.println(e.getKey() + ":=" + e.getValue());
		}
	}
}


运行之后输出预期结果:
================list===============
list1
list2
list3
================set===============
set1
set2
set3
================map===============
map1:=mapValue1
map2:=mapValue2
map3:=mapValue3
================properties===============
prop3:=propValue3
prop2:=propValue2
prop1:=propValue1

Spring的依赖注入可以使用手工装配和自动装配,建议使用手工装配,因为自动装配有时变得难以控制,可能导致无法预见的问题
手工注入有三种方式:
1-使用构造器注入
主要是在bean中配置<constructor-arg></constructor-arg>,需要设置其inedx(参数位置,从0开始),value或者ref属性的值
如:(
<constructor-arg index="0" value="royzhou"></constructor-arg>
<constructor-arg index="1" ref="otherBean"></constructor-arg>
)
2-使用setter注入(也就是我们上面程序所使用的注入方式)
3-使用Field注入(用于注解方式Annotation)
为了简化XML文件的配置,Spring2.5增加了注解功能来实现依赖对象的注入
使用注解方式我们必须修改我们的bean.xml文件如下:

<?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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<context:annotation-config />
<beans>


上述配置主要是帮我们注册了多个对注解进行解析处理的解析器.才能使我们的注解生效
可采用两种注解方式将对象注入到组件中
@AutoWire(Spring提供): 默认按类型在Spring容器中寻找类型匹配的Bean
@Resource(JDK提供): 默认按照名称在Spring容器中寻找类型匹配的Bean,如果找不到则采用类型寻找
   同时我们可以为注解指定名称即使用(@Resource(name="***"))指定寻找的名称
   如果没有指定name属性 则根据注解的属性的名称将第一个字母改成小写,以此为name的值去寻找

建议采用@Resource方式进行注解,必须导入common-annotations.jar包

下面我们使用注解方式来注入对象
bean.xml简化为如下:

<?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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<context:annotation-config />
	<bean id="usb" class="com.royzhou.start.Usb"></bean>
	<bean id="MobileDisk" class="com.royzhou.start.MobileDisk"></bean>
	<bean id="SaveUtil" class="com.royzhou.start.SaveUtil"></bean>
</beans>


修改之前的SaveUtil类

package com.royzhou.start;

import javax.annotation.Resource;

public class SaveUtil {
	@Resource(name="usb")
	public ISave save;
	
	public void setSave(ISave save) {
		this.save = save;
	}

	public void save() {
		save.save();
	}
}


编写测试类:

package com.royzhou.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.royzhou.start.SaveUtil;

public class Test {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		SaveUtil su = (SaveUtil)ctx.getBean("SaveUtil");
		su.save();
	}
}


运行结果:
using usb to save!!!

说明我们的注解生效了...
去掉name属性再次运行发现抛出异常信息:
No unique bean of type [com.royzhou.start.ISave] is defined: expected single matching bean but found 2: [usb, MobileDisk]
由此可见在没有设置name属性的时候spring会去寻找实现ISave接口的类:Usb和MobileDisk,因为找到的类型并不是唯一的,所以抛出上面异常信息.

我们也可以给一个属性的setter方法上加上注解:
修改SaveUtil.java

package com.royzhou.start;

import javax.annotation.Resource;

public class SaveUtil {
	public ISave save;
	
	@Resource(name="MobileDisk")
	public void setSave(ISave save) {
		this.save = save;
	}

	public void save() {
		save.save();
	}
}


运行之后反正结果正常,说明注解也生效了...


现在我们继续完善之前的BeanContainer类,使其支持注解方式将对象注入到组件中.
首先我们需要一个注解类BeanResource

package com.royzhou.start;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//指定运行期间检查
@Retention(RetentionPolicy.RUNTIME)
//指定作用对象,可作用与Field和Method上
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface BeanResource {
	//设置name属性,类似@Resource(name="")中的name
	String name() default "";
}


修改我们的BeanContainer.java加入注解解析器

package com.royzhou.start;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.ConvertUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

public class BeanContainer {
	private List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
	
	private Map<String, Object> singletons = new HashMap<String, Object>();

	public BeanContainer(String fileName) {
		readXML(fileName);
		instanceBeans();
		annotationInject();
		injectBeans();
	}
	
	/**
	 * 实例化bean对象并储存在Map中
	 *
	 */
	public void instanceBeans() {
		String id = null;
		String className = null;
		Object objectBean = null;
		for (BeanDefinition bd : beanDefinitions) {
			id = bd.getId();
			className = bd.getClassName();
			if (className != null && !"".equals(className.trim())) {
				try {
					objectBean = Class.forName(className).newInstance();
					singletons.put(id, objectBean);
				} catch(Exception e) {
					System.out.println("实例化bean:" + className + "出错!!!");
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 注入依赖对象
	 *
	 */
	public void injectBeans() {
		Object objectBean = null;
		Object refBean = null;
		BeanInfo beanInfo = null;
		PropertyDescriptor[] propertyDescriptors = null;
		Method setter = null;
		String ref = null;
		Object value = null;
		for (BeanDefinition bd : beanDefinitions) {
			objectBean = singletons.get(bd.getId());
			if(objectBean!=null) {
				List<PropertyDefinition> propertys = bd.getPropertys();
				if(propertys!=null && propertys.size()>0) {
					try {
						beanInfo = Introspector.getBeanInfo(objectBean.getClass());	
						propertyDescriptors = beanInfo.getPropertyDescriptors();
						for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
							for(PropertyDefinition propertyDefinition : propertys) {
								if(propertyDefinition.getName().equals(propertyDescriptor.getName())) {
									setter = propertyDescriptor.getWriteMethod();	//获取setter方法
									if(setter!=null) {
										ref = propertyDefinition.getRef();
										if(ref!=null && !"".equals(ref)) {
											refBean = singletons.get(ref);
											setter.setAccessible(true);	//避免setter方法是private的时候抛出不可访问的异常
											setter.invoke(objectBean,new Object[]{refBean}); //通过setter方法将对象注入到组件中
										} else {
											//使用ConvertUtils转换成对应的类型
											value = ConvertUtils.convert(propertyDefinition.getValue(), propertyDescriptor.getPropertyType());
											setter.setAccessible(true);	//避免setter方法是private的时候抛出不可访问的异常
											setter.invoke(objectBean,new Object[]{value}); //通过setter方法将对象注入到组件中
										}
									}
									break;
								}
							}
						}
					} catch (Exception e) {
						System.out.println(bd.getClassName() + "注入bean对象出错!!!");
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * 注解处理器
	 *
	 */
	public void annotationInject() {
		Object objectBean = null;
		for(String beanName : singletons.keySet()) {
			objectBean = singletons.get(beanName);
			if(objectBean!=null) {
				try {
					//解析方法中是否存在annotation
					PropertyDescriptor[] propertys = Introspector.getBeanInfo(objectBean.getClass()).getPropertyDescriptors();
					for(PropertyDescriptor propertyDescriptor : propertys) {
						Method setter = propertyDescriptor.getWriteMethod();
						if(setter!=null && setter.isAnnotationPresent(BeanResource.class)) {
							BeanResource beanResource = setter.getAnnotation(BeanResource.class);
							Object value = beanResource.name();
							//是否设置了name属性
							if(value!=null && !"".equals(value)) {
								value = singletons.get(value);
							} else {
								//如果没有设置name则按照属性名称查找
								value = singletons.get(propertyDescriptor.getName());
								//如果没有找到则按类型匹配
								if(value==null) {
									for(String key : singletons.keySet()){
										if(propertyDescriptor.getPropertyType().isAssignableFrom(singletons.get(key).getClass())){
											value = singletons.get(key);
											break;
										}
									}
								}
							}
							setter.setAccessible(true);
							setter.invoke(objectBean, value);//把引用对象注入到属性
						} 
					}
					//解析字段中是否存在annotation
					Field[] fields = objectBean.getClass().getDeclaredFields();
					for(Field field : fields) {
						if(field.isAnnotationPresent(BeanResource.class)) {
							BeanResource beanResource = field.getAnnotation(BeanResource.class);
							Object value = beanResource.name();
							if(value!=null && !"".equals(value)) {
								value = singletons.get(value);
							} else {
								value = singletons.get(field.getName());
								if(value==null) {
									for(String key : singletons.keySet()){
										if(field.getType().isAssignableFrom(singletons.get(key).getClass())){
											value = singletons.get(key);
											break;
										}
									}
								}
							}
							field.setAccessible(true);//允许访问private字段
							field.set(objectBean, value);
						}
					}
				} catch (Exception e) {
					System.out.println("注解解析器处理出错!!!");
					e.printStackTrace();
				}
				
			}
		}
	}
	
	/**
	 * 读取配置文件
	 * @param fileName
	 */
	@SuppressWarnings("unchecked")
	public void readXML(String fileName) {
		URL url = null;
		SAXReader reader = null;
		Document doc;
		try {
			url = this.getClass().getClassLoader().getResource(fileName);
			reader = new SAXReader();
			doc = reader.read(url);

			Map<String, String> nsMap = new HashMap<String, String>();
			nsMap.put("ns", "http://www.springframework.org/schema/beans"); 
			XPath xPath = doc.createXPath("//ns:beans/ns:bean");	//创建查询路径//ns:beans/ns:bean
			xPath.setNamespaceURIs(nsMap);	//设置命名空间
			List<Element> elements = xPath.selectNodes(doc);	//查询doc的beans下的所有bean元素
			for (Element e : elements) {
				String id = e.attributeValue("id");
				String className = e.attributeValue("class");
				BeanDefinition bd = new BeanDefinition(id, className);
				XPath propertyPath = e.createXPath("ns:property");
				propertyPath.setNamespaceURIs(nsMap);
				List<Element> propertys = propertyPath.selectNodes(e);
				for (Element property : propertys) {
					String name = property.attributeValue("name");
					String value = property.attributeValue("value");
					String ref = property.attributeValue("ref");
					PropertyDefinition pd = new PropertyDefinition(name,value, ref);
					bd.getPropertys().add(pd);
				}
				beanDefinitions.add(bd);
			}
		} catch (DocumentException e1) {
			System.out.println("解析配置文件" + fileName + "出错!!!");
			e1.printStackTrace();
		}
	}
	
	/**
	 * 获取bean实例,单例
	 * @param id
	 * @return
	 */
	public Object getBean(String id) {
		return singletons.get(id);
	}
}


修改SaveUtil类改用我们的注解BeanResource

package com.royzhou.start;

public class SaveUtil {
	public ISave save;
	
	@BeanResource(name="MobileDisk")
	public void setSave(ISave save) {
		this.save = save;
	}

	public void save() {
		save.save();
	}
}


重新运行测试类:

package com.royzhou.test;

import com.royzhou.start.BeanContainer;
import com.royzhou.start.SaveUtil;

public class Test {
	public static void main(String[] args) {
		BeanContainer ctx = new BeanContainer("bean.xml");
		SaveUtil su = (SaveUtil)ctx.getBean("SaveUtil");
		su.save();
	}
}


控制台输出结果为:
using mobile disk to save!!!

说明我们的容器已经正确的处理了我们自定义的注解,正常工作了....

尽管我们使用了注解方式来减少bean.xml的配置
但是如果项目中存在大量的bean对象 我们仍然需要在bean.xml中配置大量的bean
为此Spring为我们提供了一种通过在Classpath自动扫描的方式把组件纳入Spring管理
为了实现这样的功能,我们需要修改配置文件bean.xml

<?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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<context:component-scan base-package="com.royzhou.start" />
</beans>


其中<context:component-scan base-package="com.royzhou.start" /> 告诉spring容器我们要扫描com.royzhou.start包(包括子包)下的类,结合类上定义的注解
来确定是不是要将这些bean纳入spring管理

采用这种方式我们需要给那些要交给spring管理的类加上注解:
@Service : 用于标注业务层
@Controller : 用于标注控制层
@Repository : 用于标注DAO层
当组件不好归类时,我们可以使用@Component注解

使用之前存储例子测试:

package com.royzhou.start;

import org.springframework.stereotype.Component;

@Service
public class Usb implements ISave {
	
	public void save() {
		System.out.println("using usb to save!!!");
	}
}


package com.royzhou.start;

import org.springframework.stereotype.Service;

@Service
public class MobileDisk implements ISave {

	public void save() {
		System.out.println("using mobile disk to save!!!");
	}
}


package com.royzhou.start;

import javax.annotation.Resource;

import org.springframework.stereotype.Controller;

@Controller
public class SaveUtil {
	public ISave save;
	
	@Resource(name="mobileDisk")
	public void setSave(ISave save) {
		this.save = save;
	}

	public void save() {
		save.save();
	}
}


package com.royzhou.start;

import javax.annotation.Resource;

import org.springframework.stereotype.Controller;

@Controller
public class SaveUtil {
	public ISave save;
	
	//由于MobileDisk注解没有指定名称,所以默认使用类名第一个字母变小写后的字符串来查找
	@Resource(name="mobileDisk")
	public void setSave(ISave save) {
		this.save = save;
	}

	public void save() {
		save.save();
	}
}


运行测试程序输出:
using mobile disk to save!!!
说明我们的注解已经生效,使用这种方式可以很大程度的减少了bean.xml中配置大量bean的复杂工作,加速我们的开发速度.
这里可能会有一些疑问:我们之前在bean.xml中配置的scope,init-method,destroy-method之类的方法现在如何配置.
Spring同样为我们提供了,也可以使用注解来实现:
如:
package com.royzhou.start;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller @Scope("prototype")
public class SaveUtil {
	public ISave save;
	
	@PostConstruct
	public void init() {
		System.out.println("I am init method!!!");
	}
	
	@Resource(name="mobileDisk")
	public void setSave(ISave save) {
		this.save = save;
	}

	public void save() {
		save.save();
	}
	
	@PreDestroy
	public void destroy() {
		System.out.println("I am destroy method!!!");
	}
}


使用@Scope("prototype")可以设置bean的作用域
使用@PostConstruct 可以设置bean的初始化方法
使用@PreDestroy 可以设置bean的销毁方法


从上面的例子中我们可以得出结论:
使用Spring的IOC(控制反转)为我们的开发带来了很大的便利,很大程度的降低了程序之间的耦合,增强了系统的可维护性.
   发表时间:2009-09-30  
我晕,帖的还不是一般的多。可惜就快被新手了。
0 请登录后投票
   发表时间:2010-04-09  
很强大.....
0 请登录后投票
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics