• (十一)手写简单的Spring框架



    Spring学习目录

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

    下一篇:(十二)Spring IoC注解式开发

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

    第一步:搭建环境

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

        <!--dem4j依赖-->
        <dependency>
          <groupId>org.dom4j</groupId>
          <artifactId>dom4j</artifactId>
          <version>2.1.3</version>
        </dependency>
        <!--jaxen依赖-->
        <dependency>
          <groupId>jaxen</groupId>
          <artifactId>jaxen</artifactId>
          <version>1.2.0</version>
        </dependency>
    
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.13.2</version>
          <scope>test</scope>
        </dependency>
        <!--log4j2的依赖-->
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <version>2.19.0</version>
        </dependency>
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-slf4j2-impl</artifactId>
          <version>2.19.0</version>
        </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    第二步:准备好要管理的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;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    创建UserDao类:

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

    创建UserDao类:

    public class UserService {
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void save(){
            userDao.insert();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
        <!--这个配置文件也是框架使用者提供-->
        <bean id="user" class="com.myspring.bean.User">
            <property name="name" value="张三"/>
            <property name="age" value="20"/>
        </bean>
    
        <bean id="userDao" class="com.myspring.bean.UserDao">
    
        </bean>
        <bean id="userService" class="com.myspring.bean.UserService">
            <property name="userDao" ref="userDao"/>
        </bean>
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第四步:编写MyApplicationContext接口

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

    /**
     * MySpring框架应用上下文
     */
    public interface MyApplicationContext {
        /**
         * 根据Bean的名称获取对应的Bean对象
         * @param beanName myspring配置文件中bean标签的id
         * @return 对应的Bean单例对象
         */
        Object getBean(String beanName);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第五步:编写MyClassPathXmlApplicationContext

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

    
    public class MyClassPathXmlApplicationContext implements MyApplicationContext{
        @Override
        public Object getBean(String beanName) {
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    第六步:采用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<String,Object> singletonObjects = new HashMap<>();
    
        /**
         * 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。
         * @param resource 配置文件路径(要求在类路径当中)
         */
        public ClassPathXmlApplicationContext(String configLocation) {
    
        }
    
        @Override
        public Object getBean(String beanName) {
            return singletonObjects.get(beanName);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    第七步:解析配置文件实例化所有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文件,返回一个文档对象document
                Document document = reader.read(myspring_stream);
                //4.对标签进行处理
                //(1)获取所有的bean标签
                List<Node> nodes = document.selectNodes("//bean");
                //(2)对bean标签进行遍历,创建对象曝光到Map集合中
            	nodes.forEach(node -> {
                    try {
                        //向下转型,因为Element有更多的方法 Element 继承了 Branch 又继承了 Node ,Node方法比较少。
                        Element beanElt = (Element) node;
                        //获取Bean的id
                        String 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    第八步:给Bean的属性赋值

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

    	//(3)再次对bean标签进行遍历,这次主要对bean进行赋值
                nodes.forEach(node -> {
                    try {
                        //向下转型,因为Element有更多的方法 Element 继承了 Branch 又继承了 Node ,Node方法比较少。
                        Element beanElt = (Element) node;
                        //获取Bean的id
                        String id = beanElt.attributeValue("id");
                        //获取class属性值
                        String className = beanElt.attributeValue("class");
                        //获取Class
                        Class<?> aClass = Class.forName(className);
                        //获取该bean标签下所有的属性property标签
                        List<Element> 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();
                    }
                });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    测试MySpring框架

        @Test
        public 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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

  • 相关阅读:
    展锐平台音频框架
    动态内存(进阶四)
    【从零开始学习 SystemVerilog】11.2、SystemVerilog 断言—— Immediate Assertions(立即断言)
    说下 RESTful API 使用的几个方法
    Go-Excelize API源码阅读(三十五)——SetSheetCol
    P5766最优联通子集题解
    一次简单的 JVM 调优,学会拿去写到简历里
    mfc140u.dll丢失的详细解决方法,最详细修复mfc140u.dll丢失的办法分享
    【PGGAN】3、代码解析
    基于微信小程序的校运会管理系统设计与实现-计算机毕业设计源码+LW文档
  • 原文地址:https://blog.csdn.net/weixin_45832694/article/details/127969562