(十一)手写简单的Spring框架
创始人
2024-02-06 19:17:01

文章目录

  • 第一步:搭建环境
  • 第二步:准备好要管理的Bean
  • 第三步:准备myspring.xml配置文件
  • 第四步:编写MyApplicationContext接口
  • 第五步:编写MyClassPathXmlApplicationContext
  • 第六步:采用Map集合存储Bean
  • 第七步:解析配置文件实例化所有Bean
  • 第八步:给Bean的属性赋值
  • 测试MySpring框架

上一篇:(十)Spring之回顾反射机制

Spring IoC容器的实现原理:工厂模式 + 解析XML + 反射机制。

第一步:搭建环境

采用Maven方式新建模块:取名MySpring
引入dom4j和jaxen的依赖,因为要使用它解析XML文件,还有junit依赖和log4j2的依赖,方便测试。
log4j2.xml文件放到类路径下。

    org.dom4jdom4j2.1.3jaxenjaxen1.2.0junitjunit4.13.2testorg.apache.logging.log4jlog4j-core2.19.0org.apache.logging.log4jlog4j-slf4j2-impl2.19.0

第二步:准备好要管理的Bean

准备好我们要管理的Bean,这些Bean只是为了方便测试,也就是说这些Bean在将来开发完框架之后是要删除。
创建User类:

public class User {private String name;private int age;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}

创建UserDao类:

public class UserDao {public void insert(){System.out.println("正在新增用户信息。。。");}
}

创建UserDao类:

public class UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void save(){userDao.insert();}
}

第三步:准备myspring.xml配置文件

注意这个配置文件也是框架使用者提供,将来开发完框架之后是要删除。
使用value给简单属性赋值。使用ref给非简单属性赋值。




第四步:编写MyApplicationContext接口

使用Spring的时候我们第一个要做的是获取Spring容器对象,是一个ApplicationContext接口,所以我们也提供这样一个接口,这个接口将来是要通过getBean方法获取Bean的,所以我们提供这样一个方法。

/*** MySpring框架应用上下文*/
public interface MyApplicationContext {/*** 根据Bean的名称获取对应的Bean对象* @param beanName myspring配置文件中bean标签的id* @return 对应的Bean单例对象*/Object getBean(String beanName);
}

第五步:编写MyClassPathXmlApplicationContext

使用Spring的时候获取Spring容器对象是通过new ClassPathXmlApplicationContext得到的,这个类ApplicationContext接口的实现类。要加载类路径下的Spring配置文件。
所以我们创建MyClassPathXmlApplicationContext类继承MyApplicationContext接口。


public class MyClassPathXmlApplicationContext implements MyApplicationContext{@Overridepublic Object getBean(String beanName) {return null;}
}

第六步:采用Map集合存储Bean

Spring是提供三级缓存存储不同时期的Bean,我们不用那么麻烦,采用一个Map集合存储Bean实例。Map集合的key存储bean的id,value存储Bean实例。Map
在MyClassPathXmlApplicationContext类中添加Map属性。
并且在MyClassPathXmlApplicationContext类中添加构造方法,该构造方法的参数接收myspring.xml文件。
同时实现getBean方法。

public class ClassPathXmlApplicationContext implements ApplicationContext{/*** 存储bean的Map集合*/private Map singletonObjects = new HashMap<>();/*** 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。* @param resource 配置文件路径(要求在类路径当中)*/public ClassPathXmlApplicationContext(String configLocation) {}@Overridepublic Object getBean(String beanName) {return singletonObjects.get(beanName);}
}

第七步:解析配置文件实例化所有Bean

在MyClassPathXmlApplicationContext的构造方法中解析配置文件,获取所有bean的类名,通过反射机制调用无参数构造方法创建Bean。并且将Bean对象存放到Map集合中。


