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

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

在这里插入图片描述

文章目录

  • Spring IOC源码解析
    • 一、引言
    • 二、Spring启动配置
    • 三、IOC 源码剖析
      • 1、prepareRefresh
      • 2、obtainFreshBeanFactory
        • 2.1 refreshBeanFactory
      • 3、prepareBeanFactory
      • 4、postProcessBeanFactory
      • 5、invokeBeanFactoryPostProcessors
      • 6、registerBeanPostProcessors
      • 7、initMessageSource
      • 8、initApplicationEventMulticaster
      • 9、onRefresh
      • 10、registerListeners
      • 11、finishBeanFactoryInitialization
        • 11.1 创建实例
        • 11.2 属性填充
        • 11.3 初始化逻辑
      • 12、finishRefresh
      • 13、销毁
    • 五、流程图
    • 六、总结
    • 七、附录
      • 1、注册别名的原因
      • 2、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor
      • 3、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor

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 void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   // 实例化剩下的单例对象
   beanFactory.preInstantiateSingletons();
}

public void preInstantiateSingletons(){
    	// 拿到我们之前存储的所有beanDefinition的名字
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);		
		// 触发单例bean的初始化,遍历集合的对象
		for (String beanName : beanNames) {
            // 如果beanName对应的bean不是FactoryBean,只是普通的bean,通过beanName获取bean实例
            getBean(beanName);
        }
}

public Object getBean(String name) throws BeansException {
    // 此方法是实际获取bean的方法,也是触发依赖注入的方法
    return doGetBean(name, null, null, false);
}

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){
    // 这里需要一步转换,这里的原因我们附录1提到过,这里不再过多讨论
    String beanName = transformedBeanName(name);
    
    // 提前检查单例缓存中是否有手动注册的单例对象,剧透一下(和循环依赖有关联)
    Object sharedInstance = getSingleton(beanName);
    
    // 当对象都是单例的时候会尝试解决循环依赖的问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常
    if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
    
    if (mbd.isSingleton()) {
        // 返回以beanName的(原始)单例对象,如果尚未注册,则使用singletonFactory创建并注册一个对象:
        sharedInstance = getSingleton(beanName, () -> {
            try {
                // 为给定的合并后BeanDefinition(和参数)创建一个bean实例
                // 这也是我们的核心方法
                return createBean(beanName, mbd, args);
            }
        });
}
          
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
    // 实际创建bean的调用
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
}
    
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
    // 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    // 对bean的属性进行填充,将各个属性值注入,其中,可能存在依赖于其他bean的属性,则会递归初始化依赖的bean
    populateBean(beanName, mbd, instanceWrapper);
    
    // 执行初始化逻辑
    exposedObject = initializeBean(beanName, exposedObject, mbd);
}

到这里我们先停一停,我们总结一下创建实例的一些步骤:

  • 拿到我们之前注册的 beanDefinitionNames,遍历整个 beanDefinitionNames,每一个 BeanName 生成一个对象

  • 我们需要进行名称转化,防止传入的是一个别名或其他的名称,利用转换后的别名去调用

  • 查询我们的单例缓存中是否已经存在该实例,如果存在直接返回即可

  • 如果不存在,则需要去根据该 beanDefinition 去生成对应的实例

  • 对于生成实例共有三个步骤:

    • 创建实例
    • 属性填充
    • 初始化逻辑
      • 实现 BeanPostProcessor 的前置方法
      • 对象的初始化方法
      • 实现 BeanPostProcessor 的后置方法

我们对于每个步骤都进行分析:

11.1 创建实例

对于实例创建,Spring 中创建 bean 的方式大致可分为三种:

  • 类名称 + 自定义 beanName
  • 工厂类名称+ 自定义工厂静态方法 + 自定义 beanName
  • 提前注册工厂bean,使用工厂bean + 工厂方法+自定义 beanName

可能大家有点懵,怎么这么多创建的方法,这里其实我们不需要太过于关注,只需要关注 类名称 + 自定义 beanName 这种方法即可,其余两种基本很少用到

对于 类名称 + 自定义 beanName 我们一般有两种构造方法:

  • 无参构造(常用)
  • 有参构造(不常用)

为了便于理解,我们这里只介绍无参构造

BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 根据当前的beanName拿到其 Class
    Class<?> beanClass = resolveBeanClass(mbd, beanName);
    
    // 前面的有参都不存在,则进行无参构造
    return instantiateBean(beanName, mbd);
}

protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
    // 获取实例化策略并且进行实例化操作
    beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
}

public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
    // 锁一下对象,线程安全
    synchronized (bd.constructorArgumentLock) {
        // 得到当前bean的Class
        Class<?> clazz = bd.getBeanClass();
        // 通过class得到其默认的构造方法
        constructorToUse = clazz.getDeclaredConstructor();
        // 
        return BeanUtils.instantiateClass(constructorToUse);
    }
}

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) {
    // 构造方法+入参
    // 如果当前是无参构造方法的话,则argsWithDefaultValues为空
    return ctor.newInstance(argsWithDefaultValues);
}

总结一下通过无参构造创建实例的步骤:

  • 加锁,保证线程安全
  • 得到当前 beanClass,通过其 Class 得到默认的无参构造方法
  • 通过反射直接创建即可

其实有参的构造方法也类似,只不过相较于无参构造,反射传入的 argsWithDefaultValues 的参数,这里的参数可以为 Bean 也可以为数值,所以这里也会出现循环依赖的问题。

11.2 属性填充

属性填充相对简单,流程我们大致过一下,属性注入类似:

<bean id="messageService" class="com.mashibing.hls.MessageServiceImpl">
    <property name="name" value="hls"/>
</bean>

其中的 property 标签就是我们的属性值。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    // 得到当前 BeanDefinition 的属性值
    PropertyValues pvs = mbd.getPropertyValues();
    if (pvs != null) {
        // 注入属性
        applyPropertyValues(beanName, mbd, bw, pvs);
    }
}

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs){
    // 获取pvs的PropertyValue对象数组,并将其转换成列表
    List<PropertyValue> original = Arrays.asList(pvs.getPropertyValues());
    
    for (PropertyValue pv : original) {
        // 获取属性的名字
        String propertyName = pv.getName();
        // 获取未经类型转换的值
        Object originalValue = pv.getValue();
        
        // 这里需要进行一系列的转换
        // 因为我们的属性注入有可能注入的是一个BeanReference,需要重新去 BeanFactory 中获取实例
        // 转换后的放至 deepCopy
        
        // 按原样使用deepCopy构造一个新的MutablePropertyValues对象然后设置到bw中以对bw的属性值更新
        bw.setPropertyValues(new MutablePropertyValues(deepCopy));
    }
}

public void setPropertyValues(PropertyValues pvs){
    setPropertyValues(pvs, false, false);
}

public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid){
    // 后续主要通过反射对值进行设置,感兴趣的可以自己去看下源码实现
    setPropertyValue(pv);
}

11.3 初始化逻辑

我们这里先实现一个 BeanPostProcessor 接口,便于我们的观察:

public class MyTest implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("我前置增强");
        return bean;
    }


    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("我后置增强");
        return bean;
    }
}

直接看我们的源码:

// 执行初始化逻辑
exposedObject = initializeBean(beanName, exposedObject, mbd);

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    // 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessBeforeInitialization初始化方法。
	 // 返回的Bean实例可能是原始Bean包装器
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

	//调用初始化方法,先调用bean的InitializingBean接口方法,后调用bean的自定义初始化方法
	invokeInitMethods(beanName, wrappedBean, mbd);
	
		
	// 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessAfterInitialization方法。
	// 返回的Bean实例可能是原始Bean包装器
	wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		
	//返回包装后的Bean
	return wrappedBean;
}

// 执行所有的BeanPostProcessors接口下的类
// 如果我们自己实现的类对 Bean 进行了包装,比如AOP,则使用我们实现类里面返回的
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName){
       Object result = existingBean;
		//遍历 该工厂创建的bean的BeanPostProcessors列表
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			// 默认实现按原样返回给定的 Bean
			Object current = processor.postProcessBeforeInitialization(result, beanName);
			// 如果 current为null
			if (current == null) {
				//直接返回result,中断其后续的BeanPostProcessor处理
				return result;
			}
			//让result引用processor的返回结果,使其经过所有BeanPostProcess对象的后置处理的层层包装
			result = current;
		}
		//返回经过所有BeanPostProcess对象的后置处理的层层包装后的result
		return result;
}

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd){
       // 如果mbd不为null&&bean不是NullBean类
		if (mbd != null && bean.getClass() != NullBean.class) {
			// 获取mbd指定的初始化方法名
			String initMethodName = mbd.getInitMethodName();
			// 在bean上调用指定的自定义init方法
			invokeCustomInitMethod(beanName, bean, mbd);
            
          // 具体调用,反射执行
          // Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod);
			//  methodToInvoke.invoke(bean);
		}
}

// 执行所有的BeanPostProcessors接口下的类
// 如果我们自己实现的类对 Bean 进行了包装,比如AOP,则使用我们实现类里面返回的
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName){

		//初始化结果对象为result,默认引用existingBean
		Object result = existingBean;
		//遍历该工厂创建的bean的BeanPostProcessors列表
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			//回调BeanPostProcessor#postProcessAfterInitialization来对现有的bean实例进行包装
			Object current = processor.postProcessAfterInitialization(result, beanName);
			// 如果current为null
			if (current == null) {
				//直接返回result,中断其后续的BeanPostProcessor处理
				return result;
			}
			//让result引用processor的返回结果,使其经过所有BeanPostProcess对象的后置处理的层层包装
			result = current;
		}
		//返回经过所有BeanPostProcess对象的后置处理的层层包装后的result
		return result;
	}

其实初始化的逻辑也很简单,就是调用我们的 BeanPostProcess 实现扩展点的应用

然后初始化 init 方法即可

12、finishRefresh

整体简介: 完成整个容器的启动,所有的对象都准备完成,可以进行后续业务流程的操作,清除上下文缓存,初始化生命周期处理器,发送刷新完成事件

13、销毁

前面我们已经创建成功了对象,当我们使用完成之后,肯定要进行销毁,那么 Spring 是如何做的销毁对象的管理呢

执行 context.close(); 方法:

public void close() {
    synchronized (this.startupShutdownMonitor) {
        doClose();
    }
}

protected void doClose() {
    // 清空 DefaultListableBeanFactory 里面的缓存
    destroyBeans();
    // 直接将beanFactory置为null
    closeBeanFactory();
}

protected void destroyBeans() {
    // 清空在包含的Bean名称之间映射:bean名称-Bean包含的Bean名称集
    this.containedBeanMap.clear();
    // 清空在相关的Bean名称之间映射:bean名称-一组相关的Bean名称
    this.dependentBeanMap.clear();
    // 清空在相关的Bean名称之j键映射:bean名称bean依赖项的Bean名称集
    this.dependenciesForBeanMap.clear();
    // 清除此注册表中所有缓存的单例实例
    clearSingletonCache();
}

protected void clearSingletonCache() {
    // 加锁,使用单例对象的高速缓存:beam名称-bean实例作为锁
    synchronized (this.singletonObjects) {
        // 清空单例对象的高速缓存:beam名称-bean实例
        this.singletonObjects.clear();
        // 清空单例工厂的缓存:bean名称-ObjectFactory
        this.singletonFactories.clear();
        // 清空早期单例对象的高速缓存:bean名称-bean实例
        this.earlySingletonObjects.clear();
        // 清空已注册的单例集,按照注册顺序包含bean名称
        this.registeredSingletons.clear();
        // 设置当前是否在destroySingletons中的标志为false
        this.singletonsCurrentlyInDestruction = false;
    }
}

private void clearByTypeCache() {
    this.allBeanNamesByType.clear();
    this.singletonBeanNamesByType.clear();
}
// 直接将beanFactory置空
protected final void closeBeanFactory() {
    DefaultListableBeanFactory beanFactory = this.beanFactory;
    if (beanFactory != null) {
        beanFactory.setSerializationId(null);
        this.beanFactory = null;
    }
}

这基本就是销毁的整个流程。

这里还有一个小知识点,就是我们可以自定义我们的销毁方法,比如如下:

 <bean id="messageService" class="cn.hls.spring.MessageServiceImpl" init-method="init" destroy-method="destroy"/>

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

    public void init(){
        System.out.println("我是类的初始化");
    }

    public void destroy(){
        System.out.println("我是类的销毁");
    }
}

在执行 context.close(); 方法时,会调用该 Bean 的销毁方法,至于怎么调用的。

这里交给读者了(真不是我懒

五、流程图

在这里插入图片描述

六、总结

又是一篇大工程的文章结束了

记得校招时候,当时对 Spring 懵懂无知,转眼间也被迫看了源码

有些小伙伴可能疑惑:哎,博主,你这不对呀,你这循环依赖也没讲、三级缓存也没讲,你是不是漏的有点多。

因为咱们这篇文章主要针对的是 Spring IOC 的源码,对于三级缓存、循环依赖来说,主要解决 AOP 代理对象的问题,这个我们后面单独出一篇来描述,不要着急,小黄不会不讲的。

当然,本篇只介绍了 XML 配置,如果你对注解的配置感兴趣的话,也可以去看一下 AnnotationConfigApplicationContext 的流程,区别不大,一个是解析的 xml,一个是解析的注解

但通过这篇文章,我相信,99% 的人应该都可以理解了 Spring IOC 的来龙去脉

那么如何证明你真的理解了 Spring IOC 呢,我这里出个经典的题目,大家可以想一下:Bean 的生命周期

如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!

喜欢的可以点个关注,后续会更新 Spring 源码系列文章

我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。

我们下期再见。

七、附录

1、注册别名的原因

当我们使用 GetBean(beanName) 时,Spring会默认其是别名,并进行循环获取

protected <T> T doGetBean(String name){
    final String beanName = transformedBeanName(name);
}
protected String transformedBeanName(String name) {
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
public String canonicalName(String name) {
    String canonicalName = name;
    String resolvedName;
    do {
        resolvedName = (String)this.aliasMap.get(canonicalName);
        if (resolvedName != null) {
            canonicalName = resolvedName;
        }
    } while(resolvedName != null);
    return canonicalName;
}

2、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor

这两个类的作用主要对我们 BeanFactoryBeanDefinitions 进行修改,举个例子:

public class MyTest implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 拿到我们 id = messageService 的 PropertyValue
        MutablePropertyValues messageService = beanFactory.getBeanDefinition("messageService").getPropertyValues();
        List<PropertyValue> propertyValueList = messageService.getPropertyValueList();

        // 遍历输出,当然也可以进行修改
        for (PropertyValue propertyValue : propertyValueList) {
            System.out.println("BeanFactoryPostProcessor : name = " + propertyValue.getName() + " value = " + propertyValue.getValue());
        }
    }
}

我们看一下启动的效果:

BeanFactoryPostProcessor : name = name value = TypedStringValue: value [hls], target type [null]

3、BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor

这两个类主要是扩展进行使用,比如我们的 AOP 或者其他的扩展点,举个例子:

public class MyTest implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("我会在类初始化前调用");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("我会在类初始化后调用");
        return bean;
    }
}

我们看一下启动的效果:

我会在类初始化前调用
我是类的初始化
我会在类初始化后调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/390982.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详细聊TTL电平、RS232接口和RS485接口

TTL电平 TTL&#xff08;Transistor-Transistor Logic&#xff09;是一种数字电平标准&#xff0c;常用于数字电子设备之间的数据传输。 TTL使用0V表示逻辑0&#xff0c;使用5V表示逻辑1&#xff0c;因此TTL信号的电平比较容易理解和处理。TTL信号传输距离较短&#xff0c;通…

文件服务设计

一、需求背景 文件的上传、下载功能是软件系统常见的功能&#xff0c;包括上传文件、下载文件、查看文件等。例如&#xff1a;电商系统中需要上传商品的图片、广告视频&#xff0c;办公系统中上传附件&#xff0c;社交类系统中上传用户头像等等。文件上传下载大致流程为&#…

【Java诊断工具】Arthas使用指南

文章目录一、简介二、解决什么问题 & 适用场景解决问题&#xff1a;适应场景&#xff1a;三、安装&启动四、相关命令五、来自天边的"海市蜃楼"六、结束一、简介 Arthas 是Alibaba开源的Java诊断工具。安装在系统所在服务器。通过全局视角实时查看应用 load、…

人工智能论文术语集39

Resume 简历&#xff08;但在AI领域一般指&#xff1a;复位&#xff09;Perform arithmetic operations 执行算数操作performance measurement and improvement techniques 性能测试和改进技术draw stuff with your mouse 用鼠标画东西the Color Palette 调色板high dynamic ra…

41 42Ping-Pong操作

提高电路吞吐率的结构——Ping-Pong操作 1.Ping-Pong操作原理 作用&#xff1a;为了让两个不匹配的模块进行对接&#xff0c;并且在对接的过程中让这两个模块能够同时工作&#xff0c;提高数据处理的吞吐率&#xff08;也称throughput效能&#xff09; 常见的不匹配&#xff1…

Semaphore 源码解读

一、Semaphore Semaphore 通过设置一个固定数值的信号量&#xff0c;并发时线程通过 acquire() 获取一个信号量&#xff0c;如果能成功获得则可以继续执行&#xff0c;否则将阻塞等待&#xff0c;当某个线程使用 release() 释放一个信号量时&#xff0c;被等待的线程如果可以成…

idea初学笔记

注:初学需安装idea专业版&#xff0c;方便学习使用idea运行内存配置从eclipse工具开发 转 idea工具开发&#xff0c;可设置idea快捷键同eclipse快捷键 file -> Settings -> Keymap -> 选择Eclipse -> OK设置idea项目整体编码格式file -> Settings -> Editor …

mysql五种索引类型---实操版本

背景 最近学习了Mysql的索引&#xff0c;索引对于Mysql的高效运行是非常重要的&#xff0c;正确的使用索引可以大大的提高MySql的检索速度。通过索引可以大大的提升查询的速度。不过也会带来一些问题。比如会降低更新表的速度&#xff08;因为不但要把保存数据还要保存一下索引…

Linux【进程理解】

文章目录Linux【进程理解】一、冯诺依曼体系结构二、操作系统OS1.深入理解操作系统2.深入理解系统调用和库函数四、 进程&#xff08;一&#xff09;描述进程-PCB&#xff08;二&#xff09;组织进程和查看进程&#xff08;三&#xff09;通过系统调用创建进程-fork初识&#x…

【python】XML格式文件读写详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录XML介绍格式XML与AJAX与HTML区别联系生成XML文件案例用SAX模块处理XML用DOM模块处理XML&#x1f338;I could be bounded in a nutshell and count myself a king of infinite space. 特别鸣谢&#xff1a;…

三:BLE协议架构简介

低功耗蓝牙体系整体架构说明1. PHY(物理层)2. LL(链路层)3. HCI(主机与控制器通信接口)4. L2CAP(逻辑链路控制及适配协议)5. ATT(属性协议)6. GATT(通用属性规范)7. GAP(通用访问规范)8. SM(安全管理)整体架构说明 架构层说明PHY1. 物理层2. 控制射频的发送和接收LL1. 链路层2.…

Java开发 - Quartz初体验

前言 在上一篇博客中&#xff0c;我们对单点登录有了初步了解&#xff0c;这也让我们独立做系统有了最基础的保障。但在业务开发中&#xff0c;总是会出现一些定期处理的任务&#xff0c;我们首先想到的是Timer&#xff0c;但由于其调度功能单一&#xff0c;我们实际并不会用它…

sqlmap工具说明

目录 1 工具简介 3 1.1 简述 3 1.2 背景及需求 3 1.3 主要功能 3 2 功能确认 4 2.1 安装和使用 4 2.1.1 Windows操作系统 4 2.1.2 Linux操作系统 6 2.1.3 Kali 7 2.2 添加目标 7 2.2.1 参数-u 7 2.2.2 参数-m 8 2.2.3 参数-r 9 2.3 指定参数 11 2.4 爆破数据库 11 2.5 爆破表 …

【LeetCode】剑指 Offer(16)

目录 题目&#xff1a;剑指 Offer 33. 二叉搜索树的后序遍历序列 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer …

Uipath DataTable-Build Data Table(构建数据表)

Build Data Table(构建数据表) 活动描述 Build Data Table(构建数据表)&#xff1a;通过在“构建数据表向导”窗口可以构建数据表。 使用如下图&#xff1a; Build Data Table(构建数据表)属性配置 属性 作用 DisplayName 在设计器面板中设置活动显示的名称 Private 如…

List、Set、Map众多集合框架等你来学,让我们一起精进Java框架的知识点吧

文章目录一、各集合特性二、HashMap深入解析三、遍历集合元素的若干方式一、各集合特性 1. ArrayList特性 不唯一&#xff0c;有序&#xff1a;实现了List接口&#xff0c;该接口是序列&#xff0c;所以不唯一且按顺序保存不同步&#xff1a;因为ArrayList.add&#xff08;&am…

Java 编写Vue组件(VueGWT的初尝试)

在之前&#xff0c;我曾写过这样的文章《不会前端没事&#xff0c;用GWT Boot和Spring Boot构建Web程序》&#xff0c;这篇文字使用的Domino UI来做前端页面&#xff0c;由于现在更流行VUE&#xff0c;并且VUE的页面更具现代化&#xff0c;所以我尝试了一下VueGWT。 VueGWT 有…

Nodejs运行vue项目时,报错:Error: error:0308010C:digital envelope routines::unsupported

前言 前端项目使用( npm run dev ) 运行vue项目时&#xff0c;出现错误&#xff1a;Error: error:0308010C:digital envelope routines::unsupported 经过探索&#xff0c;发现问题所在&#xff0c;主要是nodeJs V17版本发布了OpenSSL3.0对算法和秘钥大小增加了更为严格的限制…

xFormers安装使用

xFormers是一个模块化和可编程的Transformer建模库&#xff0c;可以加速图像的生成。 这种优化仅适用于nvidia gpus&#xff0c;它加快了图像生成&#xff0c;并降低了vram的使用量&#xff0c;而成本产生了非确定性的结果。 下载地址&#xff1a; https://github.com/faceb…

C++ | 你真的了解namespace吗?

文章目录一、前言二、命名冲突三、命名空间1、域作用限定符2、命名空间的概念&#x1f449;示例1&#x1f449;示例23、命名空间的定义4、命名空间的使用① 指定命名空间访问【做项目】② 使用using部分展开【做项目】③ 使用using namespace全局展开【日常练习】5、小结解答&a…