Spring的IoC容器Bean创建过程从理论到源码分析

Posted lllllLiangjia

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring的IoC容器Bean创建过程从理论到源码分析相关的知识,希望对你有一定的参考价值。

目录

前言

1.IoC起到了什么作用?

(1)那它是如何解耦的?

(2)什么是依赖倒置?

2.举例说明IoC思想降低耦合度。

(1)不使用依赖倒置原则:

(2)使用依赖倒置思想:

一、IoC定义

二、IoC容器

2.1 IoC容器作用

2.2 IoC容器类型

2.3 容器获取对象

2.3.1 通过Resource获取BeanFactory

2.3.2 类路径下XML获取ApplicationContext

2.4 BeanFactory与ApplicationContext区别

三、spring创建bean流程

3.1 创建bean的整个流程

3.2 相关类与接口详解

四、IoC创建Bean源码分析

4.1 项目搭建

4.1.1 Text类

4.1.2 IndexService类

4.1.3 Config类

4.1.4 项目目录结构

4.2.源码分析

4.2.1 初始化容器

4.2.2 刷新Spring的应用上下文

4.2.3 判断bean对象是否存在

4.2.4 没有已存在的bean对象则开始创建

4.2.5 实例化对象并初始化填充属性

4.2.6 初始化完成后执行回调

4.2.7 获取Bean对象

总结


前言

我们都知道Spring有以下很多优点:

  1. 方便解耦,简化开发。降低了业务对象替换的复杂性,可以让开发人员更关注业务逻辑
  2. 方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付
  3. 数据库事务问题
  4. ......

1.IoC起到了什么作用?

IoC容器的主要作用其实就是第一点说的,方便解耦,简化开发。

(1)那它是如何解耦的?

Spring IoC 容器使用一种形式的配置元数据。应用程序开发人员告诉 Spring 容器实例化哪些类,这些类被创建之后,类与类之间的调用就像配置和组装一样,耦合度大大降低,这就是控制反转的应用。而这就不得不提到设计模式的七大原则(单一职责、开放封闭、里氏替换、接口隔离、依赖倒置、迪米特法则、合成复用)之一的依赖倒置原则。有了这个思想才更能了解控制反转。

(2)什么是依赖倒置?

百度百科上的解释:依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合

通过依赖倒置定义中红色的字体,我们可以知道IoC是符合依赖倒置原则的。

2.举例说明IoC思想降低耦合度。

使用的例子借鉴的文章链接:https://www.zhihu.com/question/23277575/answer/169698662

假设想设计一辆汽车,具体有三个汽车的细节类分别代表轮胎(Tire)、底盘(Bottom)、车身(Framework),加上汽车(Car)本身类一共四个类。

(1)不使用依赖倒置原则:

先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。那他们的关系:汽车类依靠车身类、车身类依靠底盘类、底盘类依靠轮子类。

汽车有大有小,我想让汽车的尺寸大小可变,由于目前的高层依赖低层的关系,那么优先修改的应该是轮胎的大小吧,底盘依靠轮胎,轮胎先要改变尺寸底盘才能改变,同理,然后是车身、最后是汽车。所以当高层想要变化时,需要一步步从低层将修改的格式传过来,非常不方便。

上面我们用的是通俗的话描述了一遍,那现在我们用uml关系解释一下。由于Car类中的字段framework是在构造函数中初始化的,那么在汽车(Car)实例化的时候,也会同步实例化一个车身(Framework)类,那他们的生命周期是一样的,同时创建同时消亡。就像大家在学习uml关系的时候,都看过的大雁和翅膀的关系一样,Framework类和Car类是组合关系。这种组合关系带来的麻烦就是耦合度太高,如果Framework类有一些变动的时候,Car类受到影响的概率就非常大。例如Framework构造函数的参数发生变化,那Car类的构造函数也需要随着进行增删更改。

代码与UML图如下

(2)使用依赖倒置思想:

将车身注入到车,将底盘注入到车身,以此类推......这样上层控制下层,高层控制低层。

代码与UML图如下

这时我们用构造方法传递的依赖注入方式,当需要改动时,只需要修改轮胎类就行了,无需修改其他任何上层类,这更容易维护代码。此时用uml关系解释的话,因为Car类包含Framework类,但是传入的参数framework的生命周期跟car的生命周期不一样,所以Car类与Framework类是聚合关系,与之前的组合关系相比耦合度降低。

