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

自己实现spring IOC

2015-11-18 10:39 477 查看
自己实现一个简易版的ioc容器,通过xml配置文件来加载bean

首先编写需要用到的bean,这里写三个简单的bean分别是A,B,C

package com.bean;

public class A {

private String name;
public A() {
System.out.println("创建A");
}

public String getName() {
return name;
}

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

}


package com.bean;

public class B {

private A a;
public B() {
System.out.println("创建B");
}

public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}

}


package com.bean;

public class C {
private B b;
public C() {
System.out.println("创建C");
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}

}


接下来编写配置文件 applicationContext.xml

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

<bean name="a" class="com.bean.A" >
<property name="name" value="Tom">
</property>
</bean>

<bean name="b" class="com.bean.B" scope="prototype">
<property name="a" ref="a">
</property>
</bean>

<bean name="c" class="com.bean.C" scope="prototype">
<property name="b" ref="b">
</property>
</bean>

</beans>


然后是保存bean信息的类,bean信息包括:bean的name,class,scope,和property

这里的scope值只设定为singleton和prototype两种,spring中bean 的scope值默认为singleton,表示在同一个ioc容器中只有一个实例,而prototype则是每次getBean()都会得到一个新的实例。

property有可能是一个普通属性,也可能是引用另一个bean

package com.config;

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

public class Bean {

private String name;
private String className;
private String scope = "singleton";//scope默认为singleton

private List<Property> properties = new ArrayList<Property>();

public String getName() {
return name;
}

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

public String getClassName() {
return className;
}

public void setClassName(String className) {
this.className = className;
}

public List<Property> getProperties() {
return properties;
}

public void setProperties(List<Property> properties) {
this.properties = properties;
}
//重新toString 方法 为了测试的时候可能明确看到读取的bean信息
@Override
public String toString() {
// TODO Auto-generated method stub
return "Bean[name="+name+" class="+className+" scope="+scope+" property="+properties+"]";
}

public String getScope() {
return scope;
}

public void setScope(String scope) {
this.scope = scope;
}

}


package com.config;

public class Property {

private String name;
private String value; //value表示普通的属性值
private String ref;   //ref表示引用的其他bean的name
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}

}


读取和解析xml文件,得到bean的信息,将其封装为bean对象,返回一个Map<beanName,bean>

package com.config.parse;

import java.io.InputStream;
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.io.SAXReader;

import com.config.Bean;
import com.config.Property;

