前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >由表及里分析Spring-IOC容器始末

由表及里分析Spring-IOC容器始末

作者头像
可为编程
发布2023-11-14 11:37:04
1910
发布2023-11-14 11:37:04
举报

戳上方蓝字“可为编程” 点击右上角选择“设为星标”,好文不错过!

上一篇我们讲到了IOC容器,其实就是我们常说的Spring容器,IOC容器是具有依赖注入功能(也就是DI)的容器,负责对象的实例化、对象的初始化,对象和对象之间依赖关系配置、对象的销毁、对使用者提供对象的查找等操作,可以说IOC容器控制了整个对象的生命周期。我们需要使用的对象都由IOC容器进行创建并管理,不需要我们再去手动通过new的方式去创建对象,而是由IOC容器直接帮我们组装好,当我们需要使用的时候直接从IOC容器中直接获取就可以了,这是上一篇我们讲过的内容,这里再回顾一下。

那么spring ioc容器是如何知道需要管理哪些对象呢?

上文说到我们需要给IOC容器提供一个配置清单,这个配置清单支持xml格式java注解的方式,在配置文件中列出需要让IOC容器管理的对象,以及指定好让IOC容器如何构建某些对象,比如A对象依赖B对象、C对象等对象与对象之间的依赖关系。当spring容器启动的时候,就会去加载这个配置文件,然后将这些对象给组装好以供外部访问者使用。

1. IOC容器初始化细节

当然,到了springboot的时候有了自动注入的功能,在启动时并不第一时间去直接加载所有Bean的配置文件,而是采用了懒加载的方式。这意味着在应用程序启动后,只有当实际需要使用到配置信息时,才会去加载对应的配置文件。通过加载META-INF/spring.factories文件来实现自动配置。这是一个特殊的配置文件,它包含了各种自动配置类的全限定类名。Spring Boot在启动时会扫描这个文件,并根据pom文件中的配置信息创建和配置应用程序所需的组件。其实本质上还是读取配置文件,只是加载的方式变了。后续到springboot章节咱细聊。

那么说具体一点,IOC容器到底是怎样实现的呢?用了哪种结构?怎么存储?从哪看?

IOC容器其实就是一个Map,本身就是一个CurrentHashMap,这里就涉及到三级缓存,后面我们会详细拿出一篇文章进行讲解,这个Map里面存放的是各种对象,包括在Xml里面配置的Bean节点和我们采用注解的方式进行标注的类,在项目启动的时候会读取配置文件里面的Bean节点,根据全限定类名使用反射机制创建对象放到Map里。扫描到带注解的类也是通过反射机制创建对象并存放到Map中,比如一些注解、xml中的bean节点内的ref属性,项目启动的时候会读取xml节点ref属性,并根据ID进行注入,同时对于注解形式,根据类型或者id进行注入,id也就是对象名称且默认按照驼峰首字母小写的形式进行注入。

定义类

整个IOC容器的启动入口在ClassPathXmlApplicationContext的构造方法中的refresh()方法里,其实在每一个容器接口类里都会存在refresh方法,比如AnnotationConfigApplicationContext、ClassPathXmlApplicationContext、FileSystemXmlApplicationContext你都会发现refresh()方法的身影。该方法调用的是父类AbstractApplicationContext的refresh()方法。BeanDefinitionParserDelegateBeanDefinition解析委托类,就是专门解析由xml 转成Document的类,Document里面是以 beans 为根节点的Spring配置文件的全部内容。也就是下面这一区域。

BeanDefinition在Spring框架中用于定义Bean的配置元信息。它包含了Bean的类名、设置父bean名称、是否为primary、Bean行为配置信息(如作用域、自动绑定模式、生命周期回调等)、依赖设置、构造参数、属性设置等内容。BeanDefinition可以被看作是对Bean属性和配置信息的抽象,这些信息被存储在Spring容器中,以便在需要的时候创建和管理Bean。通过这样的设计,Spring将Bean的创建和管理逻辑与具体的实现解耦,使得用户可以更加灵活地配置Bean。

