Spring作为Java Web最为流行的框架之一,其功能之强大,封装细节之全面不用过多赘述。使用Spring的方式很简单,不需要关注细节,把对象的创建和对象之间的关系都交给框架来管理,仅仅做好配置文件和实现具体的业务逻辑即可。可以说Spring为我们在编写Java Web应用时省去了大量重复的代码,并且可以降低对象与对象之间的耦合度。但若只是知其然,而不知其所以然,在编程时也难免会遇到各种问题,个人的水平也难以有所长进。
因此这篇文章的目的是分享本人对于SpringIOC如何实现控制反转,以及如何在运行过程中动态创建对象的理解,算是在漫长的学习过程中的一个小小的标的。废话不多说,直接上干货!
在手写Spring容器之前,需要做一些前期的准备工作:
首先是创建项目,在这里我为了后期下载jar包方便,创建的是maven工程,是在JDK1.7的环境下。当然你也可以创建普通的Java工程,在需要使用第三方的jar包时手动导入。
在pom.xml文件中添加jar包依赖路径,下载所需要的第三方API,本次需要使用dom4j去解析xml配置文件。
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
创建xml配置文件,为了后面使用方便,在这里我起名为:user.xml,放在根目录下:
配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="user1" class="entity.User" scope="singleton">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
<property name="password" value="12456"></property>
</bean>
<bean id="user2" class="entity.User" scope="prototype">
<property name="id" value="2"></property>
<property name="name" value="李四"></property>
<property name="password" value="654321"></property>
</bean>
</beans>
然后根据xml配置文件中的class路径创建对应的POJO实体类:User
User类中内容如下(为了节省篇幅,省略setter和getter方法):
public class User {
private Integer id;
private String name;
private String password;
public User() {
System.out.println("无参构造方法执行");
}
//setters和getters...
}
主角登场...
创建ClassPathXmlApplicationContext类:
先定义几个后面要用到的容器,这里我使用的是Map来存储对象:
package applicationContext;
public class ClassPathXmlApplicationContext {
/**存储单例对象容器*/
private Map<String, Object> singletonBeanFactory;
/**存储创建类定义对象的容器*/
private Map<String, Class<?>> beanDefinationFactory;
/**存储beanElement对象容器*/
private Map<String, Element> beanEleMap;
/**存储bean的scope属性容器*/
private Map<String, String> beanScopeMap;
}
定义有参的构造方法,在构造方法中初始化容器,并调用初始化方法:
/**有参的构造方法,在创建此类实例时需要指定xml文件路径*/
public ClassPathXmlApplicationContext(String xmlPath) {
//初始化容器
singletonBeanFactory = new ConcurrentHashMap<String, Object>();
beanDefinationFactory = new ConcurrentHashMap<String, Class<?>>();
beanEleMap = new ConcurrentHashMap<String, Element>();
beanScopeMap = new ConcurrentHashMap<String, String>();
//调用初始化方法
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元素,elements方法会返回元素的集合
List<Element> beanElements = rootElement.elements("bean");
//遍历元素集合
for (Element beanEle : beanElements) {
//获取bean的id值,该值用于作为key存储于Map集合中
String beanId = beanEle.attributeValue("id");
//将beanElement对象存入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");
//利用反射技术根据获得的beanClass路径得到类定义对象
Class<?> cls = Class.forName(beanClassPath);
//如果反射获取的类定义对象不为null,则放入工厂中方便创建其实例对象
if(cls!=null){
beanDefinationFactory.put(beanId, cls);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
以上为创建ClassPathXmlApplicationContext对象时自动启用的初始化方法,要想获取对象则需要使用getBean方法,代码如下:
/**
* 根据传入的bean的id值获取容器中的对象,类型为Object
*/
public Object getBean(String beanId){
//根据传入beanId获取类对象
Class<?> cls = beanDefinationFactory.get(beanId);
//根据id获取该bean对象的element元素对象
Element beanEle = beanEleMap.get(beanId);
//获取存在map中的bean元素的scope属性值
String scope = beanScopeMap.get(beanId);
Object obj = null;
try {
//如果scope等于singleton,创建单例对象
if("singleton".equals(scope) || null == scope){
//判断容器中是否已有该对象的实例,如果没有,创建一个实例对象放到容器中
if(singletonBeanFactory.get(beanId)==null){
Object instance = cls.newInstance();
singletonBeanFactory.put(beanId,instance);
}
//根据beanId获取对象
obj = singletonBeanFactory.get(beanId);
}
//如果scope等于prototype,则创建并返回多例对象
if("prototype".equals(scope)){
obj = cls.newInstance();
}
setFieldValues(beanId, beanEle, scope, cls, obj);
return obj;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//暂不支持其它类型,若不是以上两种类型或遭遇异常,返回null
return null;
}
/**
* 此为重载方法,在根据传入的bean的id值获取容器中的对象的同时,还可以自动转换类型,
* 返回指定的类型,在调用该方法时省去强转的步骤,传入时第二个参数为指定的类型,
* 方法实现同上一个方法,只是在返回对象前加了类型强转
*/
public <T>T getBean(String beanId,Class<T> c){
return (T)getBean(beanId);
}
在以上的getBean方法中,调用了setFieldValues方法,该方法代码如下:
/**
* 该方法用于为对象设置成员属性值
* @param beanEle bean所对应的element对象
* @param beanId bean元素的id属性
* @param beanScope bean元素的scope属性
* @param cls 类对象
* @param obj 要为其成员属性赋值的实例对象
*/
private void setFieldValues(String beanId,Element beanEle,String beanScope,Class<?> cls,Object obj) {
try {
//获取每个bean元素下的所有property元素,该元素用于给属性赋值
List<Element> propEles = beanEle.elements("property");
//如果property元素集合为null,调用putInMap方法将对象放进Map中
if(propEles==null){
return;
}
//遍历property元素集合
for (Element propEle : propEles) {
//获取每个元素的name属性值和value属性值
String fieldName = propEle.attributeValue("name");
String fieldValue = propEle.attributeValue("value");
//利用反射技术根据name属性值获得类的成员属性
Field field = cls.getDeclaredField(fieldName);
//将该属性设置为可访问(防止成员属性被私有化导致访问失败)
field.setAccessible(true);
//获取成员属性的类型名称,若非字符串类型,则需要做相应转换
String fieldTypeName = field.getType().getName();
//判断该成员属性是否为int或Integer类型
if("int".equals(fieldTypeName) || "java.lang.Integer".equals(fieldTypeName)){
//转换为int类型并为该成员属性赋值
int intFieldValue = Integer.parseInt(fieldValue);
field.set(obj, intFieldValue);
}
//判断该成员属性是否为String类型
if("java.lang.String".equals(fieldTypeName)){
//为该成员属性赋值
field.set(obj, fieldValue);
}
//此处省略其它类型的判断......道理相同!
}
} catch (Exception e) {
e.printStackTrace();
}
}
以上是获取单例或多例对象时需要调用的getBean方法的全部内容。当调用者使用完容器之后,自然还需要关闭容器释放资源,因此还需要有一个destroy方法:
/**
* 销毁方法,用于释放资源
*/
public void destroy(){
singletonBeanFactory.clear();
singletonBeanFactory = null;
beanDefinationFactory.clear();
beanDefinationFactory = null;
beanEleMap.clear();
beanEleMap = null;
beanScopeMap.clear();
beanScopeMap = null;
}
至此,ClassPathXmlApplicationContext类中的内容全部完成,可以写测试类进行测试:
测试类内容如下,这里我就简单写main方法进行测试:
public class springIocTest {
public static void main(String[] args) {
//创建ClassPathXmlApplicationContext对象
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("user.xml");
//使用手动强转的方式获取单例的User对象
User user1_1 = (User) ctx.getBean("user1");
System.out.println("单例user1_1:"+user1_1);
//使用传入类对象的方式获取单例的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);
}
}
控制台打印输出结果:
如果在User类型加入toString方法:
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", password=" + password + "]";
}
再次运行程序,控制台输出结果如下:
可以看到对象所定义的属性值也在创建时成功赋值了。