美文网首页Java 之旅
中级17 - Spring IoC容器原理与手写简单实现

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

作者: 晓风残月1994 | 来源:发表于2020-06-02 17:41 被阅读0次

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

  • 所有的属性都是 private(通过 getter/setter 进行读写)
  • 有一个 public 无参数的构造器
  • 实现了 Serializable(序列化,注意 Serializable 接口没有方法也没有字段,更多的是一种规范和约定)

1. Spring 是什么

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

  • Java世界中Web容器的事实标准
  • Spring 容器 - 一个 IoC 容器
    • Spring MVC - 基于 Spring 和 Servlet 的 Web 应用框架
      • Spring Boot - 集成度和自动化程度更高(内嵌了 Servlet 容器)

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

  • tomcat
  • jetty

没有 Spring 怎么办?
**

  • 一个 main 程序走天下:轻量简单,但是规模大了后难以维护。
  • 拆分并且手动管理:优点方便测试、共同开发、维护,但缺点还是规模更大后,依赖关系太复杂。

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 容器核心概念

  • Bean

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

  • BeanFactory/ApplicationContext

容器本身对应的 Java 对象

  • 依赖注入(DI)

容器负责注入所有的依赖

  • 控制反转(IoC)

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

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

  • 定义 Bean
  • 加载 Bean 的定义
  • 实例化 Bean
  • 查找依赖,实现依赖注入
  • 要什么 Bean 就设置成什么 Bean

目录结构:


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 启动过程浅析

  • 在 xml 里中定义 Bean
  • BeanDefinition 的加载和解析
  • Bean 的实例化和依赖注入
  • 对外提供服务

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


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

6. 参考

相关文章

网友评论

    本文标题:中级17 - Spring IoC容器原理与手写简单实现

    本文链接:https://www.haomeiwen.com/subject/ggcpzhtx.html