彻底搞懂Spring IOC

IOC(Inversion of Control),即控制反转,它是一种设计思想

  • 控制(谁控制谁)

之前通过new()进行创建对象,主动去创建依赖对象,而现在通过IOC容器负责实例化、配置和组装 bean。

  • 反转(反转什么)

之前对象主动直接去获取依赖对象,而现在通过Ioc容器查找及注入(DI) 依赖对象。

  • 依赖注入

DI(Dependency Injection),即依赖注入,是IOC具体的实现,IOC容器动态的将某个依赖注入到对象之中

Spring Bean 在Spring中,Spring IoC 容器管理的对象称为 bean

Spring IOC

Spring提供了两种容器:BeanFactoryApplicationContext

BeanFactory 接口提供配置框架和基本功能,能够管理任何类型的对象。

ApplicationContextBeanFactory的子接口 通过上图我们可以看到ApplicationContext还继承了其他接口,增加了消息资源处理(用于国际化)、事件发布、资源访问等功能。

初始化流程

通过前面的内容,我们可以了解到Spring IOC 容器,主要负责管理 Bean 的实例化、配置和组装,接下来我们看看Spring IOC的初始化过程(以xml配置为例)

xml 复制代码
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="clientMessageReq" class="com.tx.general.rtnp.ws.req.ClientMessageReq" scope="prototype">

    </bean>

</beans>
java 复制代码
public static void main(String[] args) {

    ApplicationContext context = new ClassPathXmlApplicationContext("clientMessageReq.xml");
    ClientMessageReq clientMessageReq = context.getBean("clientMessageReq", ClientMessageReq.class);
    clientMessageReq.setParams("text");
    System.out.println(JSON.toJSONString(clientMessageReq));
}

通过new ClassPathXmlApplicationContext()方法创建了一个IOC容器,我们主要这个构造方法开始,分析一下IOC容器的初始化过程。

java 复制代码
/**
 * 构造一个ClassPathXmlApplicationContext对象。
 *
 * @param configLocations 配置文件路径数组
 * @param refresh 是否刷新上下文
 * @param parent 父上下文对象,可能为空
 * @throws BeansException 如果在创建对象时发生错误,抛出BeansException异常
 */
public ClassPathXmlApplicationContext(
		String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
		throws BeansException {
                
  // 设置Bean资源加载器
	super(parent);
        
  // 设置Xml配置路径
	setConfigLocations(configLocations);
	if (refresh) {
		refresh();
	}
}

重点就是refresh()方法,它是IOC容器初始化的核心

java 复制代码
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

      // ▲1.调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 
      prepareRefresh();

      // ▲2.初始化BeanFactory
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // ▲3.准备Bean工厂以在上下文中使用
      prepareBeanFactory(beanFactory);

      try {
         // ▲4.为子类设置BeanFactory的后置处理器
         postProcessBeanFactory(beanFactory);

         StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
         
         // ▲5.调用实现了BeanFactoryPostProcessor的类
         invokeBeanFactoryPostProcessors(beanFactory);

         // ▲6.注册bean初始化时候的processor
         registerBeanPostProcessors(beanFactory);
         beanPostProcess.end();

         // ▲7.对上下文的消息源进行初始化
         initMessageSource();

         // ▲8.初始化上下文的事件机制
         initApplicationEventMulticaster();

         // ▲9.初始化其他特殊的Bean
         onRefresh();

         // ▲10.注册实现了ApplicationListener的listener
         registerListeners();

         // ▲11.初始化配置为lazy-init=false的bean
         finishBeanFactoryInitialization(beanFactory);

         // ▲12.触发所有监听ContextRefreshedEvent事件的listener
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         destroyBeans();

         cancelRefresh(ex);

         throw ex;
      }

      finally {
        
         resetCommonCaches();
         contextRefresh.end();
      }
   }
}

可以看到容器的创建和初始化就在obtainFreshBeanFactory()方法,重点来看一下

  • obtainFreshBeanFactory()初始化BeanFactory
java 复制代码
   protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
      refreshBeanFactory();
      return getBeanFactory();
   }

	/**
	 * 刷新Bean工厂 委托模式
	 */
	@Override
	protected final void refreshBeanFactory() throws BeansException {
           
       // 如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器
	    if (hasBeanFactory()) {
	        destroyBeans();
	        closeBeanFactory();
	    }
	    try {
	        // 创建DefaultListableBeanFactory对象
	        DefaultListableBeanFactory beanFactory = createBeanFactory();
	        // 设置序列化ID
	        beanFactory.setSerializationId(getId());
	        // 自定义Bean工厂
	        customizeBeanFactory(beanFactory);
	        // 加载定义的Bean
	        loadBeanDefinitions(beanFactory);
	        // 将beanFactory赋值给类成员变量
	        this.beanFactory = beanFactory;
	    }
	    catch (IOException ex) {
	        // 解析bean定义源时发生I/O错误抛出ApplicationContextException异常
	        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	    }
	}

定位Resource(bean的Xml)

  • AbstractXmlApplicationContext#loadBeanDefinitions() 加载BeanDefinition
java 复制代码
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {

   // 创建XmlBeanDefinitionReader读取器
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
   
   beanDefinitionReader.setEnvironment(this.getEnvironment());
   
   // 设置Spring资源加载器
   beanDefinitionReader.setResourceLoader(this);
   beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

   // 读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制
   initBeanDefinitionReader(beanDefinitionReader);

   // 通过beanDefinitionReader加载BeanDefinitions 
   loadBeanDefinitions(beanDefinitionReader);
}
  • AbstractXmlApplicationContext#loadBeanDefinitions() 定位资源,以Resource的形式去加载资源