public class ConfigManager {

//读取配置文件,并返回读取结果
public static Map<String,Bean> getConfig(String path){
Map<String,Bean> map = new HashMap<String,Bean>();
//dom4j 实现
//1.创建解析器
SAXReader reader = new SAXReader();
//2,加载配置文件=> document对象
InputStream is = ConfigManager.class.getResourceAsStream(path);
Document doc = null;
try {
doc = reader.read(is);
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//3.定义xpath表达式,取出所有Bean对象
String xpath = "//bean";
//4.对Bean元素进行遍历
List<Element> list = doc.selectNodes(xpath);
if(list != null){
for(Element beanEle : list){
//将bean元素的name/class 属性封装到Bean对象中
String name = beanEle.attributeValue("name");
String className = beanEle.attributeValue("class");
String scope = beanEle.attributeValue("scope");

Bean bean = new Bean();
bean.setName(name);
bean.setClassName(className);
if(null != scope){ //不设置scope值时,默认为singleton
bean.setScope(scope);
}
//获得Bean元素下的所有property子元素,将属性封装到Property对象
List<Element> children = beanEle.elements("property");

if(children != null){
for(Element child : children){
Property prop = new Property();
String pName = child.attributeValue("name");
String pValue = child.attributeValue("value");
String pRef = child.attributeValue("ref");

prop.setName(pName);
prop.setValue(pValue);
prop.setRef(pRef);
//将Property封装到Bean对象
bean.getProperties().add(prop);
}
}
//将Bean对象封装到Map中(用于返回)
map.put(name, bean);
}
}

//5.返回Map结果
return map;
}

}


利用反射创建bean实例并将属性注入,在容器初始化的时候,先将所有作用域为singleton的bean加载到容器中,而prototype类型的bean不会保存在容器中,每次调用getBean时都会创建一个实例。

首先创建一个beanFactory

package com.main;

public interface BeanFactory {

//根据Bean的name获得Bean对象的方法
Object getBean(String beanName);

}


实现了beanFactory,并创建容器和bean的类

package com.main;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.config.Bean;
import com.config.Property;
import com.config.parse.ConfigManager;
import com.utils.BeanUtils;

public class ClassPathXmlApplicationContext implements BeanFactory {

//希望在ClassPathXmlApplicationContext类一创建
//就初始化spring容器(装载Bean实例)

private Map<String, Bean> config; //获取的配置文件
//使用一个map来作为容器
private Map<String, Object> context = new HashMap<String, Object>();

@Override
public Object getBean(String beanName) {
//根据bean的名称获得bean实例

Object bean = context.get(beanName);
//如果bean的scope属性为prototype,那么context中不会包含该bean,需要创建该bean并返回
if(null == bean){
bean= createBean(config.get(beanName));
}

return bean;
}
public ClassPathXmlApplicationContext(String path) {
//1.读取配置文件获得需要初始化的Bean信息
config = ConfigManager.getConfig(path);
//2.遍历配置,初始化Bean
if(config != null){
for(Entry<String, Bean> en : config.entrySet()){
//获取配置中的Bean信息
String beanName = en.getKey();
Bean bean = en.getValue();
Object existBean = context.get(beanName);
//先判断容器中是否已存在该bean,因为createBean方法在创建并将一个bean加载到容器时,也会创建并加载它引用的bean
if(existBean == null && bean.getScope().equals("singleton")){
//如果不存在,并且bean的scope属性为singleton时才将其放入容器中
//根据bean配置创建bean对象
Object beanObj = createBean(bean);
//                System.out.println(beanObj);
//3.将初始的Bean放入容器
context.put(beanName, beanObj);
}
}
}

}

private Object createBean(Bean bean){
//1.获得要创建的bean的Class
String className = bean.getClassName();
Class clazz = null;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获得class后将class对应的对象创建出来
Object beanObj = null;
try {
beanObj = clazz.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2.获得Bean的属性,将其注入
if(bean.getProperties() != null){
for(Property prop : bean.getProperties()){

//获得要注入的元素名称(属性名或者bean名)
String name = prop.getName();
//根据属性名称获得对应属性的set方法
Method setMethod = BeanUtils.getWriteMethod(beanObj, name);

Object param = null;
//注入分两种情况
if(prop.getValue() != null){
//1.value属性注入
//获得要注入的属性值,为了简单起见,这里没有考虑属性类型转换的问题,通常,从配置文件中直接读取的属性值都为String类型,还需要转换成具体Bean中所定义的类型
param = prop.getValue();

}
if(prop.getRef() != null){
//2.其他bean的注入
//要注入其他bean到当前bean中,先从容器中查找,当前要注入的bean是否已经创建并放入容器中
Object existBean = context.get(prop.getRef());
if(existBean == null){
//容器中不存在要注入的bean,创建该bean
existBean = createBean(config.get(prop.getRef()));
//将创建好的单例bean放入容器中
if(config.get(prop.getRef()).getScope().equals("singleton"))
context.put(prop.getRef(), existBean);
}
param = existBean;
}

try {
//调用set方法注入该属性
setMethod.invoke(beanObj, param);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return beanObj;
}

}


上面用到了setXxx方法来注入属性,首先需要获得该类的setXxx方法,这里自己实现了一个简单的BeanUtils,也可以使用apache提供的BeanUtils

顺便提一下javaBean的一个命名规范,java的属性变量名都必须以小写字母开头,在特殊情况下也允许大写字母开头的属性变量名,不过必须满足“变量的前两个字母要么全部大写,要么全部小写”,例如iDcode这种命名是非法的,在sping中如果这样配置了属性名,将找不到setter方法。

package com.utils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class BeanUtils {

//参数1.bean对象
//参数2.要获得bean对象的属性名
public static Method getWriteMethod(Object beanObj, String name) {
// TODO Auto-generated method stub

Class<?> className = beanObj.getClass();
Method writeMethod = null;
/*使用反射
Field field = null;
try {
field = className.getDeclaredField(name);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

String firstLetter = name.substring(0, 1).toUpperCase();
String methodName = "set"+firstLetter+name.substring(1);

try {
writeMethod = className.getDeclaredMethod(methodName, field.getType());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
*/

//使用内省introspector 内省(IntroSpector)是Java语言对JavaBean 类属性、事件的一种缺省处理方法

try {
//1.分析bean对象,得到BeanInfo
BeanInfo beanInfo = Introspector.getBeanInfo(className);
//2.根据BeanInfo获得所有属性的描述器
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();

//3.遍历描述器

if(descriptors != null){
for(PropertyDescriptor descriptor : descriptors){
//判断当前遍历的描述器所描述的属性是否是我们要找的属性
if(name.equals(descriptor.getName())){
//4.如果找到了,返回该属性的set方法,如果没有找到抛出异常,提醒用户创建该属性的set方法
writeMethod = descriptor.getWriteMethod();
break;
}
}
}
if(writeMethod == null){
new Throwable("请创建创建该"+name+"属性的set方法");
}

} catch (IntrospectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

return writeMethod;

}

}


接下来写一个测试类

package com.test;

import java.util.Map;

import com.bean.A;
import com.bean.B;
import com.bean.C;
import com.config.Bean;
import com.config.parse.ConfigManager;
import com.main.BeanFactory;
import com.main.ClassPathXmlApplicationContext;

public class Test {

@org.junit.Test
public void fun(){
BeanFactory bf = new ClassPathXmlApplicationContext("/applicationContext.xml");

//        A a = (A) bf.getBean("a");
//        A a2 = (A) bf.getBean("a");
//        A a3 = (A) bf.getBean("a");
//
//
//        System.out.println(a.getName());
//
//        B b = (B) bf.getBean("b");
//        B b2 = (B) bf.getBean("b");
//        B b3 = (B) bf.getBean("b");
//        System.out.println(b.getA().getName());
C c = (C) bf.getBean("c");
C c2 = (C) bf.getBean("c");
C c3 = (C) bf.getBean("c");
}

}


输出的结果如下:

创建A
创建C
创建B
创建C
创建B
创建C
创建B

结果分析,其中A为singleton类型,所以在初始化时只创建了一次,B和C都是prototype类型,每次调用getBean时都会创建一次,其中C,引用了B,B引用了A,所以创建C的时候需要先检查B和A,这时A已经在初始化时创建了,所以每次创建C时都会创建一次B。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: