三、IOC容器原理

2023-11-06

三、IOC容器原理

    1、概述

        Sun ONE技术体系下的IOC容器有:轻量级的有Spring、Guice、Pico Container、Avalon、HiveMind;重量级的有EJB;不轻不重的有JBoss,Jdon等等。Spring框架作为Java开发中SSH(Struts、Spring、Hibernate)三剑客之一,大中小项目中都有使用,非常成熟,应用广泛,EJB在关键性的工业级项目中也被使用,比如某些电信业务。.Net技术体系下的IOC容器有:Spring.Net、Castle等等。Spring.Net是从Java的Spring移植过来的IOC容器,Castle的IOC容器就是Windsor部分。它们均是轻量级的框架,比较成熟,其中Spring.Net已经被逐渐应用于各种项目中。

        Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;还有一种就是继承了BeanFactory后派生而来的应用上下文,应用上下文即是Spring容器抽象的一种实现,应用上下文的抽象接口也就是我们上面提到的ApplicationContext,ApplicationContext本质上说就是一个维护Bean定义以及对象之间协作关系的高级接口。它能提供更多企业级的服务,例如解析配置文本信息等等,这也是应用上下文实例对象最常见的应用场景。有了上下文对象,我们就能向容器注册需要Spring管理的对象了。对于上下文抽象接口,Spring也为我们提供了多种类型的容器抽象的实现:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {...}

        (1)、AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;

        (2)、ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;

        (3)、FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载;

        (4)、AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式;

        (5)、XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

    2、依赖注入(Dependency Injection)和IOC(Inversion of Control)概述

        前提对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者查找引用已经创建对象B。无论是创建还是查找引用对象B,控制权都在对象A自己手上。但是由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。因此,对象A获得对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”。所谓依赖注入,就是由IOC容器在运行期间,动态地将对象A和对象B的某种依赖关系注入到对象A之中,获得依赖对象的过程由自身管理变成了由IOC容器主动注入。所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖注入的方式,实现控制反转,进而实现对象之间的解耦。IOC容器就是一个对象制造工厂,你需要什么,它会给你送去,你直接使用就行了,而再也不用去关心你所用的东西是如何制成的,也不用关心最后是怎么被销毁的,这一切全部由IOC容器包办。

    3、体系结构

        (1)、BeanFactory

            BeanFactory是接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范,是一个典型的工厂模式(接口),它是负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等具体的容器都是实现了BeanFactory,再在其基础之上附加了其他的功能。BeanFactory和ApplicationContext就是Spring框架的两个IOC容器,现在一般使用ApplicationContext,其不但包含了BeanFactory的作用,同时还进行更多的扩展。ApplicationContext以一种更面向框架的方式工作以及对上下文进行分层和实现继承,还提供了一些支持信息源、访问资源和支持应用事件等附加服务。

            BeanFactory:最顶层的一个接口类,定义了IOC容器的基本功能规范

            ListableBeanFactory:表示这些 Bean是可列表的,Bean的集合

            HierarchicalBeanFactory:表示的是这些Bean是有继承关系的,Bean之间的关系

            AutowireCapableBeanFactory:定义Bean的自动装配规则,Bean的行为

        (2)、BeanDefinition

            BeanDefinition是配置文件<bean>元素标签在容器中地内部表示。<bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应地beanClass、scope、lazyInit类属性,BeanDefinition就像<bean>的镜中人,二者是一一对应的。

            创建BeanDefinition主要包括两个步骤:

            ①、利用BeanDefinitionReader读取承载配置信息的Resource,通过XML解析器解析配置信息的DOM对象,简单地每个<bean>生成对应地BeanDefinition对象。但是这里生成的BeanDefinition可能是半成品,因为在配置文件中,可能通过占位符变量引用外部属性文件的属性,这些占位符变量在这一步里还没有被解析出来;

            ②、利用容器中注册的BeanFatoryPostProcessor对半成品的BeanDefinition进行加工处理,将以占位符表示的配置解析为最终的实际值,这样半成品的BeanDefinition就成为成品的BeanDefinition。

    4、Spring IOC容器初始化流程

        (1)、Web应用是部署在Web容器(Tomcat)中,Web容器启动时会读取项目中web.xml中的配置项来生成上下文对象ServletContext,ServletContext是一个全局的上下文环境,整个Web项目都能使用这个上下文,为后面的Spring IOC容器提供宿主环境。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
    <!-- Spring资源上下文定义,在指定地址找到Spring的xml配置文件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- Spring的上下文监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- DispatcherServlet的配置,这个Servlet主要用于前端控制,这是SpringMVC的基础 -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
