Spring IOC

学习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(控制反转)为我们的开发带来了很大的便利,很大程度的降低了程序之间的耦合,增强了系统的可维护性.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值