手写一个最精简的IOC容器,实现对象之间的依赖关系

579 阅读5分钟

1.基本概念:

在这之前先介绍Spring及Spring IOC的基本概念:
Spring容器-一个IOC容器,用以管理程序中的各种对象以及他们之间的联系。

  • IOC(Inversion Of Contorl)控制反转 ——原本是由应用程序管理对象之间的关系,现在把控制权交给了容器,称之为控制反转。通俗来说就是原本我们创建对象需要使用new,但是我们现在不直接跟对象打交道了,而是在配置文件中写好需要一个什么样的对象,由容器代替我们去新建对象。

比如说,我创建这样一个类:

public class OrderDao {
    public void selcet(){
        System.out.println("select");
    }
}

然后在resource资源目录下新建config.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"
       xmlns:contex="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- bean definitions here -->
 </beans>

原本我新建一个它的实例需要通过new,但是有了IOC之后,我只需要在config.xml中声明 <bean id="OrderDao" class="com.github.hcsp.OrderDao"/>然后通过工厂方法获取:

//接受一个config.xml路径
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:db/mybatis/config.xml");
//最核心的API
OrderDao orderDao = (OrderDao) beanFactory.getBean("OrderDao");
//拿到的这个Bean默认情况下是单例的既同一时刻只能存在一个类的实例

也可以通过注解告诉SpringBoot要如何构造实例:

@Configuration
public class JavaConfiguration {
    @Bean
    public OrederDao orederDao(){
        return new OrederDao();
    }
}

同时还可以基于注解声明类之间存在的依赖关系,比如这种情况:

public class OrderService {
 
    private OrderDao orderDao;

    public void doSomething() {

        orderDao.selcet();
    }
}

\quad OrderService中存在对OrderDao的引用,原本是需要我们自己去管理对象之间的依赖关系,但是有了Spring之后,我们只需要声明他们之间存在依赖关系,管理他们这种关系的工作就由Spring去完成了。如何实现这个过程呢?这时候要是你直接通过beanFactory去新建OrderService的实例,是没有这种依赖关系的:

OrderService orderService = (OrderService) beanFactory.getBean("OrderService");

但是我使用@Autowired或者@Resource注解之后

public class OrderService {
    @Resource
    private OrderDao orderDao;

    public void doSomething() {

        orderDao.selcet();
    }
}

接着在<beans>标签中添加<contex:annotation-config/>,将schemaLocation增加为:

xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

情况就不一样了:

\quad可以看到OrderService中有了OrderDao的实例,并且与原先的是同一个,这足以证明他们之间存在依赖关系。这个过程是通过依赖注入完成的,要知道在Spring中我们声明的对象在刚被创建的时候都是为null的,依赖注入就是通过接口,构造器等为对象设置具体的值及对象之间关系的过程。依赖注入是IOC的实现方法之一。

  • Spring MVC——基于Spring和Servlet的web应用框架。是基于Spring但是比Spring更强大的一个应用。
    SpringMVC架构图:

\quad M-model(模型) 代表数据。
\quad V-view(视图)代表网页,JSP等用以展示模型中的数据。
\quad C-controller(控制器)将不同的数据显示在不同的视图上,这个过程由Servlet(小服务器)完成。

  • SpringBoot——集成度和自动化程度更高的容器。(内嵌了Servlet)
    SpringBoot封装程度更高,简化了各种配置过程,但是使得人们丧失了对底层的控制,更难以了解程序的实现细节。

2.手写一个最精简的IOC容器

\quad从新建properties文件开始,记得前面讲Mybatis的动态SQL配置日志那里,新建了一个log4j.properties文件,其中都是一个个的XXX=XXX。这其实类似于HashMap的键值对(Properties类继承了HashTable),前面是对象名,后面是对象的全限定类型。
\quad在我们的IOC容器中,添加自己的映射。新建一个beans.properties文件,写入以下数据:

orderDao=com.github.hcsp.ioc.OrderDao
userDao=com.github.hcsp.ioc.UserDao
userService=com.github.hcsp.ioc.UserService
orderService=com.github.hcsp.ioc.OrderService

接下来用这样一个例子演示IOC加载类之间依赖关系的原理:

\quad在MyIocContainer中加载这个文件:

 /**
     * 从.properties文件中获取信息
     *
     * @return 一个properties类
     */
    public static Properties getAndLoadProperties() {
        Properties properties = new Properties();
        try {
            properties.load(MyIoCContainer.class.getResourceAsStream("/beans.properties"));
        } catch (IOException e) {
            throw new RuntimeException("properties路径有误"+e);
        }
        return properties;
    }
 /**
     * 遍历.properties文件中的内容,生成value的实例,将<key,value的实例>逐个映射到HashMap中
     *
     * @param properties 加载后的properties实例
     * @return 映射后的HashMap容器
     */
    public static HashMap<String, Object> newInstance(Properties properties) {
        HashMap<String, Object> hashMap = new HashMap<>();
        properties.forEach((beanName, beanInstance) -> {
            try {
                Class<?> klass = Class.forName((String) beanInstance);
                Object newBeanInstance = klass.getConstructor().newInstance();
                hashMap.put((String) beanName, newBeanInstance);
            } catch (Exception e) {
                throw new RuntimeException();
            }
        });
        return hashMap;
    }
 /**
     * 为带有@Autowired标签的成员变量设置依赖关系
     *
     * @param beanName     null
     * @param beanInstance 被依赖的类的全限定类名
     * @param beans        类与类名之间的映射关系
     */
    @SuppressWarnings("unused")
    public static void dependencyInstance(String beanName, Object beanInstance, HashMap<String, Object> beans) {
        List<Field> fields = Stream.of(beanInstance.getClass().getDeclaredFields())
                .filter(field -> field.getAnnotation(Autowired.class) != null)
                .collect(Collectors.toList());
            fields.forEach(field -> {
                String filedName = field.getName();
                Object filedInstance = beans.get(filedName);
                field.setAccessible(true);
                try {
                //为beanInstance对象设置依赖关系
                    field.set(beanInstance, filedInstance);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            });
    }
// 启动该容器
    public void start() {
        Properties properties = getAndLoadProperties();
        hashMap = newInstance(properties);
        hashMap.forEach((name, instance) -> {
            dependencyInstance(name, instance, hashMap);
        });
    }

    // 从容器中获取一个bean
    public Object getBean(String beanName) {
        return hashMap.get(beanName);
    }
public class MyIoCContainer {
    private HashMap<String, Object> hashMap;
    public static void main(String[] args) {
        MyIoCContainer container = new MyIoCContainer();
        container.start();
        OrderService orderService = (OrderService) container.getBean("orderService");
        orderService.createOrder();
      }

运行结果:

\quad其实这就是IOC容器的本质,Spring实现的过程其实与这个过程大同小异。同样是在XML里面配置Bean,在我们自己的例子中Beans的定义就是一个简单的类名。但是实际上肯定比这个复杂,所以在Spring中用一个名为BeanDefinition的接口去描述,

* A BeanDefinition describes a bean instance, which has property values,
 * constructor argument values, and further information supplied by
 * concrete implementations.

根据描述信息实现BeanDefinition的载入和解析,最后同样也是Bean的实例化跟依赖注入了。

3.参考资料