</web-app>

        (2)、容器将读取<context-param>Bean定义的配置并转化为键值对传给ServletContext,这时候Web项目还没有完全启动完成,这个动作会比所有的Servlet都要早。

        (3)、创建<listener></listener>中的类实例,即创建监听,节点中的监听器类必须实现ServletContextListener接口。

        (4)、Web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized()方法会被调用。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    //初始化方法
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }
    //销毁方法,用于关闭应用前释放资源,比如说数据库连接的关闭等
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}
/**
 * ContextLoader类的初始化Spring容器方法
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //SpringIOC容器的重复性创建校验
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
    } else {
        ......
        try {
            //创建Spring容器实例
            if (this.context == null) {
                this.context = this.createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                if (!cwac.isActive()) {
                    ...... 
                    //重点操作:配置并刷新容器
                    this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            //将创建完整的Spring容器作为一条属性添加到Servlet容器中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            ......             
            return this.context;
        } catch (Error | RuntimeException var8) {
            ......
        }
    }
}

            在这个方法中,Spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是Spring的IOC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IOC容器初始化完毕后,Spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取。

        (5)、读取web.xml中的配置<servlet>初始化Servlet。

            ①、Servlet接口中定义了Servlet — init(ServletConfig var1)方法,该方法是DispatcherServlet类初始化入口,容器初始化Servlet会调用该方法。

            ②、抽象类GenericServlet实现了接口Servlet — init(ServletConfig config)方法,并在该方法中调用自己的GenericServlet — init()方法。

            ③、抽象类HttpServletBean重写了父类HttpServlet的父类GenericServlet — init()方法,并在该方法中调用自己的HttpServletBean — initServletBean()方法。

            ④、抽象类FrameworkServlet重写了父类HttpServletBean — initServletBean()方法,并在该方法中调用自己的FrameworkServlet — initWebApplicationContext()方法,然后又在initWebApplicationContext()方法中调用自己的FrameworkServlet — onRefresh(ApplicationContext context)方法。

                DispatcherServlet上下文在初始化的时候会建立自己的IOC上下文,用以持有Spring MVC相关的Bean。在建立DispatcherServlet自己的IOC上下文时,在方法initWebApplicationContext()中会利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的Parent上下文,再初始化自己持有的上下文。

            ⑤、类DispatcherServlet重写了父类FrameworkServlet — onRefresh(ApplicationContext context)方法,并在该方法中调用自己的DispatcherServlet — initStrategies(ApplicationContext context)方法。

protected void initStrategies(ApplicationContext context) {
    this.initMultipartResolver(context);
    this.initLocaleResolver(context);
    this.initThemeResolver(context);
    //初始化Handler映射关系
    this.initHandlerMappings(context);
    //初始化Handler适配器
    this.initHandlerAdapters(context);
    //Handler执行发生异常的异常解析器
    this.initHandlerExceptionResolvers(context);
    this.initRequestToViewNameTranslator(context);
    //解析View对象的视图解析器
    this.initViewResolvers(context);
    this.initFlashMapManager(context);
}

            DispatcherServlet初始化自己上下文的工作在initStrategies(ApplicationContext context)方法中,此Servlet自己持有的上下文默认实现类也是xmlWebApplicationContext。初始化完毕后,Spring以与Servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个Servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第4步中初始化的上下文)定义的那些bean。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

三、IOC容器原理 的相关文章

随机推荐

  • 解决idea中maven的javaweb项目,输出在控制台上的中文乱码问题

    在idea中创建一个maven的javaweb项目 当有中文输出到控制台的时候 就会出现乱码 下图 第一张图是我们要输出的中文 但是我们通过servlet访问之后 控制台打印出来的都是乱码 而且我们使用的是maven自带的tomcat 所以
  • 6.6 Hessenberg法求特征值

    文章目录 1 Gram Schmidt正交化的缺点 2 Hessenberg矩阵 3 海森堡化简 Hessenberg reduction 4 Givens rotation 5 多次Givens rotation QR 6 循环QR直至收
  • Java并发编程学习11-任务执行演示

    Java并发编程学习系列 任务执行演示 引言 1 串行的页面渲染器 2 携带结果的任务 3 使用 Future 实现页面渲染器 4 使用 CompletionService 实现页面渲染器 5 为任务设置时限 5 1 限时获取广告信息示例
  • Qt线程基础使用指南

    Qt的线程一共3种使用方式 继承QThread 继承QRunnable 调用moveToThread 方法 本文旨在系统的记录这3种方法的使用过程 以及解决使用这些方法中遇到的bug 一 继承QThread 1 创建线程文件 继承基类QTh
  • day02 - Java基础语法

    day02 Java基础语法 0 类型转换问题 类型转换 理解 在Java中 会存在不同类型的数据需要一起参与运算 所以这些数据类型之间是需要相互转换的 分为两种情况 自动类型转换和强制类型转换 自动类型转换 类型范围小的变量 可以直接赋值
  • 手机探测帧频率的测试

    手机的探测帧的频率在802 11协议里面并没有一个详细的要求 并且各个厂家从省电等方面考虑设置的探测帧频率也各不相同 并且在wifi界面下 锁屏状态下 忽略掉wifi再锁屏的状态下探测帧的频率都不同 所以wifi探针并不是一个可靠的用户感知
  • FPGA实战小项目

    1 基于fpga俄罗斯方块的实现 基于fpga俄罗斯方块的实现 2 基于fpga白平衡的实现 基于fpga白平衡的实现 3 基于fpga的目标跟踪 树叶 基于fpga的目标跟踪 树叶 4 基于fpga数字0 9识别的实现 基于fpga数字0
  • HCNP——水平分割、毒性逆转、触发更新、毒性路由

    一 水平分割 原理是 RIP路由器从某个接口收到的路由不会再从该接口通告回去 这个机制很大程度上消除了RIP路由的环路隐患 二 毒性逆转 毒性逆转是另一种防止路由环路的有效机制 其原理是 RIP从某个接口学到路由后 当他从该接口发送Resp
  • linux的mtime的用法,Find–atime –ctime –mtime的用法与区别总结

    周五有同事问起find命令中 mtime n mtime n以及 mtime n的用法区别 当时虽然记得这里n是n个24个小时的意思 也是对所有这几个属性详细的用法却一知半解 索性周末仔细google并且实践了一番 终于理清楚了个中乾坤 f
  • SpringBoot分布式任务调度,可支持rabbitmq与kafka两种消息中间件的可回滚微服务实现。

    分布式任务调度管理 Distribution task center 支持Rabbit与kafka两种消息队列 实现立即执行与根据CronExpress表达式的执行及更加复杂的复合执行策略 在任务执行过程中可完成回滚操作 在微服务中我们经常
  • MyBatis-动态SQL

    实体类Car package com bjpowernode domain public class Car private Integer id private String carNum private String brand pri
  • qt 一个线程接收数据 主线程更新界面 会造成界面退出 怎么解决_打造一个好产品

    原标题 打造一个好产品 让产品自己说话 编辑导语 一个好的产品 关键在于产品经理和团队 产品经理对于产品如何理解以及产品更新迭代时的需求变化 产品如何实现更好的体验等等 本文作者分享了关于产品经理经常犯的七个问题 我们一起来看一下 不管怎么
  • c++双链表【构造函数、运算符重载、析构函数、增删查改及逆置等】

    c 中的双向链表写法 主要实现 增删查改 链表逆置 构造函数 运算符重载 等 建立头文件SList h pragma once typedef int DataType class ListNode friend class List 友元
  • Vue学习-基础篇4

    目录 组件结构讲解 如何在组件中引入其它组件 组件中如何使用外部插件 组件间的传值 Vue cli项目创建 什么是脚手架 创建项目 Vue cli项目结构 Vue cli 入口文件main js分析 组件结构讲解 把每个组件都放到一个独立的
  • 【Shell牛客刷题系列】SHELL28 nginx日志分析6-统计每分钟的请求数

    该系列是基于牛客Shell题库 针对具体题目进行查漏补缺 学习相应的命令 刷题链接 牛客题霸 Shell篇 该系列文章都放到专栏下 专栏链接为 专栏 Shell 欢迎关注专栏 本文知识预告 本文首先学习了sprintf 函数的用法 然后复习
  • python爬虫实战之模拟正方教务系统登录查询成绩

    最近由于某些需要 开始入门Python网络爬虫 想通过一个Python程序来访问正方教务管理系统并且抓取到期末的成绩 由于我并没有深入了解过过其他的编程语言 所以 也比较不出Python和其他语言 如JAVA PHP 的优缺点 只是因为我会
  • JAVA通配符

    上限通配符 我们想要的是一个确切元素类型未知的列表 这一点与数组是不同的 List
  • 一、红外遥控介绍(NEC协议)

    目录 1 1 红外线的介绍 1 2 红外遥控器的工作原理 1 3 NEC协议的介绍 1 引导码的组成 2 地址码 3 数据码 4 反码 5 连发码 1 4 红外接受装置 1 在数码管显示红外解码遥控器的按键值 1 1 红外线的介绍 红外是红
  • Kitti数据集标签中yaw角在不同坐标系的转换

    KITTI数据集中坐标系的定义如下图 相机坐标系 激光雷达坐标系 在标签文件 label 中 最后一个值为物体前进方向与x轴夹角的弧度值 在相机坐标系下 且以顺时针为正 逆时针为负 举个例子 如上图第一个car数据为例 其前进方向与x轴夹角
  • 三、IOC容器原理

    三 IOC容器原理 1 概述 Sun ONE技术体系下的IOC容器有 轻量级的有Spring Guice Pico Container Avalon HiveMind 重量级的有EJB 不轻不重的有JBoss Jdon等等 Spring框架