一、IoC定义

控制反转( Inversion of Control ),也称依赖注入(DI)。这是一个过程,在此过程中,对象仅通过构造函数参数,工厂方法的参数或在对象实例从工厂方法构造或返回后设置的属性来定义其依赖关系,即与它们一起使用的其他对象。

注:有人可能会说控制反转和依赖注入不一样,依赖注入是控制反转实现的一种方式,控制反转还有别的方式,例如包括依赖查找,所以不能笼统的说控制反转和依赖注入一样。但是就目前我们学习的Spring来说控制反转和依赖注入可以相等。Spring官网是这样说的,我只是在这引用一下,并且官网推荐的控制反转阅读文章也有提到。大家感兴趣可以看一看。
       Spring官网链接:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans
       Spring中文文档链接:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/core.html#beans
       控制反转文章链接:https://martinfowler.com/articles/injection.html

二、IoC容器

2.1 IoC容器作用

通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

● 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC容器是专门来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

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

2.2 IoC容器类型

BeanFactory:Bean工厂,功能简单。是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;

ApplicationContext:应用上下文,功能强大,一般使用它。继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;国际化、访问资源、载入多个上下文,让每个上下文都专注于一个特定的层次,比如web层。消息发送、响应机制、拦截器

2.3 容器获取对象

2.3.1 通过Resource获取BeanFactory

     //加载Spring的资源⽂件
     Resource resource = new ClassPathResource("applicationContext.xml");
     //创建IOC容器对象【IOC容器=⼯⼚类+applicationContext.xml】
     BeanFactory beanFactory = new XmlBeanFactory(resource);

2.3.2 类路径下XML获取ApplicationContext

     // 得到IOC容器对象
     ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml")

     System.out.println(ac);

2.4 BeanFactory与ApplicationContext区别

BeanFactory在启动时,不去实例化Bean,只有在容器中获取的时候才会实例化。也就是懒加载,所以当bean没有完全注入时,可能第一次获取就会抛出异常。

ApplicationContext是在启动的时候就实例化所有单例的Bean,它还可以为Bean配置lazy-init=true来让Bean延迟实例化;

BeanFactory启动占用资源少,但是对资源要求高。

ApplicationContext:在启动时就加载,运行速度快,可以尽早发现系统中的配置问题。

三、spring创建bean流程

3.1 创建bean的整个流程

bean创建过程大体分为五个步骤:加载→解析→实例化→初始化→获取

1.Tomcat启动,IoC容器初始化,加载扫描包下的Class文件。

2.BeanDefinitionReader读取配置文件中的bean定义信息加载解析为BeanDefinition。

3.通过反射将BeanDefinition实例化为Bean对象。

4.然后进行初始化Bean对象中的属性信息和方法。

      (1) 设置Bean的属性

      (2) 检查Aware相关接口并设置相关依赖

      (3) 检查BeanPostProcessor接口postProcessBeforeInitialization方法进行前置处理

      (4) 检查Bean在Spring配置文件中配置的init-method属性并自动调用其配置的初始化方法

      (5) 检查BeanPostProcessor接口postProcessAfterInitialization方法并进行后置处理

5.最后获取到完整的Bean对象。

流程图如下所示

3.2 相关类与接口详解

BeanDefinitionReader:读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构----BeanDefinition。它是一个接口,通过不同的实现子类去读取不同文件中的信息,可以是xml、properties......不管外部定义的bean是什么样子的,最后都需要经过BeanDefinitionReader这个接口去规范为一种统一的格式。

BeanDefinition:IoC容器内部的数据结构。容器初始化的时候会将用户定义好的javabean表示为BeanDefinition,然后向IoC容器注册这些BeanDefinition再进行处理。

BeanFactoryPostProcessor:允许自定义修改应用程序上下文的BeanDefinition,调整上下文的基础BeanFactory的Bean属性值,实际上是对BeanFactory扩展增强。BeanFactoryPostProcessor可以与BeanDefinition进行交互并对其进行修改,但不能与bean实例进行交互。

