Spring-手写模拟基于xml的 spring容器 管理bean对象。一文告诉你 有多简单。

可能现在大部分人写项目都接触不到xml这种方式了。毕竟有点out了。。但是作为学习 还是有值得进一步挖掘的意义。

本文只模拟 属性注入的两种方式 1.set注入 2.构造器注入 如何被spring管理到容器中。再通过getBean()的方式去获取对象。而不是耦合度太高的 new xxx()。后续的一些比如包扫描 @Autowired等DI注入。以后在慢慢弥补。或许也会开新的篇章。单独模拟内部源码。让大家更好的理解 spring底层。

先大致讲一下 整个思路吧。因为后面 我不知道该怎么介绍代码细节。好像除了贴图也只能贴图。或者让你们看代码自行理解细节。因为我代码里 注释写的还算详细。就不重复啰嗦。

github 下载源码路径 https://github.com/hzprivate/frame-topic   点到 mySpring项目即可。或者也可看贴图代码。下面都会展示。

代码思路:

1.困难点:

①如何读取 spring.xml文件 筛选有效数据?

   通过 dom4j 去读取xml,什么?dom4j没用过不会? 好吧 其实我也不会。看dom4j官方文档 https://dom4j.github.io/  一目了然 简单一批。没必要百度别人的例子。要学会看官方文档。然后后面猜一猜也知道怎么写了。

//获取xml中的<bean>标签 id属性 value值
String id = firstElement.attribute("id").getValue();
//获取xml中<bean>标签 class属性 全限定路径名称
String className = firstElement.attribute("class").getValue();

通过这两种就可以获取 你们xml中定义的bean标签 中的 id属性 和 class属性的值。

 

②如何反射生成对象然后放入容器?

spring容器就是个大map

<bean>标签中定义的class属性值通过 Element对象 的attribute("class")方法 获取, 值为该类的全限定路径名称。然后利用全限定路径名称 生成 class对象。然后class.newInstance() 实例化即可.通过键值对map.put("属性名","反射生成的对象")例如 map.put("userdao",new UserDao()) 放入spring map容器中。 通过 代码里 getBean("userDao")从 map中 获取 对象即可。到此已经实现了 如何将对象放入容器去管理。

//通过全限定路径名称反射生成class对象
Class<?> clazz = Class.forName(className);

③如何实现对象之间依赖的注入 。交给spring管理时 例如UserService中依赖了UserDao.如何对象关联调用时不报UserDao空指针?

首先要双重判断  1.类中是否含有属性。2.xml是否含有 property或 constructor-arg标签  。有的话才能证明 有对象依赖注入(构造器注入或set注入)

1.set注入时  通过 class对象获取所有类属性名称  即clazz.getDeclaredFields()。判断Field的属性名是否和xml中 <property>定义的 name值是否相同。相同则意味着 确实可以通过 <property> 关联到set去注入 因为类中也有定义属性。然后在通过<property> 中定义的ref属性 获取spring容器 map对象中已经反射生成的依赖对象。 最后通过 下面两行关键代码。就可实现 属性的set注入

declaredField.setAccessible(true);
declaredField.set(o,ref); 

 

                    for (Field declaredField : declaredFields) {
                            String name = secondElementProperty.attribute("name").getValue();
                            if(name.equals(declaredField.getName())){
                                //属性名和xml中name定义名称相同
                                String refClazzName = secondElementProperty.attribute("ref").getValue();
                                Object ref = reslutMap.get(refClazzName);
                                if(ref != null){
                                    //map中含有 该ref依赖对象名称
                                    //set方法注入
                                    declaredField.setAccessible(true);
                                    declaredField.set(o,ref);
                                }
                            }

                        }

2.构造器注入时 同样获取<constructor-arg>标签定义的 name属性值和Field的属性名是否相同。相同则意味着 确实可以通过 <constructor-arg>关联到构造器去注入。然后在通过<constructor-arg>中定义的ref属性值 获取spring容器 map对象中已经存放的依赖对象。 最后通过class对象获取指定构造器,利用构造器 newInstance() 反射生成对象。然后放入map即可。

此处要区别 set注入 和 构造器注入时 反射生成对象 。 set可以直接通过 class.newInstance()反射生成对象 。此处利用默认构造器。而一旦利用 构造器注入就不用此种方式去反射生成对象。要先获取有参构造器、利用有参构造器反射生成对象。不然会报错。

//constructor 方法注入
//因为代码里只有一个构造器 所以[0] 获取第一个
Constructor<?> declaredConstructor = clazz.getDeclaredConstructors()[0];
o = declaredConstructor.newInstance(ref);
                                    

 

代码思路到此处讲解完。具体的可直接看代码。不想看代码可看下面贴图。

 

 

1.构建好maven项目 如下结构

