自己动手写Spring-1-简单的IOC实现

该文是对Spring Framework中最核心的IOC简单实现。也是系列文章的开山之作。
IOC(Inversion of control,控制反转),是面向对象编程中的一种设计原则。但IOC说起来比较抽象,容易让人疑惑—“哪些方面的控制被反转了?”。Martin Fowler总结出是依赖对象的获得被反转了,因为大多数应用程序都是由两个或是更多的类通过彼此的合作来实现企业逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么这将导致代码高度耦合并且难以维护和调试。
下面是一个例子:
在这里插入图片描述
A妹买了器材,但要自己去和商家对接,亲自参与到器材的这个配置中;如果B哥也自己去买器材的话,同样会遇到好多麻烦。并且最后他们将会各自拥有一套器材,各自使用,各自保管,这样更费时,将大大压缩我们A妹的健身时间😣我们A妹怎么能更早成为靓女呢。
当然聪明的你在现实中肯定会给出答案:办卡呀,同学,来看看嘛,当洗澡卡也行呀。
在这里插入图片描述
这是一个典型反转例子,之前A妹,B哥各自依赖自己的器材,现在是都依赖于健身房的器材。健身房充当了IOC容器,负责采购、管理、维护器材,不再需要A妹和B给去关系,这样A妹和B哥就只要快快乐乐的关系怎么利用器材健身就好了。

大佬的理念在Spring Framework中体现的十分到位。那就是依赖注入(DI),创建应用对象之间协作关系的行为通常称为装配(wiring)。就是再bean的装配过程中注入所需要的外部资源(包括对象、资源、常量数据)。
Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:

  • 在XML中进行显式配置。
  • 在Java中进行显式配置。
  • 隐式的bean发现机制和自动装配。

乍看上去,提供三种可选的配置方案会使Spring变得复杂。每种配置
技术所提供的功能会有一些重叠,所以在特定的场景中,确定哪种技术最为合适就会变得有些困难。但是,不必紧张——在很多场景下,选择哪种方案很大程度上就是个人喜好的问题,你尽可以选择自己最喜欢的方式。
在本章我们将实践一个基于XML显式配置的简单spring。我们开启这一个简单的IOC吧:
项目目录结构:

在这里插入图片描述

  • Discus:这是一个实体类
/* description: 这是一个实体类,铁饼,及每个哑铃配置的铁饼
 * author:he sanshi
 * date:20.10.01
 */
public class Discus {
    private int  weight;
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
}

  • Dumbbell:这是一个实体类
/* description: 这是一个实体类,哑铃
 * author:he sanshi
 * date:20.10.01
 */
public class Dumbbell {
    private int dumbbell_id;
    private String brand;
    private Discus discus;
    public void setDiscus(Discus discus) {
        this.discus = discus;
    }
    public Discus getDiscus() {
        return discus;
    }
    public void setDumbbell_id(int dumbbell_id) {
        this.dumbbell_id = dumbbell_id;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public int getDumbbell_id() {
        return dumbbell_id;
    }
    public String getBrand() {
        return brand;
    }
}

