Spring IOC 原理

IOC理解

   控制反转,不需要手动new对象,而是容器管理对象实例。

自己写的简单的IOC容器

IOC容器

public class BeanFactory
{
    private BeanFactory(){};

    private static BeanFactory beanFactory= null;

    private static Properties properties = new Properties();

    static
    {
        beanFactory = new BeanFactory();

        try
        {
            //读取配置文件,加载到properties中
            String path = BeanFactory.class.getResource("").getPath()+"bean.properties";
            InputStream is = new FileInputStream(new File(path));
            properties.load(is);
        } catch (IOException e)
        {
            e.printStackTrace();
        }

    }

    public static BeanFactory getInstance()
    {
        return beanFactory;
    }

    public Object getBean(String beanName) throws ClassNotFoundException, IllegalAccessException, InstantiationException
    {
        String className = properties.getProperty(beanName);
        Class clazz = Class.forName(className);
        Object object = clazz.newInstance();

        return object;
    }
}

Dao类

public class StudentsDao
{
    public Student queryStudent()
    {
        Student student = new Student();
        student.setName("小明");
        student.setScore("100");
        student.setRank("1");

        return student;
    }
}

测试类

public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException
    {
        BeanFactory beanFactory = BeanFactory.getInstance();
        StudentsDao studentDao = (StudentsDao) beanFactory.getBean("studentDao");
        System.out.println(studentDao.queryStudent());

    }

执行结果:
在这里插入图片描述

IOC的原理

定位、加载、注册

Spring bean的创建是典型的工厂模式
BeanFactory是最顶层的接口,三个重要子类ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory,最终默认实现类是DefaultListableBeanFactory,
这三个接口定义了bean的集合、Bean之间的关系以及Bean行为
BeanFactory接口只对IOC容器最基本的行为做了定义,具体的实现在IOC容器中,比如ClassPathXmlApplicationContext
ApplicationContext是一个高级IOC容器,他提供了很多附加的功能:如国际化,访问资源,支持应用事件
BeanDefinition Bean对象在Spring实现是以BeanDefinition来描述的
BeanDefinitionReader 主要是来解析Spring配置文件的

Web IOC容器大概过程
入口处是DispatcherServlet,在父类HttpServletBean中找到init方法,真正完成初始化容器的逻辑是在initBean中
实现initBean的是FrameworkServlet,initWebApplicationContext()方法,再跟configureAndRefreshWebApplicationContext(),里面找到refresh()这才是真正启动IOC容器的入口
初始化了IOC容器后,再调用DispatcherServlet的onfresh,调用initStrategies(),初始化Spring的九大组件

下面就是IOC具体的初始化过程
基于xml配置文件的IOC容器初始化
IOC容器的初始化包括BeanDefinition的资源定位、加载和注册这三个基本过程

基于XML配置的IOC容器初始化

1、寻找入口

ApplicationContext app = new ClassPathXmlApplicationContext(“application.xml”);

2、获取配置路径

super(parent); 为容器设置好 Bean 资源加载器
setConfigLocations(configLocations); 爷爷类AbstractRefreshableConfigApplicationContext 设置 Bean 配置信息的定位路径
父类AbstractApplicationContext 默认构造方法 中创建 Spring 资源加载器: (Spring资源加载器是个啥??)
开始调用setConfigLocations方法 AbstractRefreshableConfigApplicationContext,看这个源码,xml路径是支持多个的,用逗号、分号或者空格分割即可

3、开始启动

SpringIOC容器对Bean配置资源的载入是从refresh()函数开始的,这个方法是一个模板方法,规定了IOC容器的启动流程,具体逻辑在子类中实现
看下refresh()这个方法主要为IOC容器的声明周期管理提供条件,比如,在创建IOC容器之前,如果已经有容器存在,就把已有的容器销毁和关闭,
refreshBeanFactory()这个方法调用后,载入Bean配置信息就开始了
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 后面的代码都是注册容器的信息员和生命周期事件。