然后将Document类进行解析,载入到BeanDefinition,这个过程非常复杂,在这里就不细细展开。其实BeanDefinition说白了就是引入了一个第三方来统一Bean的格式,因为创建Bean我们可能有多种方式,配置文件、注解、配置类等等,他都会转换成同一种格式文件,到这步只是一个定义类,只是将BeanDefinition信息发布到IOC容器中,此时依旧没有对应的Bean实例创建,没有被初始化,更没有完成依赖注入,也就是没有注入其配置的资源给Bean,那么它还不能完全使用。

初始化

对于初始化和依赖注入,Spring Bean还有一个配置选项lazy-init属性,其含义就是是否初始化SpringBean,在没有任何配置的情况下,它的默认值为default,实际值为false,也就是IOC默认全自动初始化Ben,如果将其设置为tue,那么只有当我们使用SpringIoC容器的getBean方法获取它时,它才会进行Bean的初始化,完成依赖注入。

DI依赖注入

当我们用到这个对象的时候,再通过DI注入。DI注入采用三种方式:

  1. 构造函数注入:这是在对象创建时通过构造函数参数进行的依赖注入。当Spring容器创建一个新的bean实例时,会调用相应的构造函数,将所需的依赖作为参数传递给该构造函数。
  2. Setter方法注入:在这种方式下,依赖注入发生在bean的初始化阶段。首先,Spring容器会调用bean的无参数构造函数创建一个bean实例,然后通过反射调用bean的setter方法,将依赖注入到bean中。
  3. 注解注入:这是通过在字段上使用@Autowired注解进行的依赖注入。这种方式的注入也发生在bean的初始化阶段,与setter方法注入类似。但需要注意的是,字段注入通常不推荐使用,因为它违反了封装的原则,而且可能会导致不可预见的副作用。

需要注意的是,以上的时机都是相对于Spring容器的生命周期来说的。在Spring容器启动并初始化bean时,依赖注入就会发生。具体的时机取决于选择的依赖注入方式(构造函数、setter、字段)以及配置。构造函数注入能够保证所有的依赖在对象创建后就立即可用,并且它们是final的,不会被修改。而setter方法注入则提供了更大的灵活性,可以在运行时动态更改依赖,但它也带来了更大的复杂性。到这终于把IOC容器的流程大致说了一遍。

2. Bean的概念

为啥叫Bean不叫其他呢,大家可以自行百度哈。由spring容器管理的对象统称为Bean对象。Bean就是普通的java对象,他和我们自己new的对象其实是一样的,只是这些对象是由spring去创建和管理的,我们需要在配置文件中告诉spring容器需要创建哪些bean对象,这就是我们之前说配置文件和注解两种方式,如果采用配置文件,需要先在配置文件中定义好需要创建的bean对象,这些配置统称为bean的元数据配置信息,spring容器通过读取这些bean配置元数据信息来构建和组装我们需要的对象。如果是采用注解的形式,那么spring会扫描指定包下所有带有相关注解的类,并通过AnnotationConfigApplicationContext上下文获取到指定的Bean对象。这里要注意,当我们通过注解的形式进行Bean定义的时候,如果没有指定BeanId,系统会默认通过类名驼峰的形式定义BeanId。这就是Bean的两种加载方式。

代码语言:javascript
复制
@Service
public class KeWeiService {
    public KeWeiService() {
        System.out.println("基于注解形式创建正在创建KeWeiService---   " + this);
    }
}
代码语言:javascript
复制
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(KeWeiService.class);
KeWeiService kw = (KeWeiService) annotationConfigApplicationContext.getBean("keWeiService");
System.out.println(kw);

3. 基于XML的SpringIOC容器初始化步骤

1、引入spring相关的maven配置

代码语言:javascript
复制
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
    </dependencies>

2、创建bean配置文件,比如test.xml配置文件

3、在test.xml文件中定义好需要spring容器管理的bean对象

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        "
>
<!--构造方法中增加妻子对象-->
    <bean class="org.kewei.pojo.Person" id="person">
        <constructor-arg type="org.kewei.pojo.Wife" ref="wife"/>
    </bean>
    <bean class="org.kewei.pojo.Wife" id="wife" autowire-candidate="true">
        <property name="age" value="18"/>
        <property name="name" value="可为"/>
    </bean>
</beans>