BeanPostProcessor:允许自定义修改新的bean实例,在这个接口中有两个方法----postProcessBeforeInitialization和postProcessAfterInitialization。postProcessBeforeInitialization在任何bean初始化回调init-method之前运行,是前置增强。postProcessAfterInitialization在任何bean初始化回调init-method之后运行,是后置增强。与AOP的联系如下图所示。

Aware:Aware是一个顶端接口,当需要在普通对象中获取容器中的相关内部对象的时,可以实现这个Aware接口的具体子接口类来得到。

四、IoC创建Bean源码分析

4.1 项目搭建

4.1.1 Text类

public class Test 
    public static void main(String[] args) 
        // 初始化IoC容器,创建扫描包下所有非懒加载单例Bean
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
        // 从容器中获取IndexService类对应的对象
        IndexService index = ac.getBean(IndexService.class);
        System.out.println(index);
    

4.1.2 IndexService类

@Component
public class IndexService 

  public IndexService() 
    System.out.println("构造函数  indexService");
  

  // 初始化回调方法
  @PostConstruct
  private void aaaa()
    System.out.println("调用了aaaa");
  

4.1.3 Config类

@ComponentScan("com.liang")
public class Config 

4.1.4 项目目录结构

4.2.源码分析

4.2.1 初始化容器

AnnotationConfigApplicationContext是AbstractApplicationContext抽象类的一个是子类,当代码进入它的有参构造方法时,调用AbstractApplicationContext类的refresh()方法。

    public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) 
		this();
		register(annotatedClasses);
        // 刷新容器
		refresh();
	

4.2.2 刷新Spring的应用上下文

refresh方法是用来刷新Spring的应用上下文,它执行了很多功能的方法,我们主要讨论方法5----invokeBeanFactoryPostProcessors(beanFactory)和方法11----finishBeanFactoryInitialization(beanFactory)

invokeBeanFactoryPostProcessors(beanFactory)方法的经过层层调用最后会将bean信息转换为RootBeanDefinition,并存放在AbstractBeanFactory类中的Map类型集合mergedBeanDefinitions属性中。

finishBeanFactoryInitialization(beanFactory)方法执行了创建bean的最重要几个步骤。从doGetBean()尝试在获取bean信息,到doCreateBean()进行实例化createBeanInstance(),以及最后的属性填充populateBean()。经过这一系列操作,Bean对象才注册到IoC容器中。

    public void refresh() throws BeansException, IllegalStateException 
    synchronized (this.startupShutdownMonitor) 
      // Prepare this context for refreshing.
      // 1.准备工作  启动时间  活跃状态 关闭状态
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      // 2.创建工厂 初始化工厂的各种属性
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      // 3.初始化工厂的各种属性
      prepareBeanFactory(beanFactory);

      try 
        // 4.空方法  为了子类扩展使用
        postProcessBeanFactory(beanFactory);

        // 5.完成扫描和解析(类--->beanDefinition)
        invokeBeanFactoryPostProcessors(beanFactory);

        // 6.注册beanPostProcessor 真正调用是getBean
        registerBeanPostProcessors(beanFactory);

        // Initialize message source for this context.
        // 7.国际化
        initMessageSource();

        // Initialize event multicaster for this context.
        // 8.初始化广播器
        initApplicationEventMulticaster();

        // 9.这个方法同样也是留个子类实现的springboot也是从这个方法进行启动tomcat的
        onRefresh();

        // Check for listener beans and register them.
        // 10.注册监听器
        registerListeners();

        // Instantiate all remaining (non-lazy-init) singletons.
        // 11.初始化非懒加载单例对象
        finishBeanFactoryInitialization(beanFactory);

        // 12.Last step: publish corresponding event.
        finishRefresh();
      

      //......
    
  

4.2.3 判断bean对象是否存在

finishBeanFactoryInitialization方法进入到beanFactory.preInstantiateSingletons()方法中,遍历存放beanDefinitionName的list集合后拿到beanName,然后执行两个重要的方法getMergedLocalBeanDefinition(beanName)方法和isFactoryBean(beanName)方法

getMergedLocalBeanDefinition(beanName)方法,通过beanName从mergedBeanDefinitions集合中获取对应的RootBeanDefinition信息。

isFactoryBean(beanName)方法中又调用了getSingleton(beanName, false)方法,getSingleton方法用beanName在singletonObjects集合get对象。由于是第一加载所以并没有已经存在的对象,所以走else代码块中的getBean()方法。

