现库存的IOC代码写了两种实现方式,一种是基于xml文件动态创建对象的实现方式,一种是基于注解创建对象的实现方式,先把这篇基于xml文件创建对象的实现方式写出来,再去发注解的。
先发类图
先创建一个MAVEN工程,在pom.xml导入需要用到的依赖,在这里只需要导入解析xml文件的dom4j的依赖即可
<!--解析xml-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
创建实体类
package com.spring.ioc.ioc_not_annotation.pojo;
public class User {
private int id;
private String name;
private String password;
public User() {
System.out.println("无参构造函数执行");
}
//在这里就省略get和set方法了不贴出来了
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
接着创建user.xml文件,放到resources目录下
内容如下
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="user1" class="com.spring.ioc.ioc_not_annotation.pojo.User" scope="singleton">
<property name="id" value="1"/>
<property name="name" value="111"/>
<property name="password" value="123456"/>
</bean>
<bean id="user2" class="com.spring.ioc.ioc_not_annotation.pojo.User" scope="prototype">
<property name="id" value="2"/>
<property name="name" value="222"/>
<property name="password" value="654321"/>
</bean>
</beans>
接下来开始创建核心的ClassPathXmlApplicationContext类。
首先定义几个后面需要用到的容器,在这里我用map来储存。
//存储单例对象容器
private Map<String, Object> singletonBeanFactory;
//存储创建类定义对象的容器
private Map<String, Class<?>> beanDefinationFactory;
//存储beanElement 也就是xml文件里的bean节点
private Map<String, Element> beanEleMap;
//存储bean的scope属性容器
private Map<String, String> beanScopeMap;
定义有参的构造函数,初始化内部容器,并调用初始化方法
//定义有参的构造方法,在创建此类实例时,需要指定xml文件路径
public ClassPathXmlApplicationContext(String xmlpath) {
singletonBeanFactory = new HashMap<>();
beanDefinationFactory = new HashMap<>();
beanEleMap = new HashMap<>();
beanScopeMap = new HashMap<>();
init(xmlpath);
}
init 初始化方法如下,每一行都加了详细代码,直接看代码就行
/**
* 初始化方法,在创建ClassPathXmlApplicationContext 对象时初始化容器
* 并解析xml配置文件,获取bean元素,在运行时动态创建对象,并为对象的属性赋值
* 最后吧对象存放在容器中
*
* @param xmlPath
*/
private void init(String xmlPath) {
//使用dom4j技术读取xml文档 创建saxreader对象
SAXReader reader = new SAXReader();
try {
//获取读取xml配置文件的输入流
InputStream is = getClass().getClassLoader().getResourceAsStream(xmlPath);
//读取xml,该操作会返回一个Document对象
Document document = reader.read(is);
//获取文档的根元素
Element rootElement = document.getRootElement();
//获取根元素下所有的bean节点,element方法会返回元素的集合
List<Element> beanElements = rootElement.elements("bean");
//遍历bean节点
for (Element beanEle : beanElements) {
//获取bean节点的id,把该值做为key存储在map集合中
String beanId = beanEle.attributeValue("id");
//将beanElementd对象存入map中,为对象设置属性值时使用
beanEleMap.put(beanId, beanEle);
//获取bean节点的scope值
String beanScope = beanEle.attributeValue("scope");
//如果beanScope不等于null,将bean的scope值存入map中方便后续使用
if (beanScope != null) {
beanScopeMap.put(beanId, beanScope);
}
//获取bean节点实体类对应的class路径
String beanClassPath = beanEle.attributeValue("class");
//利用反射根据class路径得到类定义对象
Class<?> cls = Class.forName(beanClassPath);
//如果反射获取的类定义对象不为null,则放入工厂中,方便创建实例对象
if (cls != null) {
beanDefinationFactory.put(beanId, cls);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
初始化工作完成后,就可以开始写获取对象的getBean方法了
/**
* 根据传入的bean的id值获取容器中的对象,类型为Object
* @param beanId
* @return
*/
public Object getBean(String beanId) {
//根据传入的beanId获取类对象
Class<?> cls = beanDefinationFactory.get(beanId);
//根据id获取该bean对象的element对象
Element element = beanEleMap.get(beanId);
//获取scopeMap中bean元素的scope属性值
String scope = beanScopeMap.get(beanId);
Object object = null;
try {
//如果scope 等于singleton,或者scope为空 由于为空的话系统默认设置的作用域是singleton,创建单例对象;
if ("singleton".equals(scope) || null == scope) {
//判断容器中是否已有该对象的实例,如果没有,创建一个实例对象放到容器中
if (singletonBeanFactory.get(beanId) == null) {
//创建反射加载的类的实例
Object instance = cls.newInstance();
//将创建后的对象放入实例对象容器中 beanid为xml配置文件的bean节点的id 也就是实例对象的名字
singletonBeanFactory.put(beanId, instance);
}
//根据beanid获取实例对象容器中的对象
object = singletonBeanFactory.get(beanId);
}
//如果socpe等于prototype,则创建并返回多例对象
if ("prototype".equals(scope)) {
object = cls.newInstance();
}
setFieldValues(beanId, element, scope, cls, object);
return object;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//暂不支持其他类型,若不是以上两种类型或者遭遇异常则返回null;
return null;
}
/**
* 此为重载方法,再根据传入的bean的id值获取容器中的对象的同时,还可以自动转换类型。
* 返回指定的类型,在调用该方法时省略强转的步骤,传入时第二个参数为指定的类型,
* 方法实现上一个方法,只是在返回对象前加了类型强转
*
* @param beanId 传入bean的id
* @param cls 指定的类型
* @param <T> 未知类型
* @return 返回指定的类型
*/
public <T> T getBean(String beanId, Class<?> cls) {
return (T) getBean(beanId);
}
在以上getBean方法中,调用了setFieldValues方法,该方法用于给对象的成员属性赋值
/**
* 该方法用于为对象设置成员属性值
* @param beanId bean元素的id
* @param element bean所对应的element对象
* @param beanScope bean元素的scope属性
* @param cls 类对象
* @param object 要为其成员属性赋值的实例对象
*/
private void setFieldValues(String beanId, Element element, String beanScope, Class<?> cls, Object object) {
try {
//获取每个bean元素下的所有property元素,该元素用于给属性赋值
List<Element> propEles = element.elements("property");
//如果property元素集合为null,调用putInMap方法将对象放进Map中
if (propEles == null) {
return;
}
//遍历property元素集合
for (Element e : propEles) {
//获取每个元素的name属性值和value属性值
String filedName = e.attributeValue("name");
String filedValue = e.attributeValue("value");
//利用反射根据name属性获得类的成员属性
Field field = cls.getDeclaredField(filedName);
//将该属性设置为可访问(防止成员属性被私有化导致访问失败)
field.setAccessible(true);
//获取成员属性的类型名称,若非字符串类型,则需要做相应的转换
String filedTypeName = field.getType().getName();
//判断该成员属性是否是int或INteger类型
if ("int".equals(filedTypeName) || "java.lang.Integer".equals(filedTypeName)){
//转换为int类型并为该成员属性赋值
int intFiledValue = Integer.parseInt(filedValue);
field.set(object,intFiledValue);
}
//判断改成员是否是String类型
if ("java.lang.String".equals(filedTypeName)){
//为该成员属性赋值
field.set(object, filedValue);
}
//此处省略其他类型的判断 道理同上。
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
以上是获取单例对象或多例对象需要调用getBean方法的全部内容,调用完后,还需要关闭容器释放资源,还需要一个destroy方法
public void destroy(){
singletonBeanFactory.clear();
singletonBeanFactory=null;
beanDefinationFactory.clear();
beanDefinationFactory=null;
beanEleMap.clear();
beanEleMap=null;
beanScopeMap.clear();
beanScopeMap=null;
}
至此,ClassPathXmlApplicationContext类中的内容全部完成,可以写测试类开始测试
package spring.ioc;
import com.spring.ioc.ioc_not_annotation.pojo.User;
import com.spring.ioc.ioc_not_annotation.applicationContext.ClassPathXmlApplicationContext;
public class IOCNotAnnotationTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("spring/ioc_not_annottation/user.xml");
//使用手动强转的方式获取单例的user对象
User user1_1 = (User) ctx.getBean("user1");
System.out.println("单例user1_1:"+user1_1);
/**
*getBean有一个重载方法,根据传入的id值获取到bean的同时,强制转换为指定为类型.
*/
//使用传入类对象的方式获取单例的user对象
User user1_2 = ctx.getBean("user1",User.class);
System.out.println("单例user1_2:"+user1_2);
//使用手动强转的方式获取多例的user对象
User user2_1 = (User) ctx.getBean("user2");
System.out.println("多例user2_1:"+user2_1);
//使用传入类对象的方式获取单例的user对象
User user2_2 = ctx.getBean("user2",User.class);
System.out.println("多例user2_2:"+user2_2);
ctx.destroy();
}
}
测试结果
先把实体类中的toString方法注释掉,查看一遍结果
/* @Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}*/
从控制台输出的结果中可以看到,获取到了4个对象,其中前两个为单例对象,后两个为多例对象,两个单例对象在默认调用Object类中的toString方法是其地址值的hashCode十六进制的映射,其映射值完全一致,可以说明是同一个对象。而且创建了4个对象,其无参的构造方法只执行了三次。
再加上toString方法测试一遍
可以看到对象所定义的属性值也在创建时成功赋值了。
以上就是基于xml文件动态创建对象的过程。
总结一下流程:
先开始创建工厂,初始化内部的容器,他会通过传入的xml文件路径,找到这个路径并解析他,获取到bean的id、class实体类路径和scope作用域值。再将scope值、通过class路径利用反射机制生成反射对象,分别将他们作为value,beanId作为key存储在类对象容器,和scope容器中,再将beanID作为key,bean节点作为value放入保存节点的map容器中。都存储完毕后,容器初始化完成。
容器初始化完成后,可以调用工厂内的getBean(beanID)方法,他会通过BeanId分别从上述的容器中获取相应的属性,scope,类对象和节点,获取过后,
先判断scope对象是否是单例,如果是则再判断单例容器是否有该对象的实例,如果没有则创建一个类对象,在将beanid作为可以,对象作为值存储在单例容器中,在通过beanid获取到该容器的对象并返回出去,
如果scope对象是多例,则直接创建对象返回出去。
在创建对象期间会先通过beanId获取到bean容器内的bean节点,然后遍历该节点,获取子节点property的name和value,再根据反射用name获取到类对象的成员属性,将这些成员属性设置为可访问(因为类里成员属性设置的是私有属性,为了防止访问私有属性报错,将他设置为可访问),
再将获取到的成员属性进行类型转换,将他们的属性转换成统一属性,比如int和Integer都变成Integer,转换类型完成后,就可以将value3(这个value为property节点的value值)赋值给成员属性了。至此成员属性赋值完毕