4、创建容器

obtainFreshBeanFactory开始了,调用refreshBeanFactory();这是个抽象方法,需要子类实现,AbstractRefreshableApplicationContext实现了
先创建DefaultListableBeanFactory,再调用loadBeanDefinitions(beanFactory)装载bean定义,这里用了委派模式,具体的实现调用子类容器

5、载入配置路径

看下具体的实现子类,AbstractXmlApplicationContext
XmlBeanDefinitionReader是一种XmlBean读取器,他调用的是父类的loadBeanDefinitions(configResources)方法,父类是AbstractBeanDefinitionReader
这里走的分支是reader.loadBeanDefinitions(configLocations);

6、分配路径处理策略

AbstractBeanDefinitionReader中定义了载入过程,看下源码,这个方法做了两件事
1、调用资源加载器的获取资源方法 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 获取到要加载的资源
2、执行加载功能的是其子类XmlBeanDefinitionReader的loadBeanDefinition方法
//委派调用其子类XmlBeanDefinitionReader的方法,实现加载资源文件功能
int loadCount = loadBeanDefinitions(resources);
getResources(location)方法 实际上是调用AbstractApplicationContext中的getResources方法,这个方法是在ResourcePatternResolver接口中定义的,看下ResourcePatternResolver 的类图
可以看出ApplicationContext继承了ResourcePatternResolver,ResourcePatternResolver又继承了ResourceLoader
通过类图得知getSource()实际上是调用的DefaultResourceLoader的getSource() 方法定位的Resource,因为ClassPathXmlApplicationContext 本身就是 DefaultResourceLoader 的实现类,所以此时又回到了
ClassPathXmlApplicationContext 中来。

7、解析配合文件路径

XmlBeanDefinitionReader通过调用ClassPathXmlApplicationContext的父类的DefaultResourceLoader的 getResource()方法获取要加载的资源,提供了getResourceByPath方法,在ClassPathReource中完成了对整个路径的解析。这样,就可以从类的路径上对IOC配置文件进行加载,不管IOC配置文件在哪里。
在Spring中提供了各种资源抽象,如ClassPathResource、URLResource、FileSystemResource

8、开始读取配置内容

继续回到XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …),看到bean文件的资源定义以后的载入过程
//将XML文件转换为DOM对象,解析过程由documentLoader实现
Document doc = doLoadDocument(inputSource, resource);

9、准备文档对象

doLoadDocument可以再往下找,是在DefaultDocumentLoader类中,是调用 JavaEE 标准的 JAXP 标准进行处理。至此Spring IOC 容器根据定位的 Bean 配
置信息,将其加载读入并转换成为 Document 对象过程完成

10、分配解析策略

XmlBeanDefinitionReader 的 doLoadBeanDefinition()方法主要是从xml文件中载入Bean配置资源,再转化为Document对象,接下来调用registerBeanDefinitions,启动Spring IOC容器对Bean的定义解析过程
Bean 配置资源的载入解析分为以下两个过程:
1、将XML文件转换为DOM对象,解析过程由documentLoader实现
2、按照Spring Bean的定义规则对Document对象进行解析,解析过程是在接口BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader中

11、将配置载入内存

继续往下,调用registerBeanDefinitions方法,BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader对docuent解析,
了解下 导入元素、别名元素

12、载入元素

元素信息交给BeanDefinitionParserDelegate的方法parseBeanDefinitionElement来实现
在解析时,没有创建和实例化bean对象,只是创建了Bean对象的定义类BeanDefinition,将Bean的信息封装在BeanDefinition里,依赖注入时使用

13、载入元素

BeanDefinitionParserDelegate的方法parseBeanDefinitionElement 中,调用parsePropertyElements这个方法来解析中的元素
处理过程:
1、ref 被封装为指向依赖对象一个引用
2、value 配置都会封装成一个字符串类型的对象。
3、ref 和 value 都通过“解析的数据类型属性值.setSource(extractSource(ele));”方法将属性值/引用与所引用的属性关联起来。

