浏览 2054 次
锁定老帖子 主题:Spring IOC
该帖已经被评为新手帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-09-30
首先我们有这样一个需求. 使用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(控制反转)为我们的开发带来了很大的便利,很大程度的降低了程序之间的耦合,增强了系统的可维护性. 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-09-30
我晕,帖的还不是一般的多。可惜就快被新手了。
|
|
返回顶楼 | |
发表时间:2010-04-09
很强大.....
|
|
返回顶楼 | |