IOC

IoC,Inversion of Control,控制反转。控制反转是一种通过描述(在java中可以使XML或者注解)并通过第三方去获取特定对象的方式。简单地说就是将对象由虚拟机主动创建变为从IoC容器中获取,它是面向对象编程的一种思想,主要作用是降低开发难度、对模块解耦、有利于测试,

Spring IoC容器的设计

Spring IoC容器的设计主要是基于BeanFactory和ApplicationContext两个接口。BeanFactory是Spring IoC所定义的最底层的接口,ApplicationContext是其高级接口之一,是最常用的Spring IoC容器。Spring IoC容器设计图如下:

Spring IoC容器的初始化和依赖注入

1、Bean的定义和初始化

Bean的定义和初始化在Spring IoC中是两个步骤,它是先定义然后初始化和依赖注入的。Bean的定义分为3步:

Resource定位,这步是Spring IoC根据开发者的配置,进行资源定位。定位的内容由开发者提供。

BeanDefinition的载入,这个时候是将Resource定位的信息保存到Bean定义(BeanDefinition)中,此时不会创建Bean的实例。

BeanDefinition的注册,这个过程是将BeanDefinition的信息发布到Spring IoC容器中,此时仍没有对应的Bean的实例创建。

做完这三步,Bean就在Spring IoC容器中定义了,还没有初始化,更没有完成依赖注入。对于初始化和依赖注入,Spring Bean还有一个配置项——lazy-init,其含义是是否延迟初始化Spring Bean,默认值false,也就是默认自动初始化Bean,如果设置成true,那么Bean的初始化和依赖注入将在执行getBean方法时进行。

2、依赖注入

Spring实现IoC主要采用依赖注入,一般而言,依赖注入分为以下三种方式:

构造器注入:依赖于构造方法实现,构造方法可以使有参的和无参的。Spring内部采用反射的方式调用构造方法实现注入。XML方式对应c标签。

设值注入:设值注入依赖setter方法,是Spring最主流的注入方式。灵活且可读性高。其原理也是通过反射实现,设值注入依赖于无参构造器,当bean类声明了带参构造器时必须同时声明无参构造器。XML方式对应p标签。

接口注入:适用于来自于外界的资源,比如数据库连接资源可以在Tomcat下配置,然后通过JNDI的形式去获取它。此时可以使用借口注入。

IOC的简单实现

最简单的 IOC 容器只需4步即可实现,如下:

  1. 加载 xml 配置文件,遍历其中的标签

  2. 获取标签中的id,加载class属性对应的类,并创建 bean

  3. 遍历标签中的标签,获取属性值,并将属性值填充到 bean 中

  4. 将 bean 注册到 bean 容器中

要注入的Bean

public class Car {
    private String name;
    private String length;
    private String width;
    private String height;
    private Wheel wheel;
    
    // get set...
}

public class Wheel {
    private String brand;
    private String specification;
}

容器实现类:

public class SimpleIOC {
    private Map<String, Object> beanMap = new HashMap<>();

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

    public Object getBean(String name) {
        Object bean = beanMap.get(name);
        if (bean == null) {
            throw new IllegalArgumentException("there is no bean with name " + name);
        }

        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 (ClassNotFoundException e) {
                    e.printStackTrace();
                    return;
                }

                // 创建 bean
                Object bean = beanClass.newInstance();

                // 遍历 <property> 标签
                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) {
                            // 将属性值填充到相关字段中
                            declaredField.set(bean, value);
                        } else {
                            String ref = propertyElement.getAttribute("ref");
                            if (ref == null || ref.length() == 0) {
                                throw new IllegalArgumentException("ref config error");
                            }

                            // 将引用填充到相关字段中
                            declaredField.set(bean, getBean(ref));
                        }

                        // 将 bean 注册到 bean 容器中
                        registerBean(id, bean);
                    }
                }
            }
        }
    }

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

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="wheel" class="spring.ioc.Wheel">
        <property name="brand" value="Michelin" />
        <property name="specification" value="265/60 R18" />
    </bean>

    <bean id="car" class="spring.ioc.Car">
        <property name="name" value="Mercedes Benz G 500" />
        <property name="length" value="4717mm" />
        <property name="width" value="1855mm" />
        <property name="height" value="1949mm" />
        <property name="wheel" ref="wheel" />
    </bean>
</beans>

测试方法

public class SimpleIOCTest {
    @Test
    public void getBean() throws Exception {
        System.out.println(SimpleIOC.class.getClassLoader().getResource(""));
        String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile();
        SimpleIOC bf = new SimpleIOC(location);
        Wheel wheel = (Wheel) bf.getBean("wheel");
        System.out.println(wheel);
        Car car = (Car) bf.getBean("car");
        System.out.println(car);
    }
}

参考

https://blog.csdn.net/weixin_41172473/article/details/81388198

http://www.tianxiaobo.com/2018/01/18/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E5%AE%9E%E7%8E%B0%E7%9A%84-Spring-IOC-%E5%92%8C-AOP-%E4%B8%8A%E7%AF%87/