public MyClassPathXmlApplicationContext(String configLocation) {try {//1.使用Dom4j创建reader对象SAXReader reader = new SAXReader();//2.通过类加载器获取SqlMapper的配置文件输入流InputStream myspring_stream = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);//3.读XML文件,返回一个文档对象documentDocument document = reader.read(myspring_stream);//4.对标签进行处理//(1)获取所有的bean标签List nodes = document.selectNodes("//bean");//(2)对bean标签进行遍历,创建对象曝光到Map集合中nodes.forEach(node -> {try {//向下转型,因为Element有更多的方法 Element 继承了 Branch 又继承了 Node ,Node方法比较少。Element beanElt = (Element) node;//获取Bean的idString id = beanElt.attributeValue("id");//获取class属性值String className = beanElt.attributeValue("class");logger.info("beanName:" + id);//使用log4j2记录logger.info("beanClassName:" + className);//通过反射机制创建对象,将其放到Map集合中Class aClass = Class.forName(className);Constructor defaultCon = aClass.getDeclaredConstructor();Object beanObj = defaultCon.newInstance();//将bean曝光,加入Map集合singletonObjects.put(id,beanObj);logger.info(singletonObjects.toString());} catch (Exception e) {e.printStackTrace();}});} catch (Exception e) {e.printStackTrace();}
}

第八步:给Bean的属性赋值

通过反射机制调用set方法,给Bean的属性赋值。
继续在MyClassPathXmlApplicationContext构造方法中编写代码。

	//(3)再次对bean标签进行遍历,这次主要对bean进行赋值nodes.forEach(node -> {try {//向下转型,因为Element有更多的方法 Element 继承了 Branch 又继承了 Node ,Node方法比较少。Element beanElt = (Element) node;//获取Bean的idString id = beanElt.attributeValue("id");//获取class属性值String className = beanElt.attributeValue("class");//获取ClassClass aClass = Class.forName(className);//获取该bean标签下所有的属性property标签List propertys = beanElt.elements("property");//遍历property标签propertys.forEach(property ->{try {//获取属性名String propertyName = property.attributeValue("name");logger.info(propertyName);//获取set方法名String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);//获取属性类型Field field = aClass.getDeclaredField(propertyName);//获取set方法Method setMethod = aClass.getDeclaredMethod(setMethodName,field.getType());//调用set方法//这里的具体值有可能是value或者ref,需要进行判断String value = property.attributeValue("value");String ref = property.attributeValue("ref");Object actualValue = null;if (value != null) {//简单类型//这里比较麻烦,因为获取到的value的是字符串,需要根据属性类型转换成对应的类型//为了程序简单化,我们定义8种基本类型以及对应的包装类,加上String为简单类型//首先获取属性类型名的简称String propertySimpleName = field.getType().getSimpleName();switch (propertySimpleName){case "byte":actualValue = Byte.parseByte(value);break;case "short":actualValue = Short.parseShort(value);break;case "int":actualValue = Integer.parseInt(value);break;case "long":actualValue = Long.parseLong(value);break;case "float":actualValue = Float.parseFloat(value);break;case "double":actualValue = Double.parseDouble(value);break;case "boolean":actualValue = Boolean.parseBoolean(value);break;case "char":actualValue = value.charAt(0);break;case "Byte":actualValue = Byte.valueOf(value);break;case "Short":actualValue = Short.valueOf(value);break;case "Integer":actualValue = Integer.valueOf(value);break;case "Long":actualValue = Long.valueOf(value);break;case "Float":actualValue = Float.valueOf(value);break;case "Double":actualValue = Double.valueOf(value);break;case "Boolean":actualValue = Boolean.valueOf(value);break;case "Character":actualValue = Character.valueOf(value.charAt(0));break;case "String":actualValue = value;break;}setMethod.invoke(singletonObjects.get(id), actualValue);}if (ref != null) {//非简单类型setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref));}} catch (Exception e) {e.printStackTrace();}});} catch (Exception e) {e.printStackTrace();}});

测试MySpring框架

    @Testpublic void testMySpring(){MyApplicationContext myApplicationContext = new MyClassPathXmlApplicationContext("myspring.xml");Object user = myApplicationContext.getBean("user");System.out.println(user);UserService userService = ((UserService) myApplicationContext.getBean("userService"));userService.save();}

测试完毕,运行成功
请添加图片描述

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...