2023年再不会 IOC 源码,就要被淘汰了

Posted 爱敲代码的小黄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023年再不会 IOC 源码,就要被淘汰了相关的知识,希望对你有一定的参考价值。

  • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
  • 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:hls1793929520,和大家一起学习,一起进步👀

文章目录

Spring IOC源码解析

一、引言

对于Java开发者而言,关于 Spring ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

本期 Spring 源码解析系列文章,将带你领略 Spring 源码的奥秘

本期源码文章吸收了之前 Kafka 源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。

废话不多说,发车!

本文流程图可关注公众号:爱敲代码的小黄,回复:IOC 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用

二、Spring启动配置

首先,我们要引入 Spring 的依赖,这里是用的 4.3.11.RELEASE 版本的,不同的版本源码较有差异,但整体业务逻辑不变

这里讲个小细节,如果你在面试,这里一定要提前给面试官说好你阅读的源码版本,有三方面好处:
第一:避免不同版本的Spring源码不一致导致和面试官的分歧问题
第二:让面试官感觉你小子是真的阅读过源码,就算你说的逻辑和面试官有分歧,面试官第一反应会是源码版本差异导致的
第三:装逼使用…

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.11.RELEASE</version>
</dependency>

创建接口 MessageService

public interface MessageService 
    String getMessage();

实现类 MessageServiceImpl

public class MessageServiceImpl implements MessageService 
    public String getMessage() 
        return "hello world";
    

配置文件 application.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="messageService" class="cn.hls.spring.MessageServiceImpl"/>
</beans>

启动类 SpringStart

public class SpringStart 
    public static void main(String[] args) 
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

        System.out.println("context 启动成功");

        
        MessageService messageService = context.getBean(MessageService.class);

        // 输出: hello world
        System.out.println(messageService.getMessage());
    

最终输出结果:

context 启动成功
hello world

通过上述代码,我们可以看到,SpringIOC 完全代替了我们之前的 new 的功能,将创建实例交由 Spring 来管理。

三、IOC 源码剖析

Spring 是如何管理的呢?源码背后又有什么小技巧呢?今天我们一起来看一下 IOC 源码的解析

为了阅读性,我们将以 xml 文件的配置来阅读源码

首先,我们从 ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); 这一行入手,看其到底执行了什么

我们 debug 点进去可以看到共分为了三部分:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) 
        refresh();
    

简单来说,这三部分的作为分别是:

  • super(parent):调用父类的构造方法,创建 PathMathingResourcePatternResolver 解析配置文件
  • setConfigLocations(configLocations):设置 configLocations(配置文件路径) 到当前应用程序中
  • refresh():解析配置文件、完成bean的注册、实例化、初始化、代理等一系列的工作最终创建出一个 Bean 实例

我们一起来看一下 refresh 到底做了什么

1、prepareRefresh

**整体简介:**做容器刷新前的准备工作

  • 设置容器的启动时间:this.startupDate = System.currentTimeMillis();
  • 设置活跃状态为 trueactive.set(true)
  • 设置关闭状态为 falseclosed.set(false)
  • 获取 Environment 对象,并加载当前系统的属性值到 Environment 对象中
  • 准备监听器和事件的集合对象,默认为空的集合:earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();

这个方法不重要,也不必要去深入了解,知道做了容器刷新的准备工作即可。

2、obtainFreshBeanFactory

**整体简介:**创建容器,并且完成配置文件的加载

重要的来了,这个方法是比较重要的,一定要记住

首先该方法分为了以下几部分:

  • refreshBeanFactory:解析我们的 application.xml 文件并生成 BeanDefinition 注册至 DefaultListableBeanFactorybeanDefinitionMap
  • getBeanFactory:获取工厂bean

2.1 refreshBeanFactory

protected final void refreshBeanFactory() throws BeansException 
    // 创建 BeanFactory,类型为 DefaultListableBeanFactory
    DefaultListableBeanFactory beanFactory = createBeanFactory();
    beanFactory.setSerializationId(getId());
    customizeBeanFactory(beanFactory);
    loadBeanDefinitions(beanFactory);
    synchronized (this.beanFactoryMonitor) 
        this.beanFactory = beanFactory;
    

上述我们必须记住这个工厂类:DefaultListableBeanFactory,甚至要做到背诵+默写的程度

其次,最重要的就属 loadBeanDefinitions(beanFactory) 方法了

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
   // 创建XmlBeanDefinitionReader,这个是我们xml文件的解析器
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
   beanDefinitionReader.setEnvironment(this.getEnvironment());
   beanDefinitionReader.setResourceLoader(this);
   beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
   initBeanDefinitionReader(beanDefinitionReader);
   
   // 加载BeanDefinitions
   loadBeanDefinitions(beanDefinitionReader);

我们继续深入看其做了什么

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException 
   // 拿到xml文件的地址
   Resource[] configResources = getConfigResources();
   if (configResources != null) 
      reader.loadBeanDefinitions(configResources);
   
   String[] configLocations = getConfigLocations();
   
   if (configLocations != null) 
      reader.loadBeanDefinitions(configLocations);
   

继续往下看,一直到 loadBeanDefinitions

public int loadBeanDefinitions(String location, Set<Resource> actualResources)
   ResourceLoader resourceLoader = getResourceLoader();
  
   if (resourceLoader instanceof ResourcePatternResolver) 
      try 
         Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
         // 直接看这行,其余不重要
         int loadCount = loadBeanDefinitions(resources);
         if (actualResources != null) 
            for (Resource resource : resources) 
               actualResources.add(resource);
            
         
         return loadCount;
      
    else 
      Resource resource = resourceLoader.getResource(location);
      int loadCount = loadBeanDefinitions(resource);
      if (actualResources != null) 
         actualResources.add(resource);
      
      return loadCount;
   

XmlBeanDefinitionReader388

// 将路径封装成一个DOC格式
Document doc = doLoadDocument(inputSource, resource);
// 继续注册
return registerBeanDefinitions(doc, resource);

XmlBeanDefinitionReader505

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException 
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   int countBefore = getRegistry().getBeanDefinitionCount();
   // 其余不重要,直接看这行
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   return getRegistry().getBeanDefinitionCount() - countBefore;


public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 
   this.readerContext = readerContext;
   // 获取根节点
   Element root = doc.getDocumentElement();
   // 从根节点开始解析遍历
   doRegisterBeanDefinitions(root);


protected void doRegisterBeanDefinitions(Element root) 
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) 
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) 
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			
		

		preProcessXml(root);
    	// 直接看这里,其余不重要
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 
   if (delegate.isDefaultNamespace(root)) 
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) 
         Node node = nl.item(i);
         if (node instanceof Element) 
            Element ele = (Element) node;
            if (delegate.isDefaultNamespace(ele)) 
               // 直接看这里
               parseDefaultElement(ele, delegate);
            
            else 
               delegate.parseCustomElement(ele);
            
         
      
   
   else 
      delegate.parseCustomElement(root);
   


// 根据不同的配置走不同的分支,配置:import、alias、bean、beans
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) 
			importBeanDefinitionResource(ele);
		
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) 
			processAliasRegistration(ele);
		
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) 
			processBeanDefinition(ele, delegate);
		
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) 
			// recurse
			doRegisterBeanDefinitions(ele);
		
	

我们这里只看 bean 的配置,其余的读者有兴趣可以自己去 debug

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 
   // 解析各种xml标签去生成对应的BeanDefinition,读者有兴趣可以自己看一下
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) 
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try 
         // 重点:正式开始注册
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   


public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			 

		// 重点,开始注册
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// 注册别名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) 
			for (String alias : aliases) 
				registry.registerAlias(beanName, alias);
			
		
	

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 
		// 看一下原来的beanDefinitionMap是不是已经有该 beanName 了
  		// 如果已经存在,我们要排抛出异常(Spring不允许覆盖)
		BeanDefinition oldBeanDefinition = this.beanDefinitionMap
beanDefinitionMap.get(beanName);
    if (oldBeanDefinition != null) 
        if (!isAllowBeanDefinitionOverriding()) 
            throw new BeanDefinitionStoreException()
        
    
		if (oldBeanDefinition != null) 
			this.beanDefinitionMap.put(beanName, beanDefinition);
		else 
             // 重点在这:将我们的beanName与beanDefinition放至beanDefinitionMap中
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);

		
	