注:这里的singletonObjects集合就是存放单例对象的高速缓存容器,它是线程安全的ConcurrentHashMap集合。

    public void preInstantiateSingletons() throws BeansException 
		//......

		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// 触发所有非惰性单例bean的初始化...
		for (String beanName : beanNames) 
            // 根据beanName从mergedBeanDefinitions集合中获取该beanName对应的RootBeanDefinition
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) 
                //通过Bean的Name,判断IoC容器中是否已经存在该Bean
				if (isFactoryBean(beanName)) 
					//......
				
				else 
					getBean(beanName);
				
			
		

		//......
	

4.2.4 没有已存在的bean对象则开始创建

由于目前没有已存在的bean对象,从getSingleton(beanName)方法没有获取到的对象是null。再获取beanName对应的RootBeanDefinition,以RootBeanDefinition和beanName为参数执行getSingleton回调方法中的createBean(beanName, mbd)方法。createBean()方法中又调用了doCreateBean()方法。doCreateBean()开始真正的创建逻辑。

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException 


		// 再次判断是否IoC容器中存在Bean
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) 
			//......
		

		else 
			//......
			try 
                // 根据beanName从mergedBeanDefinitions集合中获取该beanName对应的RootBeanDefinition
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

				//......

				// Create bean instance.
				if (mbd.isSingleton()) 
                    // 从这里进入的createBean,执行完createBean方法会再次回调getSingleton方法。
					sharedInstance = getSingleton(beanName, () -> 
						try 
                            // 开始创建bean对象
							return createBean(beanName, mbd, args);
						
						catch (BeansException ex) 
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						
					);
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				

				//......

		return (T) bean;
	

4.2.5 实例化对象并初始化填充属性

createBeanInstance(beanName, mbd, args)方法通过反射得到实例对象,bean获取到该对象。执行addSingletonFactory()方法,将beanName、RootBeanDefinition类型的mbd和实例的对象bean以lambda的形式存储在singletonFactories缓存中。

然后开始初始化对象阶段,populateBean(beanName, mbd, instanceWrapper)填充属性。initializeBean(beanName, exposedObject, mbd)初始化给定的bean实例,应用工厂回调以及init方法和beanPostProcessors,进行相关前置后置增强操作。

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException 

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) 
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		
		if (instanceWrapper == null) 
			// 使用反射创建实例
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		
        // 获取反射后的实例对象
		final Object bean = instanceWrapper.getWrappedInstance();

		//......

			// 添加到singletonFactories缓存中
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		

		// 初始化bean实例。
		Object exposedObject = bean;
		try 
            // 开始填充bean的属性
			populateBean(beanName, mbd, instanceWrapper);
            // 初始化给定的bean实例
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		
		catch (Throwable ex) 
			
        //......

        
		return exposedObject;
	

4.2.6 初始化完成后执行回调

刚刚是从2.4中的createBean方法进入开始创建bean的,执行完后返回,开始执行getSingleton方法,它里面调用了addSingleton(beanName, singletonObject)方法将beanName和SingletonObject的映射关系添加到该工厂的单例缓存singletonObjects中。并移除了singletonFactories缓存中的beanName对象信息。

    protected void addSingleton(String beanName, Object singletonObject) 
		synchronized (this.singletonObjects) 
			this.singletonObjects.put(beanName, singletonObject);
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		
	

4.2.7 获取Bean对象

通过以上的流程,如此Bean对象便被创建完成,单例非懒加载的对象便都存在于singletonObjects单例缓存中,之后如果有需要使用这些单例bean的地方直接通过getBean获取即可。

还有一个疑点就是为什么感觉singletonFactories并没有什么卵用?它存入的lambda对象没有被执行就被移除了,这其实就要说到另一个问题,singletonFactories的存在是为了循环依赖的。


总结

我理解的IoC宏观的图如下。任何东西都是从理论到实践的,23个设计模式和7个设计原则是很重要的思想理论,他们使系统更优美,更健壮。

以上是关于Spring的IoC容器Bean创建过程从理论到源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Spring 从0开始IOC容器的Bean管理

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

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

Spring IoC容器以及Bean的创建过程

Spring IoC容器以及Bean的创建过程

Spring IoC容器以及Bean的创建过程