2.pom文件中添加maven依赖 (为了通过idea生成spring.xml文件 )

    <!-- 毫无作用 只是为了 通过idea可以生成 spring.xml文件-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.1.RELEASE</version>
    </dependency>

    <!--解析xml 工具-->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>

 

3.在resources文件夹下生成 spring.xml文件

4.新建如下四个接口以及实现类(后续测试使用)

5.spring.xml 配置对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.hz.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.hz.service.UserServiceImpl">
        <!--property setter注入  name 是传入的userDao 变量名称 ref是 属性方法setUserDao名称-->
        <!--<property name="userDao" ref="userDao"></property>-->

        <!--constructor-arg 构造器注入-->
        <constructor-arg name="userDao" ref="userDao"></constructor-arg>
    </bean>

</beans>

6.核心代码 来了 BeanFactory文件

package com.hz;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author hz
 * @create 2020-09-21
 */
public class BeanFactory {
    //解析xml
    public Map<String,Object> parseXml(String xml){
        SAXReader reader = new SAXReader();
        try {
            //定义spring map容器(包含所有bean对象)
            Map<String,Object> reslutMap = new HashMap(16);
            //获取项目工程中 spring.xml文件路径
            String springXmlUrl = this.getClass().getResource(xml).getFile();
            File file = new File(springXmlUrl);
            //通过file 形式读取xml文件 生成Document对象
            Document document = reader.read(file);
            //获取<beans>标签对象
            Element rootElement = document.getRootElement();

            for(Iterator<Element> iterator = rootElement.elementIterator();iterator.hasNext();){
                Object o = null;
                //获取 <bean> 一级标签对象
                Element firstElement = iterator.next();
                //获取<bean> 标签中的 id属性值 和 class属性值
                String id = firstElement.attribute("id").getValue();
                //获取全限定路径名称
                String className = firstElement.attribute("class").getValue();
                //通过全限定路径名称反射生成class对象
                Class<?> clazz = Class.forName(className);

                //对象依赖注入 思路判断
                //1.类中是否含有属性。2.xml是否含有 property或 constructor-arg标签
                Field[] declaredFields = clazz.getDeclaredFields();
                if(declaredFields != null && declaredFields.length>0){
                    //获取二级标签 <property>对象
                    Element secondElementProperty = firstElement.element("property");
                    // setter注入  判断
                    if(secondElementProperty != null){
                        o = clazz.newInstance();
                        for (Field declaredField : declaredFields) {
                            String name = secondElementProperty.attribute("name").getValue();
                            if(name.equals(declaredField.getName())){
                                //属性名和xml中name定义名称相同
                                String refClazzName = secondElementProperty.attribute("ref").getValue();
                                Object ref = reslutMap.get(refClazzName);
                                if(ref != null){
                                    //map中含有 该ref依赖对象名称
                                    //set方法注入
                                    declaredField.setAccessible(true);
                                    declaredField.set(o,ref);
                                }
                            }

                        }

                    }
                    //获取二级标签 <constructor-arg>对象
                    Element secondElementConst = firstElement.element("constructor-arg");
                    //构造器注入判断
                    if(secondElementConst != null){
                        for (Field declaredField : declaredFields) {
                            String name = secondElementConst.attribute("name").getValue();
                            if(name.equals(declaredField.getName())){
                                //属性名和xml中name定义名称相同
                                String refClazzName = secondElementConst.attribute("ref").getValue();
                                Object ref = reslutMap.get(refClazzName);
                                if(ref != null){
                                    //map中含有 该ref依赖对象
                                    //constructor 方法注入
                                    //因为代码里只有一个构造器 所以[0] 获取第一个
                                    Constructor<?> declaredConstructor = clazz.getDeclaredConstructors()[0];
                                    o = declaredConstructor.newInstance(ref);
                                }
                            }

                        }

                    }


                }else{
                    //无依赖注入  直接反射生成对象
                    o = clazz.newInstance();
                }

                reslutMap.put(id,o);
            }
            return reslutMap;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    //获取bean对象
    public Object getBean(String name){
        Map<String, Object> map = parseXml("/spring.xml");
        Object o = map.get(name);
        return o;
    }
}

 

7.test测试

package com.hz.test;

import com.hz.BeanFactory;
import com.hz.dao.UserDao;
import com.hz.service.UserService;
import com.hz.service.UserServiceImpl;

/**
 * @author hz
 * @create 2020-09-21
 */
public class test {
    public static void main(String[] args) {
        BeanFactory beanFactory = new BeanFactory();

        //垃圾版 试验 人为set方法注入 (只实现了无依赖的bean对象生成)
//        UserService userService = new UserServiceImpl();
//        UserDao userDao =(UserDao) beanFactory.getBean("userDao");
//        ((UserServiceImpl) userService).setUserDao(userDao);
//        userService.query();

        //进阶版 试验 xml set注入 或 构造器注入
        UserService userService =(UserService) beanFactory.getBean("userService");
        userService.query();
        
    }
}

8.测试结果

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值