Java 之旅

中级17 - Spring IoC容器原理与手写简单实现

2020-06-02  本文已影响0人  晓风残月1994

一切开始之前,先了解下 JavaBean 是什么,它是一种标准和约定。
一个 JavaBean:

1. Spring 是什么

Srping 是自动化管理 Java 对象和其中依赖关系的容器。

-let 词根,“小”的意思,servlet 小服务器(service + let)。
Servlet 将网络请求封装成对象,交给上层 WebApp(spring容器、mvc、boot)。
Servlet <-> HttpServletRequest/HttpServletResponse <-> WebApp。
常见的 servlet 有:

没有 Spring 怎么办?
**

Spring 的出现解放了我们的双手。

2. Spring 最简单的用法

通过配置一种“上古”的 xml 来使用。
xml 配置文件中声明了两个 Bean, 而这两个 Bean 之间存在依赖关系,因此为 OrderService 中的成员属性 OrderDao 添加了注解,用于对 OrderDao 自动装配。

在这里,容器就是 BeanFactory。有了容器之后,向容器索要 Bean。

// maven 依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
package spring.demo;

public class OrderDao {
    public void select() {
        System.out.println("select!");
    }
}
package spring.demo;

import org.springframework.beans.factory.annotation.Autowired;

public class OrderService {

    @Autowired // 加了这个注解才会自动装配 OrderDao,否则 OrderService 对象中的 orderDao 是 null
    private OrderDao orderDao;

    public void doSomething() {
        orderDao.select();
    }
}
<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config /> <!-- 这句不加的话,即使写了上面的注解也不生效 -->
    <!-- bean definitions here -->
    <bean id="orderDao" class="spring.demo.OrderDao"/>
    <bean id="orderService" class="spring.demo.OrderService"/>
</beans>
package spring.demo;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringMain {
    public static void main(String[] args) {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:spring/config.xml");

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

        System.out.println(orderService);
        orderService.doSomething();
        System.out.println(orderDao);
    }
}

现在没有写 new,却拿到了 OrderService 对象(一个Bean),默认是单例模式,也就是 OrderService 对象中的 OrderDao 和再次通过 getBean 得到的 OrderDao 对象是同一个对象。


image.pngimage.png

假如没有使用 Spring,那么可能要自己手动创建,涉及到的各种对象的各种 new。

3. Spring 容器核心概念

容器中的最小工作单元,通常为一个 Java 对象

容器本身对应的 Java 对象

容器负责注入所有的依赖

用户将控制权交给了 Spring 容器来进行自动装配

4. 手写一个简单的 IoC 容器

目录结构:


image.pngimage.png

使用方法是在字段是声明注解(部分代码不再罗列,这里仅供举例):

import org.springframework.beans.factory.annotation.Autowired;

public class OrderService {
    @Autowired private OrderDao orderDao;
    @Autowired private UserService userService;

    public void createOrder() {
        orderDao.createOrder(userService.getCurrentLoginUser());
    }
}

具体实现:

Java 从初代就支持了 .properties 格式的配置文件,该文件用来存储简单的基于 key-value pairs 的参数。该配置文件处于被编译的代码之外。

  1. 先写一个简单的 beans.properties 配置文件,定义了 Bean 的名字和对应的实现类:
# Bean 名字 和 Bean 的全限定类名
orderDao=com.github.hcsp.ioc.OrderDao
userDao=com.github.hcsp.ioc.UserDao
userService=com.github.hcsp.ioc.UserService
orderService=com.github.hcsp.ioc.OrderService
  1. MyIoCContainer 容器:
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MyIoCContainer {
    // 实现一个简单的IoC容器,使得:
    // 1. 从beans.properties里加载bean定义
    // 2. 自动扫描bean中的@Autowired注解并完成依赖注入

    // 定义一个容器! 存放 bean 的名字到 bean 实例对象的映射
    private Map<String, Object> beans = new HashMap<>();

    /**
     * 依赖注入
     *
     * @param beanInstance bean 的 实例
     */
    private void dependencyInject(Object beanInstance) {
        // 拿到带有 @AutoWired 注解的 fields
        List<Field> fieldsToBeAutoWired = Stream.of(beanInstance.getClass().getDeclaredFields())
                .filter(field -> field.getAnnotation(Autowired.class) != null)
                .collect(Collectors.toList());
        // 为当前 bean 对象的需要依赖的字段注入依赖(设置字段值)
        fieldsToBeAutoWired.forEach(field -> {
            String fieldName = field.getName(); // 加了 @AutoWired 的字段名即是所要依赖的 bean 的名字
            Object dependencyBeanInstance = beans.get(fieldName); // 所依赖的 bean 实例
            try {
                field.setAccessible(true); // 设置为 true 用来压制针对被反射对象的访问检查
                field.set(beanInstance, dependencyBeanInstance); // 从而可以在这里设置当前 bean 的私有字段
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 启动该容器
     */
    public void start() {
        // bean 的初始化

        Properties properties = new Properties();

        // 从 InputStream 中读取属性列表(键值对)
        try {
            properties.load(MyIoCContainer.class.getResourceAsStream("/beans.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        System.out.println(properties);

        properties.forEach((beanName, beanClassName) -> {
            try {
                // 通过反射拿到 bean 的实例并放入容器中
                Class<?> klass = Class.forName((String) beanClassName);
                Object beanInstance = klass.getConstructor().newInstance();
                beans.put((String) beanName, beanInstance);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });

        // 使用反射,处理依赖关系,注入依赖
        beans.forEach((beanName, beanInstance) -> dependencyInject(beanInstance));
    }

    /**
     * 从容器中获取一个bean
     *
     * @param beanName bean 的名字
     * @return 返回 bean 的实例
     */
    public Object getBean(String beanName) {
        return beans.get(beanName);
    }

    public static void main(String[] args) {

        MyIoCContainer container = new MyIoCContainer();
        container.start();
        OrderService orderService = (OrderService) container.getBean("orderService");
        orderService.createOrder();
    }
}

即使是两个依赖字段存在循环依赖也没关系,因为在创造期间,会先各自创建出实例对象。相关依赖字段此时是 null,不影响 bean 的创建。而创建完之后,再回头对字段注入所依赖的 bean。

当然,以上只是个简单的实现,实际的 Spring 中还可以在构造器上使用 @Autowired 注解,而不推荐在私有字段上使用。这样即使不是用 Spring,比如写测试代码的时候,还是可以方便的 new 一个实例,否则,私有字段还要通过反射一顿操作才能创建实例进行测试。

另外,实际应用中 @Autowired 也不推荐使用了,更推荐 @Inject 注解,这样除了 Spring 之外,还可能受到其他类似框架的识别。

所谓的 Spring 只不过是在以上基础上扩充了无穷无尽的功能,比如支持 xml 中配置依赖,支持构造器注入(调用哪个构造器,传递哪些参数)等等,从一个很简单的思想不断扩充成庞大的体系。

5. Spring 启动过程浅析

建议 debug Spring 源码的时候,要带着目前来进行,不要在无关细节里浪费太多时间。


image.pngimage.png
image.pngimage.png
image.pngimage.png

6. 参考

上一篇 下一篇

猜你喜欢

热点阅读