4、创建spring容器,并给容器指定需要装载的bean配置文件,当spring容器启动之后,会加载这些配置文件,然后创建好配置文件中定义好的bean对象,将这些对象放在容器中以供使用。

代码语言:javascript
复制
public class Main {
    public static void main(String[] args) {

        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("test.xml");
        Person person = (Person) classPathXmlApplicationContext.getBean("person");

        System.out.println(person.getWifeName() + person.getWifeAge());
    }

5、通过容器提供的方法获取容器中的对象,然后调用Person中的方法获取Wife的年龄和名字。

4. IOC容器对象分析

Spring内部提供了很多表示Spring容器的接口和对象,比如BeanFactory接口,ApplicationContext接口,我们来看看比较常见的几个容器接口和具体的实现类。

BeanFactory接口

代码语言:javascript
复制
org.springframework.beans.factory.BeanFactory

spring容器中具有代表性的容器就是BeanFactory接口,这个是spring容器的顶层接口,提供了容器最基本的功能,其他实现类都是基于BeanFactory进行的功能扩展。

常用的几个方法
代码语言:javascript
复制
//按bean的id或者别名查找容器中的bean
Object getBean(String name) throws BeansException
//这个是一个泛型方法,按照bean的id或者别名查找指定类型的bean,返回指定类型的bean对象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
//返回IOC容器中指定类型的bean对象
<T> T getBean(Class<T> requiredType) throws BeansException;
//获取指定类型bean对象的获取器,这个方法比较特别,以后会专门来讲
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
//判断容器中是否有该Bean
boolean containsBean(String name);
//判断某个Bean对象是否为单例Bean
boolean isSingleton(String name) throws NoSuchBeanDefinitionException
//判断某个Bean是否是原型Bean
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

ApplicationContext接口

代码语言:javascript
复制
  1. org.springframework.context.ApplicationContext

这个接口继承了BeanFactory接口,所以内部包含了BeanFactory所有的功能,并且在其上进行了扩展,增加了很多企业级功能,比如AOP、国际化、事件支持等等。

BeanFactory与ApplicationContext有什么区别?

BeanFactory采用的是延迟加载形式来注入Bean,即只有在使用某个Bean的时候才调用getBean()方法,才对Bean进行加载和实例化。意味着Bean在需要时才进行初始化,而不是在应用启动时立即初始化。这样我们就不会发现一些存在的Spring的配置问题。如果Bean中的某一个属性没有进行注入,当BeanFactory加载后,直到第一次使用调用getBean方法的时候才会抛出异常。

要实现延迟加载,你可以通过在Bean的定义中使用lazy-init属性来配置。例如,在XML配置中,可以这样设置:

代码语言:javascript
复制
xml复制代码

<bean id="myBean" class="com.example.MyBean" lazy-init="true"/>

通过设置lazy-init="true",告诉Spring在第一次请求Bean时才初始化它,而不是在应用上下文启动时去加载。一开始我以为是设置了延迟时间吗?诚然不是,这里所说的延迟加载是BeanFactory本身没有默认的延迟时间,只是控制Bean的初始化时机,不涉及具体的延迟时间。

ApplicationContext是在容器启动的时候一次性创建了所有的Bean对象。先根据byType去找再通过byName去找,这样在容器启动的时候就能够发现Spring中存在的错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

相对于基本的BeanFactory,ApplicationContext唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建, 如使用ContextLoader。

BeanFactory与ApplicationContext都支持BeanPostProcessor与BeanFactoryPostProcessor但两者之间的区别是BeanFactory要手动注册,而ApplicationContext则是自动注册。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-11-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 可为编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 上一篇我们讲到了IOC容器,其实就是我们常说的Spring容器,IOC容器是具有依赖注入功能(也就是DI)的容器,负责对象的实例化、对象的初始化,对象和对象之间依赖关系配置、对象的销毁、对使用者提供对象的查找等操作,可以说IOC容器控制了整个对象的生命周期。我们需要使用的对象都由IOC容器进行创建并管理,不需要我们再去手动通过new的方式去创建对象,而是由IOC容器直接帮我们组装好,当我们需要使用的时候直接从IOC容器中直接获取就可以了,这是上一篇我们讲过的内容,这里再回顾一下。
    • BeanFactory接口
      • 常用的几个方法
    • ApplicationContext接口
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档