浅试实现mini-spring-ioc容器(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 浅试实现mini-spring-ioc容器

IOC

我们学Java的基本上都会使用Spring进行开发,而Spring中最为核心的又是IOC和AOP,接下来的内容是在学习手写Spring渐进式源码实践这本书后的学习总结,看是否我们能开发出一个mini-Spring。因为后期代码会很多,而且基本上都是在前一版的基础上进行扩展。这里我只声明每一章的目标扩展点是啥,具体从Github上获取源码:

第一章:实现一个简单的Spring Bean容器

先不深究Spring源码,我就看自己平时使用Spring时的体会,使用Spring时,通过XML配置文件或者通过注解,声明哪些类是需要注入到容器中的,到自己使用时,可以从容器中获取该类对象。那这不就是我们基础中学的Collection或者Map就能实现的操作嘛,因为我需要频繁的从容器中获取指定类对象,所以查询返回的效率需要非常高,那就我们就用Map来实现,先不要想那么多。

public class BeanFactory {
    // 用Map来存储Bean
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    // 获取
    public Object getBean(String name) {
        return beanDefinitionMap.get(name).getBean();
    }
    // 注册
    public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(name, beanDefinition);
    }
}

这里我们使用BeanDefinition来包裹实际的对象,因为我们知道一个Bean不仅拥有自己属性,还拥有一些通用属性,比如:单例模式还是原型模式,Bean内需要填充的属性,这里我们只做包裹,其余的后面实现。

public class BeanDefinition {
    // 实际填充的对象
    private Object bean;
    // 省略 构造器 和 get方法。。
}

我们来测试一下:

/@Test
public void test_BeanFactory(){
    // 1.初始化 BeanFactory
    BeanFactory beanFactory = new BeanFactory();
    // 2.注入bean
    BeanDefinition beanDefinition = new BeanDefinition(new UserService());
    beanFactory.registerBeanDefinition("userService", beanDefinition);
    // 3.获取bean
    UserService userService = (UserService) beanFactory.getBean("userService");
    userService.queryUserInfo();
}

第二章:实现Bean对象的定义,注册和获取

在上面我们可以看到我是创建好了的UserService放入容器中,这和我们之前学的依赖倒置并不符合,我们应只指定哪些Bean需要加载到容器中,具体的对象实例应又BeanFactory自己管理,所以我们修改BeanDefinition,在内只存放Bean的Class对象。

public class BeanDefinition {
    private Class beanClass;
    // 构造器 get,set方法
}

BeanFactory也修改为接口,定义获取Bean的方式,具体Bean存储与获取交给子类来实现,使职责单一。

我们在这里先假设每个Bean都是单例的,创建SingletonBeanRegistry接口,定义注册和获取单例对象的方式,剩下的交给子类具体实现。

现在系统中有两个容器,一个是BeanDefinitionMap负责存储加载到容器的Bean信息,一个是singletonObjects负责存储已经创建好的单例对象。具体可看下面的UML类图,关系还是很清楚的。BeanFactory在实例化对象时,通过BeanDefinitionMap中的类信息,通过反射直接创建对象,并放入容器中。

Spring中有大量的接口与抽象类,我个人感觉是将职责划分清楚,容易未来系统的扩展性,有一点是通过抽象类去实现接口,并定义自己的抽象方法,而且可以实现一部分的接口方法,这样既可以扩容接口,又能保证自己可以只实现自己职责内的方法。

第三章:基于Cglib实现含有构造函数的类实例化策略

在第二章中我们在AbstractAutowireCapableBeanFactory::createBean中通过反射进行Bean的实例化,这一章我们引入了实例化策略:InstantiationStrategy,并通过JDK和Cglib两个种方式去实现。

JDK 和 Cglib实例化对象有什么区别

CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

image.png

第四章:注入属性与依赖对象

image.png

比方说我UserService内需要注入UserDao来操作数据库,所以我在实例化Bean后,应将所需的属性和依赖注入到Bean中,而这部分信息通过PropertyValues记录,并封装到BeanDefinition中。在实例化后应通过对应BeanDefinition获取到需要哪些内容,并进行填充处理。

image.png

第五章:资源加载器解析文件注册对象

image.png

这里我们要加入XML解析,在上面我们都是手动将所有信息通过Java注册到BeanFactory的,这不利于维护,现实Spring中我们都是使用Xml进行配置或者注解开发,所以我们需要定义一个ResourceLoader去加载各种数据(XML文件,URL,Classpath)。

资源加载,读取指定资源,转化为Resource,核心是获取文件的二进制流为后面解析

image.png

资源解析:利用XML解析器解析Spring.xml文件,获取所有Bean信息定义,并将其封装为BeanDefinition并注册

image.png

容器初始化:完成实例的实例化与属性填充

image.png

这一章类扩展的很多,建议仔细读两遍源码,着重关注资源加载解析部分即可,其余流程大体不变。

第六章:实现应用上下文

image.png

重点:

引入应用上下文,进行资源扫描与加载,为Bean对象实例化过程添加扩展机制,允许加载Bean对象和在其实例化前后进行修改和扩展。

核心:

增加BeanPostProcessor 和 BeanFactoryPostProcessor

有啥用:

比如说我们想在Bean的实例化之前,对BeanDefinition做出记录或修改,影响Bean的实例化,或者在Bean的初始化方法前后进行一系列操作,这是后面章节的基础部分,实现容器感知,AOP等技术的实现

AbstractApplicationContext::refresh() 刷新容器

