zoukankan      html  css  js  c++  java
  • 源码学习之路--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的操作。

  • 相关阅读:
    大数据学习之路(持续更新中...)
    数据挖掘之初级
    用户画像
    机器挖掘之经典算法
    算法时间复杂度
    01-IOS-越狱指南
    gradle 内容学习参考
    python 获取文件路径相关
    第一行代码阅读笔记
    使用同步锁防止多进程的并发操作
  • 原文地址:https://www.cnblogs.com/hg1205/p/15117563.html
Copyright © 2011-2022 走看看