java 复制代码
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    
    // 获得Bean配置文件的资源位置
    Resource[] configResources = getConfigResources();    
    if (configResources != null) {
        // 读取AbstractBeanDefinitionReader中定位的资源
        reader.loadBeanDefinitions(configResources);
    }
    // 获取ClassPathXmlApplicationContext构造方法中setConfigLocations方法设置的资源  
    String[] configLocations = getConfigLocations();    
    if (configLocations != null) {
        //通过AbstractBeanDefinitionReader#loadBeanDefinitions()方法,以Resource的形式去加载资源。
        reader.loadBeanDefinitions(configLocations);
    }
}

载入BeanDefinition

经过上面的步骤我们定义的资源以及容器本身需要的资源全部加载到reader中,通过XmlBeanDefinitionReader#loadBeanDefinitions()方法会得到一个XML文件的InputStream,然后 将Bean定义资源转换成Document对象。最后在registerBeanDefinitions()方法中完成对解析

java 复制代码
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   // 创建DefaultBeanDefinitionDocumentReader对象
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   int countBefore = getRegistry().getBeanDefinitionCount();
   // 通过DefaultBeanDefinitionDocumentReader对象的registerBeanDefinitions()方法解析Document
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

而具体的解析过程由BeanDefinitionParserDelegate()来实现

java 复制代码
protected void doRegisterBeanDefinitions(Element root) {
    // ... 代码部分省略
    
    // 执行XML预处理操作
    preProcessXml(root);

    // 解析Bean定义
    parseBeanDefinitions(root, this.delegate);

    // 执行XML后处理操作
    postProcessXml(root);
    
    // ... 代码部分省略
}

通过BeanDefinitionParserDelegate#parseBeanDefinitionElement()方法,得到结果BeanDefinitionHolderBeanDefinitionHolder是BeanDefinition的封装类,封装了BeanDefinition,Bean的名字、别名)

java 复制代码
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // 对给定的<bean>标签进行解析,得到BeanDefinitionHolder对象
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // 向IOC容器注册解析到BeanDefinition
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      
     // 在BeanDefinition像IOC容器注册完以后,发送事件消息
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

BeanDefiniton注册

最终Xml配置被解析成了BeanDefinitionHolder类,通过DefaultListableBeanFactory#registerBeanDefinition()方法,将BeanDefinition注册到beanDefinitionsMap

java 复制代码
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {

   Assert.hasText(beanName, "Bean name must not be empty");
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");

   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
      catch (BeanDefinitionValidationException ex) {
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Validation of bean definition failed", ex);
      }
   }

   BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
   if (existingDefinition != null) {
      if (!isAllowBeanDefinitionOverriding()) {
         throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
      }
      else if (existingDefinition.getRole() < beanDefinition.getRole()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
         if (logger.isInfoEnabled()) {
            logger.info("Overriding user-defined bean definition for bean '" + beanName +
                  "' with a framework-generated bean definition: replacing [" +
                  existingDefinition + "] with [" + beanDefinition + "]");
         }
      }
      else if (!beanDefinition.equals(existingDefinition)) {
         if (logger.isDebugEnabled()) {
            logger.debug("Overriding bean definition for bean '" + beanName +
                  "' with a different definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace("Overriding bean definition for bean '" + beanName +
                  "' with an equivalent definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      if (hasBeanCreationStarted()) {
         // Cannot modify startup-time collection elements anymore (for stable iteration)
         synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            removeManualSingletonName(beanName);
         }
      }
      else {
         // Still in startup registration phase
         this.beanDefinitionMap.put(beanName, beanDefinition);
         this.beanDefinitionNames.add(beanName);
         removeManualSingletonName(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }

   if (existingDefinition != null || containsSingleton(beanName)) {
      resetBeanDefinition(beanName);
   }
   else if (isConfigurationFrozen()) {
      clearByTypeCache();
   }
}

至此,IOC容器完成了初始化,其内部使用ConcurrentHashMap(Spring IOC容器本质)保存了BeanDefinition信息

总结

Spring IOC 初始化可以分为三个步骤

  • 通过 ResourceLoader 来完成资源文件位置的定位(Resource)
  • 将Resource定位好的资源载入到BeanDefinition(通过 BeanDefinitionReader来完成定义信息的解析)
  • 将BeanDefiniton注册到容器中(通过BeanDefinitionRegistry接口,将BeanDefinition保存到ConcurrentHashMap中)

Spring IOC容器管理了定义的各种Bean对象及其相互的关系,降低了耦合度

相关推荐
柏油5 小时前
MySQL InnoDB 行锁
数据库·后端·mysql
咖啡调调。5 小时前
使用Django框架表单
后端·python·django
白泽talk5 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务
摆烂工程师5 小时前
全网最详细的5分钟快速申请一个国际 “edu教育邮箱” 的保姆级教程!
前端·后端·程序员
yuren_xia5 小时前
IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤
spring·mvc·intellij-idea
一只叫煤球的猫6 小时前
你真的会用 return 吗?—— 11个值得借鉴的 return 写法
java·后端·代码规范
Asthenia04126 小时前
HTTP调用超时与重试问题分析
后端
颇有几分姿色6 小时前
Spring Boot 读取配置文件的几种方式
java·spring boot·后端
AntBlack6 小时前
别说了别说了 ,Trae 已经在不停优化迭代了
前端·人工智能·后端
人生导师yxc6 小时前
Spring MVC
java·spring·mvc