14、载入的子元素

还是在BeanDefinitionParserDelegate类中,parsePropertySubElement()方法,解析元素的子元素,例如, array、list、set、map、prop 等,生成对应的数据对象
ManagedList、ManagedArray、ManagedSet 等,这些 Managed 类是 Spring 对象 BeanDefiniton 的数据封装

15、载入的子元素

还是在BeanDefinitionParserDelegate类的parseListElement()方法,实现了解析元素的子元素集合的子元素

到此为止,经过对Spring Bean配置信息转换Document对象中的元素层层解析,IOC已经将XML转换成了BeanDefinition,他是Bean配置信息中配置的POJO对象在IOC容器中的映射,
我们可以通过 AbstractBeanDefinition为入口,看到IOC容器进行索引查询和操作
通过IOC对配置信息的解析,IOC容器大志完成了管理Bean对象的准备工作,即初始化过程,但是最为重要的依赖注入还没有发生,现在在IOC容器中BeanDefinition存储的只是一些静态信息,接下来需要向容器中注册Bean定义信息才能完成全部IOC容器的初始化过程

16、分配注册策略

DefaultBeanDefinitionDocumentReader类中parseDefaultElement()完成了对Document对象解析后得到了封装BeanDefinition的BeanDenitionHold对象,调用BeanDefinitionReaderUtils的registerBeanDefinition方法向IOC容器注册解析的BeanDefinition时,真正完成注册功能的是DefaultListableBeanFactory

17、向容器注册

DefaultListableBeanFactory中使用一个HashMap的集合对象存放IOC容器中注册解析的BeanDefinition

至此,Bean配置信息中配置Bean被解析后,已经注册到IOC容器中,以BeanDefinition的形式存在,BeanDefinition可以被检索。

时序图

在这里插入图片描述

基于 Annotation 的 IOC 初始化

注解

注解有两类,类级别的和类内部的。
类级别的:如@Component、@Repository、@Controller、@Service
类内部级别的:如@Autowire、@Value、@Resource

定位Bean扫描路径

管理注解Bean定义的容器:
AnnotationConfigApplicationContext
AnnotationConfigWebApplicationContex
两者相差无几

分析下AnnotationConfigApplicationContext源码:
通过源码分析,我们可以看啊到 Spring 对注解的处理分为两种方式:
1)、直接将注解 Bean 注册到容器中
可以在初始化容器时注册;也可以在容器创建之后手动调用注册方法向容器注册,然后通过手动刷新容
器,使得容器对注册的注解 Bean 进行处理。
2)、通过扫描指定的包及其子包下的所有类
在初始化注解容器时指定要自动扫描的路径,如果容器创建以后向给定路径动态添加了注解 Bean,则
需要手动调用容器扫描的方法,然后手动刷新容器,使得容器对所注册的 Bean 进行处理。
接下来,将会对两种处理方式详细分析其实现过程。

读取 Annotation 元数据

