一文浅析Spring的IOC原理

1:简介

spring是一个轻量级的开源框架,核心是IOC和AOP。

2:IOC

I- OC: 控制反转,也就是将对象的创建和对象之间的依赖关系较给Spring

  • 目的:为了降低耦合性,以达到可以不修改源码就能对项目进行一定的操作,对开发来说就是给你一个框架,你照我的逻辑编写各类配置和代码。

2.1 ioc容器

ioc图解(借鉴网图)

在这里插入图片描述

  • 谁控制谁,控制什么:传统程序,我们想要在一个对象中调用另一个对象的方法,一般会在该对象中创建实例对象,或者采取工厂模式。而spring ioc是程序主动去创建依赖对象。为此IoC专门准备了一个容器(这就是我们说的ioc容器,又或者直接说是对象工厂)去创建这些对象,即由Ioc容器来控制对象的创建以及外部资源获取(不只是对象包括比如文件等)。

  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象:由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转,依赖对象的获取被反转了。

那我们怎么去获取?两种方式:

【1】BeanFactory, ioc容器内部的使用接口,算是一种机制或者说根接口?
特点:加载配置文件的时候不会去创建对象,在获取对象(使用)的时候才会创建对象
【2】ApplicationContext配合它的实现类。这个接口是上述接口的子接口,既然是子接口说明功能更加完善。而这个子接口有很多的实现类。ClassPathXmlApplicationContext、FileSystemXmlApplicationContext等
特点:在加载配置文件时就会创建对象

在这里插入图片描述

2.2 IoC容器的初始化过程

P:以前有种方法是XmlBeanFactory(已经被弃用了,但是资料挺多的,大家可以自行查阅)
P:现在推荐使用的是ApplicationContext:如下,调用父类的构造器并设置resource,这两步是做先手准备,从refresh开始真正的初始化

 public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
       super(parent);  //调用父类构造器方法设置好资源加载器
       this.setConfigLocations(configLocations); 设置好 Resource
       if (refresh) {
           this.refresh();  //重建IOC容器,并进行初始化
       }
   }

2.2.1 资源的定位

refresh直接进去

public void refresh() throws BeansException, IllegalStateException {  
       synchronized (this.startupShutdownMonitor) {  
           //调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识  
           prepareRefresh();  
           //告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从  
          //子类的refreshBeanFactory()方法启动  
           ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  
           //为BeanFactory配置容器特性,例如类加载器、事件处理器等  
           prepareBeanFactory(beanFactory);  
           try {  
               //为容器的某些子类指定特殊的BeanPost事件处理器  
               postProcessBeanFactory(beanFactory);  
               //调用所有注册的BeanFactoryPostProcessor的Bean  
               invokeBeanFactoryPostProcessors(beanFactory);  
               //为BeanFactory注册BeanPost事件处理器.  
               //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件  
               registerBeanPostProcessors(beanFactory);  
               //初始化信息源,和国际化相关.  
               initMessageSource();  
               //初始化容器事件传播器.  
               initApplicationEventMulticaster();  
               //调用子类的某些特殊Bean初始化方法  
               onRefresh();  
               //为事件传播器注册事件监听器.  
               registerListeners();  
               //初始化所有剩余的单态Bean.  
               finishBeanFactoryInitialization(beanFactory);  
               //初始化容器的生命周期事件处理器,并发布容器的生命周期事件  
               finishRefresh();  
           }  
           catch (BeansException ex) {  
               //销毁以创建的单态Bean  
               destroyBeans();  
               //取消refresh操作,重置容器的同步标识.  
               cancelRefresh(ex);  
               throw ex;  
           }  
       }  
   }

进入obtainFreshBeanFactory,调用refreshBeanFactory方法,先保证有一个干净的beanfactory容器。

主要就是先销毁,再重建
protected final void refreshBeanFactory() throws BeansException {  
       if (hasBeanFactory()) {//如果已经有容器,销毁容器中的bean,关闭容器  
           destroyBeans();  
           closeBeanFactory();  
       }  
       try {  
            //创建IoC容器  
            DefaultListableBeanFactory beanFactory = createBeanFactory();  
            beanFactory.setSerializationId(getId());  
           //对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等  
           customizeBeanFactory(beanFactory);  
           //调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器  
           loadBeanDefinitions(beanFactory);  
           synchronized (this.beanFactoryMonitor) {  
               this.beanFactory = beanFactory;  
           }  
       }  
       catch (IOException ex) {  
           throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);  
       }  
   }