  • ioc.xml:这是我们常见的xml配置bean
<beans>
    <bean id="discus" class="IOC.Discus">
        <property name="weight" value="10"></property>
    </bean>
    <bean id="dumbbell" class="IOC.Dumbbell">
        <property name="dumbbell_id" value="0001"></property>
        <property name="brand" value="nike"></property>
        <property name="discus" ref="discus"></property>
    </bean>
</beans>
  • SimpleIOC:为我们加载xml,并配置bean,生成bean容器

/* description: 这是一个IOC的实现类
 * 1.加载xml配置文件
 * 2.遍历其中的标签
 * 3.填充相应配置到bean中
 * 4.将bean注册到bean容器中
 *
 * author:he sanshi
 * date:20.10.01
 */

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class SimpleIOC {

    private Map<String, Object> beanMap = new HashMap<String, Object>();

    public SimpleIOC(String location) throws Exception {
        loadBeans(location);
    }

    private void registerBean(String id, Object bean) {
        beanMap.put(id, bean);
    }

    public Object getBean(String name) {
        Object bean = beanMap.get(name);
        if (null == bean) {
            throw new IllegalArgumentException(name + "not exists" );
        }
        return bean;
    }

    private void loadBeans(String location) throws Exception {
        // 加载xml配置文件
        InputStream inputStream = new FileInputStream(location);
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        Document doc = docBuilder.parse(inputStream);
        Element root = doc.getDocumentElement();
        NodeList nodes = root.getChildNodes();

        // 遍历<bean>标签
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                String id = ele.getAttribute("id");
                String className = ele.getAttribute("class");
                // 加载beanClass
                Class beanClass = null;
                try {
                    beanClass = Class.forName(className);
                } catch (Exception e) {
                    return ;
                }

                // 创建bean
                Object bean = beanClass.newInstance();
                // 遍历<propertiy>标签
                NodeList propertyNodes = ele.getElementsByTagName("property");
                for (int j = 0; j < propertyNodes.getLength(); j++) {
                    Node propertyNode = propertyNodes.item(j);
                    if (propertyNode instanceof Element) {
                        Element propertyElement = (Element) propertyNode;
                        String name = propertyElement.getAttribute("name");
                        String value = propertyElement.getAttribute("value");

                        // 利用反射将bean相关字段访问权限设为可访问
                        Field declaredField = bean.getClass().getDeclaredField(name);
                        declaredField.setAccessible(true);
                        if (value != null && value.length() > 0) {
                            // 将相关属性值填充到相关字段中
                            setFieldValue(declaredField, bean, value);
                        } else {
                            String ref = propertyElement.getAttribute("ref");
                            if (null == ref || 0 == ref.length()) {
                                throw new IllegalArgumentException("ref config error");
                            }
                            // 将引用填充到相关字段中,前提是该引用已加入beanmap容器中
                            declaredField.set(bean, getBean(ref));
                        }
                        // 将bean注册到bean容器中
                        registerBean(id, bean);
                    }
                }
            }
        }
    }
    /**
     *完成对属性类型判断,并填充对应类型的值
     *这里少了一个异常输出。
     */
    private static void setFieldValue(Field f, Object obj, String value)
            throws Exception {
        Class<?> type = f.getType();
        if(type == int.class) {
            f.setInt(obj, Integer.parseInt(value));
        } else if(type == byte.class) {
            f.setByte(obj, Byte.parseByte(value));
        } else if(type == short.class) {
            f.setShort(obj, Short.parseShort(value));
        } else if(type == long.class) {
            f.setLong(obj, Long.parseLong(value));
        } else if(type == float.class) {
            f.setFloat(obj, Float.parseFloat(value));
        } else if(type == double.class) {
            f.setDouble(obj, Double.parseDouble(value));
        } else if(type == char.class) {
            f.setChar(obj, value.charAt(0));
        } else if(type == boolean.class) {
            f.setBoolean(obj, Boolean.parseBoolean(value));
        } else if(type == String.class) {
            f.set(obj, value);
        } else {
            Constructor<?> ctor = type.getConstructor(
                    new Class[] { String.class });
            f.set(obj, ctor.newInstance(value));
        }
    }
}
  • SimpleIOCTest:这是一个test,完成我们上面代码的测试
public class SimpleIOCTest {
    public static void main(String[] args) throws Exception {
        //获取xml的绝对路径,通过与SimpleIOC的相对路径
        String location = SimpleIOC.class.getClassLoader().getResource("IOC/ioc.xml").getFile();
        //完成加载xml,和ioc容器初始化
        SimpleIOC bf = new SimpleIOC(location);
        //A、B两线程都去获取dumbbell
        Thread A=new Thread(()->{
             Dumbbell dumbbell=(Dumbbell)bf.getBean("dumbbell");
             System.out.println("A妹拿到了"+dumbbell.getBrand()+"哑铃");
             System.out.println("A妹使用"+dumbbell.getDiscus().getWeight()+"kg重锻炼了~");
        });
        Thread B=new Thread(()->{
            Dumbbell dumbbell=(Dumbbell)bf.getBean("dumbbell");
            System.out.println("B哥拿到了"+dumbbell.getBrand()+"哑铃");
            System.out.println("B哥使用"+dumbbell.getDiscus().getWeight()+"kg重锻炼了~");
        });
        A.start();
        B.start();
    }
}
  • 测试结果:

在这里插入图片描述

  • 最后总结:
    Dumbbell这个哑铃由Discus铁饼组成,所以Dumbbell具有Discus这个属性,且他们都用各自的属性,但是他们都由健身房(BeanMap)进行存放,这个装配过程也由SimpleIOC根据用户指定的方案(ioc.xml)进行装配、完成,我们的A妹和B哥两个线程只需要,直接获取就行,不需要自己等待和装配。
    因为这是一个简单的ioc,所以我们还有许多问题没有解决:
  • 1.现在的装配文件xml中Dumbbell、Discus的顺序不能进行交换
  • 2.没有异常处理机制😥
  • 3.bean是单例模式
    但对于一个简单的学习和了解IOC的案列来说这就已经满足了,接下来的章节我们将要入spring的注解。

代码地址:
https://github.com/sanshi-07/simple-spring/tree/master/simplr-spring-v1.0.0/src/ioc

评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页