Spring源码:Bean生命周期(三)

spring,源码,bean,生命周期 · 浏览次数 : 299

小编点评

**代码解析** * `getBean` 方法主要包含以下几个步骤: * 从单例缓存池中获取 bean 实例。 * 如果没有,Spring 会创建新的 bean 实例,并将其添加到单例缓存池中。 * 检查当前容器是否有指定名称的 bean 定义。 * 如果没有,Spring 会调用父容器的 `getBean` 方法,直到找到为止。 *一旦找到了 bean 定义,Spring 会根据不同的作用域类型,创建对应的 bean 实例,并将其存储在作用域中。 *最后,Spring 会返回创建好的 bean 实例。 * `single例缓存池` 是一个在 Spring 中用于管理单例缓存的池。 * ``objectFactory.getObject()`` 方法用于创建对象实例。 * ````setAttribute``方法用于设置属性值。 * ```````方法用于获取属性值。 * ```````方法用于设置作用域中对象的值。 * ```````方法用于获取作用域中对象的值。 **代码分析** * ```````方法的逻辑判断非常复杂,包含多个步骤和条件。 * ````````方法的主要目的是创建和设置作用域中对象的值。 * ````````方法使用单例缓存池和工厂来创建对象实例。 * ````````方法使用````````方法设置属性值和获取属性值。 * ````````方法使用````````方法设置作用域中对象的值。 **其他** * Spring 的```````方法使用了单例缓存池和工厂来创建对象实例。 * ````````方法使用````````方法设置属性值和获取属性值。 * ````````方法使用`````````方法设置作用域中对象的值。

正文

前言

在之前的文章中,我们已经对 bean 的准备工作进行了讲解,包括 bean 定义和 FactoryBean 判断等。在这个基础上,我们可以更加深入地理解 getBean 方法的实现逻辑,并在后续的学习中更好地掌握createBean 方法的实现细节。

getBean用法

讲解getBean方法之前,我们先来看看他有几种常见的用法:

// 创建一个Spring容器  
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);  
UserService bean1 = applicationContext.getBean(UserService.class);  
UserService bean2 = (UserService)applicationContext.getBean("userService");  
UserService bean3 = applicationContext.getBean("userService",UserService.class);  
UserService bean4 = (UserService) applicationContext.getBean("userService",new OrderService());  
bean1.test();  
bean2.test();  
bean3.test();  
bean4.test();

关于获取 bean 的方法,前两种方法应该比较常见,这里就不再赘述。第三种方法实际上是在获取 bean 的时候,会先判断是否符合指定的类型,如果符合,则进行类型转换并返回对应的 bean 实例。第四种方法则是在创建 bean 实例时,通过推断构造方法的方式来选择使用带有参数的构造方法进行实例化。

如果我们想要让第四种方法生效,可以考虑使用多例的形式,即通过设置 scope 属性为 prototype 来实现。这样,每次获取 bean 时,都会创建新的 bean 实例,从而可以触发使用带有参数的构造方法进行实例化,比如这样:

@Component  
@Scope("prototype")  
public class UserService {  
  
     public UserService(){  
      System.out.println(0);  
   }  
     public UserService(OrderService orderService){  
      System.out.println(1);  
   }  
   public void test(){  
      System.out.println(11);  
   }  
}

getBean大体流程

由于方法代码太多,我就不贴代码了,我这边只贴一些主要的伪代码,方便大家阅读,然后我在对每个流程细讲下:

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

		// name有可能是 &xxx 或者 xxx,如果name是&xxx,那么beanName就是xxx
		// name有可能传入进来的是别名,那么beanName就是id
		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			
			// 如果sharedInstance是FactoryBean,那么就调用getObject()返回对象			
		}

		else {	
			//检查是否本beanfactory没有当前bean定义,查看有父容器,如果有,则调用父容器的getbean方法				  
			try {				 
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				// 检查BeanDefinition是不是Abstract的
				checkMergedBeanDefinition(mbd, beanName, args);
				// Guarantee initialization of beans that the current bean depends on.				 
				//查看是否有dependsOn注解,如果存在循环依赖则报错
				// Create bean instance.
				if (mbd.isSingleton()) {
					//调用createBean方法
					//如果是FactoryBean则调用getObject
				}
				else if (mbd.isPrototype()) {
					//调用createBean方法,与单例只是前后逻辑不一样
					//如果是FactoryBean则调用getObject
				}
				else {
					Scope不同类型有不同实现
					//调用createBean方法,与单例只是前后逻辑不一样
					//如果是FactoryBean则调用getObject
				}
			}catch{
				......
			}
		}

		// 检查通过name所获得到的beanInstance的类型是否是requiredType
		return adaptBeanInstance(name, beanInstance, requiredType);
	}

单例缓存池

在 Spring 中,不管传入的 beanName 是多例的还是单例的,都会先从单例缓存池中获取。有些人可能会觉得这样做会浪费一些性能,但实际上 Spring 考虑到了大部分托管的 bean 都是单例的情况,因此忽略了这一点性能。实际上,这样的性能消耗并不大。可以将其类比于 Java 的双亲委派机制,都会先查看本加载器是否有缓存,如果没有再向父加载器去加载。