到这里,基本就结束了,我们来回顾一下 refreshBeanFactory 的业务:

  • 通过我们传递的xml 文件的路径,利用 documentLoader 将其封装成 Document 格式
  • 创建 BeanDefinitionDocumentReader 来正式解析 xml 文件并找到文件的 root
  • 根据 root 扫描遍历,对不同配置的标签(import、alias、bean、beans)走不同的逻辑判断
  • 将当前的标签各属性进行组装成 beanDefinition,调用 DefaultListableBeanFactory 进行注册
  • 根据 BeanName 查询该 beanDefinition 是否被注册过,如果被注册过,则直接抛出异常(Spring不允许覆盖
  • 如果没有注册过,则将 BeanNamebeanDefinition 注册至 DefaultListableBeanFactorybeanDefinitionMap
  • 最后,如果该 beanDefinition 含有别名,也要将别名进行注册,至于为什么注册别名,可见:附录1

这里可能有人会说,小黄小黄,按你之前解析 kafka 的套路,肯定会分析 beanDefinition 的形成的,现在怎么偷懒不分析了,是不是看不懂~

答:之前分享的 kafka 系列的文章,大家都知道分享的很细,但是我们细细品味一下,我们读源码到底为了什么,以及如何去读、如何有效的读、如何快速的读,我相信每一个人心中都有一套读源码的方式。至于哪一种阅读方式更为合理,后面博主准备单独出一篇文章来讲解,或者你可以私信我,告知我你的读源码的方式,一起加油、一起学习。

3、prepareBeanFactory

整体简介: beanFactory 的准备工作,对各种属性进行填充

这个方法不重要,也不必要去深入了解,知道做了 beanFactory 的填充即可

不过,这里记住,beanFactory 的类一定要记清楚,是 DefaultListableBeanFactory ,不多说直接 背诵+默写

4、postProcessBeanFactory

整体简介: 默认没有实现,留给子类进行实现操作

5、invokeBeanFactoryPostProcessors

整体简介: 可以自由扩展,通过实现BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor 接口,对 beanFactory 里面的 BeanDefinition 进行修改

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) 
   // 找到当前 beanDefinitionMap 中`BeanFactoryPostProcessor` 和 `BeanDefinitionRegistryPostProcessor`接口的实现
   // 若这些实现有对应的order(顺序),则排序之后依次调用
   PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

   if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) 
      beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
      beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
   

温馨小提示:这里有的小伙伴可能对 BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor 接口不熟悉,我们将此处的讲解放至:附录2

6、registerBeanPostProcessors

整体简介: 完成 spring 自带或者用户自定义的 BeanPostProcessor 的解析

protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) 
   // 实例化并且注册所有的beanPostProcessor
   PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);

这里的操作实际上和我们上面 invokeBeanFactoryPostProcessors 里面很像,都是对实现一些特定接口的类做加载,但需要注意的是:对于实现 BeanPostProcessor 接口的来说,我们不会在此立即调用,会在 Bean 初始化方法前后调用。

对了,提前剧透一下,我们响当当的 AOP 也是在这里实现的,后续我们也会讲的。

温馨小提示:这里有的小伙伴可能对 BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor 接口不熟悉,我们将此处的讲解放至:附录3

7、initMessageSource

整体简介: Spring 中国际化的功能

8、initApplicationEventMulticaster

整体简介: 初始化事件广播器

9、onRefresh

整体简介:spring 中默认没有任何实现,模板方法,但是在 springboot 中启动了 web 容器

10、registerListeners

整体简介: 注册监听器,为了方便接受广播的事件

11、finishBeanFactoryInitialization

整体简介:完成所有非懒加载的单例对象的实例化操作,从此方法开始进行对象的创建,包含了实例化,初始化,循环依赖,AOP等核心逻辑的处理过程,此步骤是最最核心且关键的点,要对其中的细节最够清楚

由于篇幅原因,博主会尽量挑选一些重要的地方进行分析。

protected Spring从成神到升仙系列 一2023年再不会动态代理,就要被淘汰了

2023年了,安卓开发为什么还没被淘汰?

2023年了,安卓开发为什么还没被淘汰?

拜托,都2023年了,再不懂Android Framework底层原理,真要被淘汰了

Spring IOC 容器源码分析 - 创建单例 bean 的过程

Spring IOC 容器源码分析 - 创建单例 bean 的过程