源码学习之路--Spring-ioc

这篇文章我将一步一步实现一个简单的spring框架

首先,我先简单的介绍下Spring的核心思想,IOC和AOP。

本文主要实现的是ioc

什么是IOC?

IoC Inversion of Control (控制反转/反转控制),它是一个思想,而不是一个技术实现,对于传统开发,比如类A依赖于类B,往往会在类A中new一个B的对象。
IoC思想下开发方式:我们不用自己去new对象了,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问IoC容器要即可。

控制:其实就是对象的创建权利;反转,就是把对象的创建权利交给了外部环境。 

IoC解决了什么问题?

IoC解决对象之间的耦合问题

当类B依赖于类A时,我们只让A来实现一个接口Interface,而B类中,我们只引用这个接口Interface(我们的容器将这个接口引用指向A的实现),当A类的业务功能有变动时,我们只需要修改A类的实现,

或者重新创建一个C类来实现这个接口Interface(我们的容器将这个接口引用指向C的实现),而我们的B类是不需要进行修改的。

IoC和DI的区别

DI:Dependancy Injection(依赖注入)

其实IOC与DI描述的是同一个事情,IOC是站在对象的角度,对象实例化及其管理的权利反转给了容器;DI是站在容器的角度,容器会把对象A依赖的对象B送到对象A中。

 

什么是AOP

AOP: Aspect oriented Programming 面向切面编程/面向方面编程

首先我们要知道什么叫横切逻辑代码,在多个纵向流程中出现的相同子流程代码就是横切逻辑代码。简单的说就是在不同的方法中存在重复代码,比如事务控制,权限校验,日志记录等。

横切逻辑代码存在什么问题:
       1.横切代码重复问题
       2.横切逻辑代码和业务代码混杂在一起,代码臃肿,维护不方便

而AOP解决的就是这些问题,它将横切逻辑代码和业务逻辑代码分离。它可以在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

为什么叫做面向切面编程

    切:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑
    面:横切逻辑代码往往要影响的是很多个方法,每一个方法都如同一个点,多个点构成面,有一个面的概念在里面

接下来,开始编写一个简单的SpringDemo

与Mybatis相同,我们要创建一个beans.xml来记录我们的依赖关系,然后通过工厂去进行解析创建并存储对象,供我们后续的调用(这一步其实就是实现一个IOC的功能)

创建beans.xml

<?xml version="1.0" encoding="UTF-8" ?>

<beans>
    
    <bean id="accountDao" class="com.hg.dao.impl.JdbcAccountDaoImpl">
        <property name="ConnectionUtils" ref="connectionUtils"/>
    </bean>
    <bean id="transferService" class="com.hg.service.impl.TransferServiceImpl">
       
        <property name="AccountDao" ref="accountDao"></property>
    </bean>

    <bean id="connectionUtils" class="com.hg.utils.ConnectionUtils"></bean>

    
    <bean id="transactionManager" class="com.hg.utils.TransactionManager">
        <property name="ConnectionUtils" ref="connectionUtils"/>
    </bean>

    <bean id="proxyFactory" class="com.hg.factory.ProxyFactory">
        <property name="TransactionManager" ref="transactionManager"/>
    </bean>
</beans>

其中,每个bean标签都代表一个对象,id做为唯一标识来定位一个bean,class是用来标识我们将要创建哪一个类,也就是类的类路径,方便后面通过反射去创建这个类;而property代表对象中的一个属性,property中的 name是为了去创建对象的时候,通过set方法将属性注入,ref用来

解析属性需要哪一个具体的bean来赋值,这样我们就可以通过一个工厂类来进行beans.xml的解析。

创建BeanFactory

public class BeanFactory {

    /**
     * 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
     * 任务二:对外提供获取实例对象的接口(根据id获取)
     */

    private static Map<String,Object> map = new HashMap<>();  // 存储对象


    static {
        // 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (int i = 0; i < beanList.size(); i++) {
                Element element =  beanList.get(i);
                // 处理每个bean元素,获取到该元素的id 和 class 属性
                String id = element.attributeValue("id");        // accountDao
                String clazz = element.attributeValue("class");  // com.hg.dao.impl.JdbcAccountDaoImpl
                // 通过反射技术实例化对象
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();  // 实例化之后的对象

                // 存储到map中待用
                map.put(id,o);

            }

            // 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值
            // 有property子元素的bean就有传值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            // 解析property,获取父元素
            for (int i = 0; i < propertyList.size(); i++) {
                Element element =  propertyList.get(i);   //<property name="AccountDao" ref="accountDao"></property>
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                // 找到当前需要被处理依赖关系的bean
                Element parent = element.getParent();

                // 调用父元素对象的反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);
                // 遍历父对象中的所有方法,找到"set" + name
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    if(method.getName().equalsIgnoreCase("set" + name)) {  // 该方法就是 setAccountDao(AccountDao accountDao)
                        method.invoke(parentObject,map.get(ref));
                    }
                }

                // 把处理之后的parentObject重新放到map中
                map.put(parentId,parentObject);

            }


        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }


    // 任务二:对外提供获取实例对象的接口(根据id获取)
    public static  Object getBean(String id) {
        return map.get(id);
    }

}

 

首先我们读取beans.xml流,然后通过Dom4j进行解析,获取所有的bean标签,然后通过反射(bean标签中的class获取到类的类路径)进行实例化然后将对象存入到map中,key就是我们bean标签中的id,值就是我们实例化的对象。

后面我们可以通过getBean(id)来获取到对象。接下来就要解析property属性,并给属性赋值。

  先获取到所有的property标签,解析property获得name 和ref 的值,然后得到property的父标签,从而获得这个属性所属的类(比如是类A),然后遍历类的所有方法,找出set+name的那个set方法,通过反射将ref指向的类通过set方法放入到类A,然后再把类A放回到map中,这样基本上一个IOC的功能就完成了,通过一个测试来试验一下。

 

 结果,我们获取到了我们的类,而整个过程我们也没有进行new的操作。

 

 

 

posted @ 2021-08-09 21:25  蜜桃乌龙茶  阅读(38)  评论(0编辑  收藏  举报