loadBeanDefinition()

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {  
    ……  
    //实现父类抽象的载入Bean定义方法  
    @Override  
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {  
        //创建XmlBeanDefinitionReader,即创建Bean读取器,容器使用该读取器读取Bean定义资源  
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  
        //为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的  
        //祖先父类AbstractApplicationContext继承DefaultResourceLoader,因此,容器本身也是一个资源加载器  
       beanDefinitionReader.setResourceLoader(this);  
       //为Bean读取器设置SAX xml解析器  
       beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  
       //当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制  
       initBeanDefinitionReader(beanDefinitionReader);  
       //Bean读取器真正实现加载的方法  
       loadBeanDefinitions(beanDefinitionReader);  
   }  
    protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
        reader.setValidating(this.validating);
    }

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = this.getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }

        String[] configLocations = this.getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }

    }
}

定位我们的资源

    public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        Assert.notNull(paths, "Path array must not be null");
        Assert.notNull(clazz, "Class argument must not be null");
        this.configResources = new Resource[paths.length];

        for(int i = 0; i < paths.length; ++i) {
            this.configResources[i] = new ClassPathResource(paths[i], clazz);
        }

        this.refresh();
    }

通过ReasourceLoader来完成资源文件的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了ResourceLoader的实现,可以通过类路径、文件系统、URL等方式来定位资源

2.2.2 资源的载入

如果XmlBeanFactory作为IOC容器,那么需要为它指定Bean定义的资源,也就是说Bean定义文件是通过抽象成Resource来被IOC容器处理,容器通过BeanDefinitionReader来完成定义信息的解析和Bean信息的注册,往往使用XmlBeanDefinitionReader来解析Bean的XML定义文件—实际的处理过程是委托给BeanDefinitionParserDelegate来完成的,从而得到Bean的定义信息,这些信息在Spring中使用BeanDefinition来表示(这个名字可以让我们想到loadBeanDefinition()、registerBeanDefinition()这些相关的方法,他们都是为处理BeanDefinition服务的)。

同理:虽然xml有些过时,但是意思一样

不同的BeanDefinationReader拥有不同的功能,如果我们要读取xml配置元信息,那么可以使用XmlBeanDefinationReader。如果我们要读取properties配置文件,那么可以使用PropertiesBeanDefinitionReader加载。而如果我们要读取注解配置元信息,那么可以使用 AnnotatedBeanDefinitionReader加载。我们也可以很方便的自定义BeanDefinationReader来自己控制配置元信息的加载。总的来说,BeanDefinationReader的作用就是加载配置元信息,并将其转化为内存形式的BeanDefination

2.2.3 资源的注册

那么我们需要某一个对象的时候,去哪里找到对应的BeanDefination呢?这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。

  • 注册:解析得到BeanDefinition以后,需要在IOC容器中注册,这由IOC实现BeanDefinitionRegister接口来实现,注册过程就是在IOC容器内容维护一个HashMap来保存得到的BeanDefinition的过程,这个HashMap是IOC容器持有Bean信息的场所,以后Bean的操作都是围绕这个HashMap来实现
    它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefination。
  • 修正:BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,主要负责对注册到BeanDefinationRegistry中的一个个的BeanDefination进行一定程度上的修改与替换。例如我们的配置元信息中有些可能会修改的配置信息散落到各处,不够灵活,修改相应配置的时候比较麻烦,这时我们可以使用占位符的方式来配置。例如配置Jdbc的DataSource连接

3、依赖注入

依赖注入的场景

spring在创建beanFactory实例化类的时候,先加载类,然后调用类的构造器进行初始化,在完成初始化后进行依赖注入(常见的就是set注入,通过执行set方法)

具体分析

  • 用户第一次调用getBean()方法时,IOC容器触发依赖注入。
  • 当用户在配置文件中将元素配置了lazy-init=false属性时,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。

获取过程

  • getBean方法
  • getbean进来调用doGetBean
  • transformedBeanName判断有没有别名之后
  • getSingleton,去单例缓存池中获取Bean的实例,没有去二级。只要两个里面有实力,就取出来:singletonFactory.getObject()
  • dependsOn,有没有循环依赖,在springboot里面经常报错,需要配置
  • beforeSingletonCreation就把你的对象标记为了早期暴露的对象,提前暴露对象用于创建Bean的实例
  • resolveBeforeInstantiation代理Bean定义信息
  • doCreateBean然后找到createBeanInstance这个方法,在这里面它将为你创建你的Bean实例信息(Bean的实例)。这里面回去检测依赖,你依赖另一个bean,他就会去创建另一个,然后另一个bean依赖你,然后你还在创建当中,这样就出错了
  • 将创建好的Bean放入缓存,addSingletonFactory方法就是将你创建好的Bean放入三级缓存中,并且移除早期暴露的对象。
  • 通过populateBean给属性赋值,我们知道,创建好的对象,并不是一个完整的对象,里面的属性还没有被赋值。所以这个方法就是为创建好的Bean为它的属性赋值。并且调用了我们实现的的XXXAware接口进行回调初始化,然后调用我们实现的Bean的后置处理器,给我们最后一次机会去修改Bean的属性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值