parentBeanFactory

在分析 bean 定义是如何创建的时,我们可以不考虑单例缓存池中获取对象的情况,而是逐步分析 bean 定义是如何创建的。在这个过程中,即使存在 parentBeanFactory,我们也可以跳过它,因为我们的启动容器并没有设置任何父容器。源码也很简单,如果本容器没有 bean 定义,就直接调用父容器的 getBean 相关方法:

BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                // &&&&xxx---->&xxx
                String nameToLookup = originalBeanName(name);
                if (parentBeanFactory instanceof AbstractBeanFactory) {
                    return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                            nameToLookup, requiredType, args, typeCheckOnly);
                }
                else if (args != null) {
                    // Delegation to parent with explicit args.
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else if (requiredType != null) {
                    // No args -> delegate to standard getBean method.
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
                else {
                    return (T) parentBeanFactory.getBean(nameToLookup);
                }
            }

dependsOn

在调用 getBean 方法之前,已经将合并的 bean 定义存入了容器中。因此,我们可以直接获取已经合并好的 bean 定义,并解析 bean 定义上的 dependsOn 注解。具体的源码逻辑如下:

RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

                // 检查BeanDefinition是不是Abstract的
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    // dependsOn表示当前beanName所依赖的,当前Bean创建之前dependsOn所依赖的Bean必须已经创建好了
                    for (String dep : dependsOn) {
                        // beanName是不是被dep依赖了,如果是则出现了循环依赖
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        // dep被beanName依赖了,存入dependentBeanMap中,dep为key,beanName为value
                        registerDependentBean(dep, beanName);

                        // 创建所依赖的bean
                        try {
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }

这里的逻辑还是相对简单的。如果当前 bean 被 dependsOn 注解所依赖,那么会先去创建所依赖的 bean。但是这种方式是解决不了循环依赖的问题的。在实现上,只使用了两个 Map 进行判断:

// 某个Bean被哪些Bean依赖了
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
// 某个Bean依赖了哪些Bean
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

isSingleton

sharedInstance = getSingleton(beanName, () -> {
                        try {
                            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;
                        }
                    });
                    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

在这个阶段,我们可以看到代码已经在准备创建单例 bean 实例了,因此我们可以不去深入理解这部分的源码逻辑。反正,在 getSingleton 方法中,会调用 createBean 方法。这里使用了 lambda 表达式,如果有不太了解的读者,可以参考下之前发的文章进行学习:

isPrototype

  if (mbd.isPrototype()) {
			// It's a prototype -> create a new instance.
			Object prototypeInstance = null;
			try {
				beforePrototypeCreation(beanName);
				prototypeInstance = createBean(beanName, mbd, args);
			}
			finally {
				afterPrototypeCreation(beanName);
			}
			beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
		}

在这个阶段,我们发现创建 bean 的方法已经改变了,直接调用了 createBean 方法,而不是通过 getSingleton 方法进行调用。至于 beforePrototypeCreation 和 afterPrototypeCreation,我们可以不用管它们,因为它们只是存储一些信息,对我们创建 bean 并没有太大的影响。

其他Scope

讲解这部分源码之前,我们先来看看还有哪些Scope域:

//@RequestScope
@SessionScope
public class User {
}

现在我们来看一下 RequestScope 和 SessionScope,它们与其他作用域类似,只是一个组合注解。它们的元注解信息如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

	/**
	 * Alias for {@link Scope#proxyMode}.
	 * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
	 */
	@AliasFor(annotation = Scope.class)
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

然后我们再来看下Spring对其他Scope注解的逻辑判断:

String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {  // session.getAttriute(beaName)  setAttri
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new ScopeNotActiveException(beanName, scopeName, ex);
					}

其实他主要用的getAttriute方法,我们看下scope.get主要的逻辑判断:

	public Object get(String name, ObjectFactory<?> objectFactory) {
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
		Object scopedObject = attributes.getAttribute(name, getScope());
		if (scopedObject == null) {
			scopedObject = objectFactory.getObject();
			attributes.setAttribute(name, scopedObject, getScope());
			// Retrieve object again, registering it for implicit session attribute updates.
			// As a bonus, we also allow for potential decoration at the getAttribute level.
			Object retrievedObject = attributes.getAttribute(name, getScope());
			if (retrievedObject != null) {
				// Only proceed with retrieved object if still present (the expected case).
				// If it disappeared concurrently, we return our locally created instance.
				scopedObject = retrievedObject;
			}
		}
		return scopedObject;
	}

在这个阶段,我们可以看到,通过 objectFactory.getObject() 方法,会调用外层定义的 lambda 表达式,也就是 createBean 方法的逻辑。假设这个过程成功地创建了 bean 实例,并返回了它,那么 Spring 会调用 setAttribute 方法,将这个 bean 实例以及其 scope 值放入以 beanName 为 key 的属性中。这样,当需要获取这个 bean 实例时,Spring 就可以直接从作用域中获取了。

结语

getBean 方法主要包含以下几个步骤:

  1. 首先,从单例缓存池中获取 bean 实例。如果没有,Spring 会创建新的 bean 实例,并将其添加到单例缓存池中。
  2. 接着,Spring 会检查当前容器是否有指定名称的 bean 定义。如果没有,Spring 会调用父容器的 getBean 方法,直到找到为止。
  3. 一旦找到了 bean 定义,Spring 会根据不同的作用域类型,创建对应的 bean 实例,并将其存储在作用域中。
  4. 最后,Spring 会返回创建好的 bean 实例。

非常好,这样我们对 getBean 方法的逻辑判断有了一个大体的了解,有助于我们更好地理解 createBean 方法的实现细节。如果在后续的学习中有任何问题或疑问,可以随时联系我进行咨询。

公众号

与Spring源码:Bean生命周期(三)相似的内容:

Spring源码:Bean生命周期(三)

在之前的文章中,我们已经对 `bean` 的准备工作进行了讲解,包括 `bean` 定义和 `FactoryBean` 判断等。在这个基础上,我们可以更加深入地理解 `getBean` 方法的实现逻辑,并在后续的学习中更好地掌握`createBean` 方法的实现细节。

Spring源码:Bean的生命周期(二)

FactoryBean 和 BeanFactory 是两个不同的概念。前者是一个接口,我们可以在实现该接口时通过调用 getObject 方法来返回实例,同时 FactoryBean 本身也是一个实例。后者是 Spring 容器的工厂,通过其中的 bean 定义 Map 一个一个地实例化我们通过注解等方式注入进去的 bean 工厂。在判断 FactoryBean 时,如果当前 BeanFactor

Spring源码:Bean生命周期(四)

在本文中,我们深入探讨了 Spring 框架中 Bean 的实例化过程,关于某些细节以后我会单独拿出一篇文章单独讲解,我们来总结下实例化都做了哪些事情:先从bean定义中加载当前类,因为最初Spring使用ASM技术解析元数据时只获取了当前类的名称寻找所有InstantiationAwareBeanPostProcessors实现类,并调用实例化前的方法postProcessBeforeInsta

Spring源码:Bean生命周期(终章)

本系列前面讲解了Spring的bean定义、bean实例化、bean初始化等生命周期阶段。这些步骤使我们能够了解bean从创建到准备好使用所经历的过程。但是,除了这些步骤,bean的销毁也是非常重要的一步。在本系列的最后,我们将深入探讨bean的销毁过程,包括在什么情况下会发生销毁、销毁的顺序以及如何在bean销毁之前执行一些清理任务等。通过学习bean的销毁过程,我们将更全面地了解Spring的

Spring源码:bean的生命周期(一)

Spring的Bean定义环节是Spring IoC容器中的核心流程之一。在这个过程中,Spring会扫描指定的包路径,找到符合条件的Bean,并将其转换为Bean定义。在这个过程中,Spring使用了ASM技术来解析类的注解信息,判断当前类是否符合要求。然后,Spring将符合条件的Bean定义加入到候选集合中,并对其进行唯一标识命名、默认值赋值、常见定义注解的解析等操作。最后,Spring使用合并的Bean定义来包装原始的Bean定义,以便在Bean实例化的过程中进行更好的管理和控制。

Spring源码:Bean生命周期(五)

在今天的文章中,我们将深入探讨 Bean 的属性注入和初始化流程,从而使其成为一个真正意义上的 Bean。这个过程包括属性注入、Aware 接口回调、BeanPostProcessor 的前置和后置处理等多个步骤,通过本文的学习,读者将能够更深入地了解 Spring 框架中 Bean 的属性注入和初始化过程,为后续的学习和实践打下坚实的基础。

Spring源码系列一:入门——Hello World

本文介绍了学习Spring源码前需要掌握的核心知识点,包括IOC、AOP、Bean生命周期、初始化和Transaction事务。通过Hello World示例,讲解了如何使用Spring,并指出了深入了解Spring内部机制的方向。

Spring入门系列:浅析知识点

本文介绍了学习Spring源码前需要掌握的核心知识点,包括IOC、AOP、Bean生命周期、初始化和Transaction事务。通过Hello World示例,讲解了如何使用Spring,并指出了深入了解Spring内部机制的方向。

从源码层面深度剖析Spring循环依赖

作者:郭艳红 以下举例皆针对单例模式讨论 图解参考 https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce 1、Spring 如何创建Bean? 对于单例Bean来说,在Spring容器整个生命周期内,有且只有一个对象。 Sprin

Spring源码之XML文件中Bean标签的解析1

## 读取XML文件,创建对象 xml文件里包含Bean的信息,为了避免多次IO,需要一次性读取xml文件中所有bean信息,加入到Spring工厂。 #### 读取配置文件 ```java new ClassPathResource("applicationContext.xml") ``` Cl