@Override
public void refresh() throws BeansException {
    // 1. 创建 BeanFactory,并加载 BeanDefinition
    refreshBeanFactory();
    // 2. 获取 BeanFactory
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    // 3. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor ,这时候Bean还没有实例化,找到所有的BeanFactoryPostProcessor,进行处理,可以修改BeanDefinition !!!
    invokeBeanFactoryPostProcessors(beanFactory);
    // 4. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作,找到所有的BeanPostProcessor,添加到一个容器中,等对象实例化时进行处理  !!! 
    registerBeanPostProcessors(beanFactory);
    // 5. 提前实例化单例Bean对象
    beanFactory.preInstantiateSingletons();
}

加载出所有注册的BeanFactoryPostProcessor,普通Bean实例化前操作

private void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
    for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactoryPostProcessorMap.values()) {
        beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
    }
}

加载出所有注册的BeanPostProcessor,必须加载并注册,因为这些Bean是转为针对普通Bean实例化后操作的。

private void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    Map<String, BeanPostProcessor> beanPostProcessorMap = beanFactory.getBeansOfType(BeanPostProcessor.class);
    for (BeanPostProcessor beanPostProcessor : beanPostProcessorMap.values()) {
        beanFactory.addBeanPostProcessor(beanPostProcessor);
    }
}
public void preInstantiateSingletons() throws BeansException {
    // 实例化所有未实例化的Bean
    beanDefinitionMap.keySet().forEach(this::getBean);
}
protected <T> T doGetBean(final String name, final Object[] args) {
        // 先从单例工厂找,有则直接返回,这样就不担心之前加载的Processor再被加载处理了
        Object bean = getSingleton(name);
        if (bean != null) {
            return (T) bean;
        }
        // 获取Bean对应的定义信息
        BeanDefinition beanDefinition = getBeanDefinition(name);
        // 实例化Bean
        return (T) createBean(name, beanDefinition, args);
    }
@Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Object bean = null;
        try {
            // 将 Bean 实例化,执行构造器方法
            bean = createBeanInstance(beanDefinition, beanName, args);
            // 给 Bean 填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }
        // 将Bean注入单例容器中
        registerSingleton(beanName, bean);
        return bean;
    }
private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {
        // 1. 执行 BeanPostProcessor Before 处理 !!!
        Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
        // 待完成内容:invokeInitMethods(beanName, wrappedBean, beanDefinition);  这里就是未来init方法调用的地方
        invokeInitMethods(beanName, wrappedBean, beanDefinition);
        // 2. 执行 BeanPostProcessor After 处理 !!!
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        return wrappedBean;
    }

第七章:Bean对象的初始化与销毁

image.png

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="userDao" class="cn.bugstack.springframework.test.bean.UserDao"
          init-method="initDataMethod"
          destroy-method="destroyDataMethod"/>
</beans>

在Bean定义信息中我们指定了初始化方法(和对象构造器方法无关)和销毁方法。加入了init-method 和 destroy-method。我们可以在Xml解析中,读取出该数据,并把该数据保存到BeanDefinition中。这里的销毁方法,我们利用JVM的Hook进行调用。

public class BeanDefinition {
    private Class beanClass;
    private PropertyValues propertyValues;
    private String initMethodName;
    private String destroyMethodName;
  // ...get/set/construct方法
}

除了在Xml中进行设置初始化和销毁方式外,我们也可以设计两个接口,让Bean来实现对应的方法,未来初始化时执行相应方法。

public interface InitializingBean {
    /**
     * Bean 处理了属性填充后调用
     */
    void afterPropertiesSet() throws Exception;
}
public interface DisposableBean {
    /**
     * Bean 销毁时执行
     */
    void destroy() throws Exception;
}

image.png



目录
相关文章
|
3月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
30天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0
|
2月前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
120 3
|
3月前
|
XML Java 数据格式
Spring5入门到实战------3、IOC容器-Bean管理XML方式(一)
这篇文章详细介绍了Spring框架中IOC容器的Bean管理,特别是基于XML配置方式的实现。文章涵盖了Bean的定义、属性注入、使用set方法和构造函数注入,以及如何注入不同类型的属性,包括null值、特殊字符和外部bean。此外,还探讨了内部bean的概念及其与外部bean的比较,并提供了相应的示例代码和测试结果。
Spring5入门到实战------3、IOC容器-Bean管理XML方式(一)
|
3月前
|
XML Java 数据格式
Spring5入门到实战------5、IOC容器-Bean管理(三)
这篇文章深入探讨了Spring5框架中IOC容器的高级Bean管理,包括FactoryBean的使用、Bean作用域的设置、Bean生命周期的详细解释以及Bean后置处理器的实现和应用。
Spring5入门到实战------5、IOC容器-Bean管理(三)
|
3月前
|
XML Java 数据格式
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
这篇文章是Spring5框架的实战教程,主题是IOC容器中Bean的集合属性注入,通过XML配置方式。文章详细讲解了如何在Spring中注入数组、List、Map和Set类型的集合属性,并提供了相应的XML配置示例和Java类定义。此外,还介绍了如何在集合中注入对象类型值,以及如何使用Spring的util命名空间来实现集合的复用。最后,通过测试代码和结果展示了注入效果。
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
|
3月前
|
XML Java 数据格式
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
这篇文章是Spring5框架的入门教程,详细讲解了IOC容器中Bean的自动装配机制,包括手动装配、`byName`和`byType`两种自动装配方式,并通过XML配置文件和Java代码示例展示了如何在Spring中实现自动装配。
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
|
3月前
|
XML Java 数据格式
Spring5入门到实战------2、IOC容器底层原理
这篇文章深入探讨了Spring5框架中的IOC容器,包括IOC的概念、底层原理、以及BeanFactory接口和ApplicationContext接口的介绍。文章通过图解和实例代码,解释了IOC如何通过工厂模式和反射机制实现对象的创建和管理,以及如何降低代码耦合度,提高开发效率。
Spring5入门到实战------2、IOC容器底层原理
|
3月前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式