当创建注解处理容器时,如果传入的初始参数是具体的注解 Bean 定义类时,注解容器读取并注册。
1)、AnnotationConfigApplicationContext 通过调用注解 Bean 定义读取器
AnnotatedBeanDefinitionReader 的 register()方法向容器注册指定的注解 Bean,
从源码我们可以看出,注册注解 Bean 定义类的基本步骤:
a、需要使用注解元数据解析器解析注解 Bean 中关于作用域的配置。
b、使用 AnnotationConfigUtils 的 processCommonDefinitionAnnotations()方法处理注解 Bean 定
义类中通用的注解。
c、使用 AnnotationConfigUtils 的 applyScopedProxyMode()方法创建对于作用域的代理对象。
d、通过 BeanDefinitionReaderUtils 向容器注册 Bean。
2)、AnnotationScopeMetadataResolver 解析作用域元数据
AnnotationScopeMetadataResolver 通过 resolveScopeMetadata()方法解析注解 Bean 定义类的作用域元信息,即判断注册的 Bean 是原生类型(prototype)还是单态(singleton)类型
3)、AnnotationConfigUtils 处理注解 Bean 定义类中的通用注解
AnnotationConfigUtils 类的 processCommonDefinitionAnnotations()在向容器注册 Bean 之前,首先对注解 Bean 定义类中的通用 Spring 注解进行处理
4)、AnnotationConfigUtils 根据注解 Bean 定义类中配置的作用域为其应用相应的代理策略
AnnotationConfigUtils 类的 applyScopedProxyMode()方法根据注解 Bean 定义类中配置的作用域@Scope 注解的值,为 Bean 定义应用相应的代理模式,主要是在 Spring 面向切面编程(AOP)中使用。
5)、BeanDefinitionReaderUtils 向容器注册 Bean
BeanDefinitionReaderUtils 主要是校验 BeanDefinition 信息,然后将 Bean 添加到容器中一个管理BeanDefinition 的 HashMap 中。

扫描指定包并解析为 BeanDefinition

当创建注解处理容器时,如果传入的初始参数是注解 Bean 定义类所在的包时,注解容器将扫描给定的
包及其子包,将扫描到的注解 Bean 定义载入并注册。
1)、ClassPathBeanDefinitionScanner 扫描给定的包及其子包AnnotationConfigApplicationContext 通 过 调 用 类 路 径 Bean 定 义 扫 描 器
ClassPathBeanDefinitionScanner 扫描给定包及其子包下的所有类
类路径 Bean 定义扫描器 ClassPathBeanDefinitionScanner 主要通过 findCandidateComponents()
方法调用其父类 ClassPathScanningCandidateComponentProvider 类来扫描获取给定包及其子包下
的类。
2)、ClassPathScanningCandidateComponentProvider 扫描给定包及其子包的类
ClassPathScanningCandidateComponentProvider 类的 findCandidateComponents()方法具体实现扫描给定类路径包的功能,

注册注解 BeanDefinition

AnnotationConfigWebApplicationContext 是 AnnotationConfigApplicationContext 的 Web 版,它们对于注解 Bean 的注册和扫描是基本相同的,但是 AnnotationConfigWebApplicationContext
对注解 Bean 定义的载入稍有不同,AnnotationConfigWebApplicationContext 注入注解 Bean 定义

IOC 容器初始化小结

总结一下 IOC 容器初始化的基本步骤:
1、初始化的入口在容器实现中的 refresh()调用来完成。
2、对 Bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition(),
其中的大致过程如下:通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader
是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统,URL 等方式来定为资源位置。如果是 XmlBeanFactory 作为 IOC 容器,那么需要为它指定 Bean 定义的资源,也 就 是 说 Bean 定 义 文 件 时 通 过 抽 象 成 Resource 来 被 IOC 容 器 处 理 的 , 容 器 通 过BeanDefinitionReader 来 完 成 定 义 信 息 的 解 析 和 Bean 信 息 的 注 册 , 往 往 使 用 的 是XmlBeanDefinitionReader 来 解 析 Bean 的 XML 定 义 文 件 - 实 际 的 处 理 过 程 是 委 托 给BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用BeanDefinition对象来表示-这个名字可以让我们想到loadBeanDefinition(),registerBeanDefinition()这些相关方法。它们都是为处理 BeanDefinitin 服务的,容器解析得到 BeanDefinition 以后,需要把它在 IOC 容器中注册,这由 IOC 实现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器
内部维护的一个 HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IOC 容器持有Bean 信息的场所,以后对 Bean 的操作都是围绕这个 HashMap 来实现的。
然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IOC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。
Spring本身提供了对声明式载入web应用程序用法的应用程序上下文,并将其存储在ServletContext中的框架实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值