一、引言
在Java企业级开发领域,Spring Boot凭借其简洁、快速、高效的特点,迅速成为了众多开发者的首选框架。Spring Boot通过自动配置、起步依赖等特性,极大地简化了Spring应用的搭建和开发过程。而在Spring Boot的众多核心特性中,Bean的管理与解析无疑是重中之重。本文将从IOC思想出发,深入探讨Spring Boot 3中Bean的解析过程,包括XML和注解两种配置方式、refresh方法的解析以及Bean实例化过程。
说明:本文分析使用的Spring Boot源码版本为3.3.5
二、IOC思想
IOC(Inversion of Control,控制反转)是Spring框架的核心思想之一。它意味着对象之间的关系不再由传统的程序控制,而是由Spring容器来统一管理对象的创建、协调和销毁。通过这种方式,对象的依赖关系被解耦,提高了代码的可维护性和可扩展性。
在Spring Boot 3中,IOC容器负责实例化、配置和组装Bean。Bean是Spring框架中构成应用程序的基本组件,Spring容器通过配置文件或注解等方式来定义和管理这些Bean。
三、XML方式配置Bean介绍
在Spring Boot中,虽然推荐使用基于Java的配置(例如使用@Configuration和@Bean注解),但XML配置仍然是支持的,特别是对于需要与遗留系统集成或偏好XML配置的场景。以下是使用XML配置bean和注入bean的几种方式:
- 无参构造
- 有参构造
- 静态工厂方法
- 实例工厂方法
1. 无参构造
XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 通过无参构造方法创建bean --><bean id="myBean" class="com.example.MyBean"/></beans>
Java类:
package com.example;public class MyBean {public void doSomething() {System.out.println("Doing something in MyBean");}
}
2. 有参构造
XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 通过有参构造方法创建bean,使用<constructor-arg>指定参数 --><bean id="myBeanWithArgs" class="com.example.MyBeanWithArgs"><constructor-arg value="Argument1"/><constructor-arg value="Argument2"/></bean></beans>
Java类:
package com.example;public class MyBeanWithArgs {private String arg1;private String arg2;public MyBeanWithArgs(String arg1, String arg2) {this.arg1 = arg1;this.arg2 = arg2;}public void doSomething() {System.out.println("Doing something with arguments: " + arg1 + ", " + arg2);}
}
3. 静态工厂方法
XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 通过静态工厂方法创建bean --><bean id="myStaticFactoryBean" class="com.example.MyStaticFactoryBean" factory-method="createMyBean"/></beans>
Java类:
package com.example;public class MyStaticFactoryBean {public static MyBean createMyBean() {return new MyBean();}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean created by static factory");}
}
4. 实例工厂方法
XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 先创建工厂bean --><bean id="myInstanceFactoryBean" class="com.example.MyInstanceFactoryBean"/><!-- 通过实例工厂方法创建bean,使用factory-bean和factory-method属性 --><bean id="myBeanFromFactory" factory-bean="myInstanceFactoryBean" factory-method="createMyBean"/></beans>
Java类:
package com.example;public class MyInstanceFactoryBean {public MyBean createMyBean() {return new MyBean();}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean created by instance factory");}
}
5. 测试
在测试类上使用@ContextConfiguration注解,并指定要加载的配置文件或配置类的位置。例如,如果你有一个名为applicationContext.xml的XML配置文件,可以这样写:
@SpringBootTest
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class MyBeanTest {@Autowiredprivate MyBean myBean;@Testpublic void testMyBean() {// 调用myBean的方法并进行断言myBean.doSomething();// 根据需要添加更多的断言来验证myBean的行为}
}
在这些示例中,XML配置文件定义了bean的创建方式和依赖注入。Java类则定义了bean的逻辑。请注意,对于现代Spring Boot应用,通常推荐使用基于Java的配置,因为它提供了更强大的类型检查和重构支持。然而,在需要与遗留系统或特定库集成时,XML配置仍然是一个有用的选项。
四、注解方式配置Bean介绍
在Spring Boot中,有多种方式可以配置和注册Bean。下面介绍五种方式:
- @Component
- @Configuration+@Bean
- 实现FactoryBean
- 实现BeanDefinitionRegistryPostProcessor
- 实现ImportBeanDefinitionRegistrar
1. 使用 @Component 注解
@Component 注解用于自动检测和注册Bean。通常用于类级别。以下几种注解都是同类型:
@Component:用于标识一个普通的Spring Bean组件。
@Service:用于标识业务逻辑层的Bean。
@Repository:用于标识数据访问层的Bean。
@Controller:用于标识控制层的Bean。
import org.springframework.stereotype.Component;@Component
public class MyComponent {public void doSomething() {System.out.println("Doing something in MyComponent");}
}
使用@Autowired或者@Resource注解在其他类中注入。
2. 使用 @Configuration 和 @Bean 注解
@Configuration 注解用于定义配置类,@Bean 注解用于在配置类中声明Bean。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyConfiguration {@Beanpublic MyBean myBean() {return new MyBean();}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean");}
}
在这个例子中,MyConfiguration是一个配置类,其中@Bean注解的方法myBean用于声明一个Bean。当Spring容器启动时,它会调用这个方法并注册返回的Bean实例。
使用@Autowired或者@Resource注解在其他类中注入。
3. 实现 FactoryBean 接口
FactoryBean 接口允许你自定义Bean的创建过程。
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;@Component
public class MyFactoryBean implements FactoryBean<MyBean> {@Overridepublic MyBean getObject() throws Exception {return new MyBean();}@Overridepublic Class<?> getObjectType() {return MyBean.class;}@Overridepublic boolean isSingleton() {return true;}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean created by MyFactoryBean");}
}
使用@Autowired或者@Resource注解在其他类中注入。
4. 实现 BeanDefinitionRegistryPostProcessor 接口
BeanDefinitionRegistryPostProcessor 接口允许你在Spring容器启动时动态地添加、修改或删除Bean定义。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionRegistry;
import org.springframework.beans.factory.config.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {BeanDefinition beanDefinition = new RootBeanDefinition(MyDynamicBean.class);registry.registerBeanDefinition("myDynamicBean", beanDefinition);}@Overridepublic void postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory beanFactory) throws BeansException {// No-op}
}class MyDynamicBean {public void doSomething() {System.out.println("Doing something in MyDynamicBean");}
}
使用@Autowired或者@Resource注解在其他类中注入。
5. 实现 ImportBeanDefinitionRegistrar 接口
ImportBeanDefinitionRegistrar 接口允许你在使用 @Import 注解时动态地注册Bean定义。
首先,创建一个实现 ImportBeanDefinitionRegistrar 的类:
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {BeanDefinition beanDefinition = new RootBeanDefinition(MyImportedBean.class);registry.registerBeanDefinition("myImportedBean", beanDefinition);}
}class MyImportedBean {public void doSomething() {System.out.println("Doing something in MyImportedBean");}
}
然后,在配置类中使用 @Import 注解导入这个注册器:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyImportingConfiguration {// No additional bean definitions needed here
}
五、XML配置方式和注解配置方式对比
使用XML配置Bean和使用注解方式是Spring框架中两种常见的配置方式,它们各有优缺点。
XML配置Bean的优缺点
优点:
- 集中式管理:XML配置文件是集中式的,可以方便地对所有Bean进行统一管理,不需要和代码绑定,降低了类之间的耦合度。
- 清晰明了:XML配置方式能够清晰地展示对象之间的关系和业务类之间的调用,使得配置信息一目了然。
- 易于扩展:XML配置方式具有良好的扩展性,可以通过添加新的XML文件或修改现有文件来扩展配置。
- 与其他系统交互方便:XML作为一种标准的数据交换格式,使得Spring应用可以方便地与其他系统进行数据交互和共享。
缺点:
- 解析开销大:XML配置文件需要解析,这会占用一定的系统资源,可能会影响应用程序的性能。
- 维护成本高:XML配置文件与代码分离,需要同时维护代码和配置文件,增加了维护成本。
- 类型不安全:XML配置文件中的配置信息在编译期间无法验证其正确性,只能在运行时发现错误,这增加了排查问题的难度。
- 不支持复杂的依赖注入:XML配置方式在处理复杂的依赖注入关系时可能不够灵活和直观。
注解方式配置Bean的优缺点
优点:
简化配置:注解方式将配置信息直接写在代码中,简化了配置文件,降低了维护成本。
类型安全:注解方式可以在编译期间验证配置信息的正确性,减少了运行时错误的可能性。
提高开发效率:注解方式使得配置信息与代码紧密结合,开发者可以更快地理解和修改配置,提高了开发效率。
支持复杂的依赖注入:注解方式可以更灵活和直观地处理复杂的依赖注入关系。
缺点:
- 修改麻烦:如果需要对注解进行修改,通常需要重新编译整个项目,这可能会增加开发周期。
- 可读性较差:注解方式将配置信息嵌入代码中,可能会降低代码的可读性,特别是当注解较多时。
- 可能引入潜在问题:如果注解使用不当,可能会引入一些潜在的问题,如循环依赖、配置错误等。
六、refresh方法解析
Spring Boot依赖于Spring。Bean的管理和实例化是在Spring的org.springframework.context.support.AbstractApplicationContext#refresh方法完成的。
在Spring Boot 3中,refresh方法是Spring容器初始化的核心步骤之一。当Spring容器创建后,它会调用refresh方法来完成一系列的配置和初始化工作。
通过上图可以看到,Spring Boot启动时会调用Spring的org.springframework.context.support.AbstractApplicationContext#refresh方法加载Bean。
下面是refresh方法的源码说明。
核心内容是:
加载或刷新配置的持久化表示(到内存),该配置可能来自基于Java的配置、XML文件、属性文件、关系数据库模式或其他格式。
由于这是一个启动方法,如果失败,它应该销毁已经创建的单例,以避免资源悬空。换句话说,调用此方法后,要么所有单例都被实例化,要么根本没有单例被实例化。
该方法的源码如下:
@Overridepublic void refresh() throws BeansException, IllegalStateException {this.startupShutdownLock.lock();try {this.startupShutdownThread = Thread.currentThread();StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (RuntimeException | Error ex ) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {contextRefresh.end();}}finally {this.startupShutdownThread = null;this.startupShutdownLock.unlock();}}
refresh方法的主要步骤包括:
1、prepareRefresh:为刷新操作做准备
- 设置容器的启动时间和状态
- 撤销关闭状态
- 初始化属性设置
- 检查必备属性是否存在等
2、obtainFreshBeanFactory:
- 设置BeanFactory序列化ID
- 获取新的BeanFactory实例
3、prepareBeanFactory:
- 对BeanFactory进行配置,如设置类加载器、忽略的依赖等
- 添加后置处理器
- 设置忽略的自动装配接口
- 注册一些组件
4、postProcessBeanFactory:允许上下文子类对BeanFactory进行后置处理。
5、invokeBeanFactoryPostProcessors:调用已注册的BeanFactoryPostProcessor实现。
- 调用BeanDefinitionRegistryPostProcessor实现向容器内添加Bean定义
- 调用BeanFactoryPostProcessor实现向容器内Bean的定义添加属性
6、registerBeanPostProcessors:注册BeanPostProcessor实现,用于拦截Bean的创建过程。
- 找到BeanPostProcessor的实现
- 排序后注册进容器
7、initMessageSource:初始化国际化相关的属性。
8、initApplicationEventMulticaster:初始化事件多播器。
9、onRefresh:这是一个空实现,留给子类来创建Web容器等特定操作。
10、registerListeners:将事件监听器注册到事件多播器中。
- 添加容器内的事件监听器到事件多播器
- 派发早期事件
11、finishBeanFactoryInitialization:实例化所有剩余的非懒加载单例Bean。
12、finishRefresh:完成刷新操作,如发布刷新事件、清空缓存等。
- 初始化生命周期处理器
- 调用生命周期处理器onRefresh方法
- 发布ContextRefreshedEvent事件
七、refresh方法中的Bean实例化解析
在refresh方法的finishBeanFactoryInitialization步骤中,Spring容器会实例化所有剩余的非懒加载单例Bean。下面是该方法的源码
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// Initialize conversion service for this context.if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));}// Register a default embedded value resolver if no BeanFactoryPostProcessor// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:// at this point, primarily for resolution in annotation attribute values.if (!beanFactory.hasEmbeddedValueResolver()) {beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));}// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);for (String weaverAwareName : weaverAwareNames) {try {beanFactory.getBean(weaverAwareName, LoadTimeWeaverAware.class);}catch (BeanNotOfRequiredTypeException ex) {if (logger.isDebugEnabled()) {logger.debug("Failed to initialize LoadTimeWeaverAware bean '" + weaverAwareName +"' due to unexpected type mismatch: " + ex.getMessage());}}}// Stop using the temporary ClassLoader for type matching.beanFactory.setTempClassLoader(null);// Allow for caching all bean definition metadata, not expecting further changes.beanFactory.freezeConfiguration();// Instantiate all remaining (non-lazy-init) singletons.beanFactory.preInstantiateSingletons();}
上面代码逻辑如下:
1、初始化转换服务:如果存在 ConversionService 类型的Bean,则将其设置为BeanFactory的转换服务。
2、注册默认的嵌入值解析器:如果没有其他BeanFactoryPostProcessor注册过嵌入值解析器,则注册一个默认的解析器,用于解析注解属性值中的占位符。
3、初始化 LoadTimeWeaverAware 类型的 Bean:提前初始化这些Bean,以便它们可以尽早注册其转换器。
4、停止使用临时类加载器:停止使用临时类加载器进行类型匹配。
5、冻结配置:允许缓存所有Bean定义元数据,不再期望进一步的更改。
6、预实例化单例 Bean:实例化所有剩余的非懒加载单例Bean。
其中第6步对单例的Bean进行实例化。调用方法org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons实例化单例Bean,下面是该方法的源码:
public void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {getBean(beanName);}}else {getBean(beanName);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize").tag("beanName", beanName);smartSingleton.afterSingletonsInstantiated();smartInitialize.end();}}}
preInstantiateSingletons方法的主要功能是在Spring容器启动时预实例化所有非懒加载的单例Bean。代码解释如下:
1、日志记录:如果启用了跟踪日志,记录预实例化的开始信息。
2、获取 Bean 名称列表:创建一个Bean名称列表的副本,以便在初始化过程中可以安全地注册新的Bean定义。
3、初始化非懒加载的单例 Bean:
- 遍历Bean名称列表,获取每个Bean的定义。
- 检查Bean是否是非抽象的、单例的且不是懒加载的。
- 如果是FactoryBean,先获取FactoryBean实例,再根据条件决定是否获取实际的Bean实例。
否则,直接获取Bean实例。
4、触发后初始化回调:
- 再次遍历Bean名称列表,获取每个单例Bean实例。
- 如果Bean实现了SmartInitializingSingleton接口,调用其afterSingletonsInstantiated方法。
- Bean的实例化是调用org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法实现的,源码如下:
public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);}
调用doGetBean方法实现Bean的实例化,源码如下:protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {String beanName = transformedBeanName(name);Object beanInstance;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {// Fail if we're already creating this bean instance:// We're assumably within a circular reference.if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// Check if bean definition exists in this factory.BeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// Not found -> check parent.String nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory abf) {return abf.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);}}if (!typeCheckOnly) {markBeanAsCreated(beanName);}StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate").tag("beanName", name);try {if (requiredType != null) {beanCreation.tag("beanType", requiredType::toString);}RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// Guarantee initialization of beans that the current bean depends on.String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}registerDependentBean(dep, beanName);try {getBean(dep);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}catch (BeanCreationException ex) {if (requiredType != null) {// Wrap exception with current bean metadata but only if specifically// requested (indicated by required type), not for depends-on cascades.throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Failed to initialize dependency '" + ex.getBeanName() + "' of " +requiredType.getSimpleName() + " bean '" + beanName + "': " +ex.getMessage(), ex);}throw ex;}}}// Create bean instance.if (mbd.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);}else 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);}else {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 {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);}}}catch (BeansException ex) {beanCreation.tag("exception", ex.getClass().toString());beanCreation.tag("message", String.valueOf(ex.getMessage()));cleanupAfterBeanCreationFailure(beanName);throw ex;}finally {beanCreation.end();if (!isCacheBeanMetadata()) {clearMergedBeanDefinition(beanName);}}}return adaptBeanInstance(name, beanInstance, requiredType);}
doGetBean方法的主要功能是从BeanFactory中获取指定名称的Bean实例。具体步骤如下:
1、转换 Bean 名称:将传入的Bean名称转换为标准形式。
String beanName = transformedBeanName(name);
2、检查 Singleton 缓存:如果缓存中存在Singleton实例且没有参数,则直接返回。
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {// 返回缓存实例
}
3、检查原型创建状态:如果当前正在创建原型Bean,则抛出异常。
if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);
}
4、查找父工厂:如果当前工厂没有定义该Bean,则委托给父工厂处理。
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// 委托给父工厂
}
5、标记 Bean 创建:标记Bean已经被创建。
if (!typeCheckOnly) {markBeanAsCreated(beanName);
}
6、初始化依赖:初始化当前Bean所依赖的其他Bean。
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {for (String dep : dependsOn) {// 初始化依赖}
}
7、创建 Bean 实例:
if (mbd.isSingleton()) {// 单例
} else if (mbd.isPrototype()) {// 原型
} else {// 其他作用域
}
- 如果是Singleton,则从缓存中获取或创建并缓存。
- 如果是Prototype,则每次调用时都创建新的实例。
- 其他作用域的Bean通过相应的Scope对象创建。
8、处理异常:捕获并处理创建Bean时的异常。
catch (BeansException ex) {// 处理异常
}
9、清理元数据:如果不需要缓存元数据,则清除合并后的Bean定义。
if (!isCacheBeanMetadata()) {clearMergedBeanDefinition(beanName);
}
10、返回 Bean 实例:
return adaptBeanInstance(name, beanInstance, requiredType);
上面代码调用AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])实例化Bean,之后调用SimpleInstantiationStrategy#instantiate(RootBeanDefinition, String, BeanFactory)方法初始化Bean,调用栈如下:
SimpleInstantiationStrategy#instantiate源码如下:
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// Don't override the class with CGLIB if no overrides.if (!bd.hasMethodOverrides()) {Constructor<?> constructorToUse;synchronized (bd.constructorArgumentLock) {constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;if (constructorToUse == null) {Class<?> clazz = bd.getBeanClass();if (clazz.isInterface()) {throw new BeanInstantiationException(clazz, "Specified class is an interface");}try {constructorToUse = clazz.getDeclaredConstructor();bd.resolvedConstructorOrFactoryMethod = constructorToUse;}catch (Throwable ex) {throw new BeanInstantiationException(clazz, "No default constructor found", ex);}}}return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.return instantiateWithMethodInjection(bd, beanName, owner);}}
BeanUtils.instantiateClass(constructorToUse):使用 BeanUtils 类的 instantiateClass 方法,传入已经找到的构造函数 constructorToUse,通过反射机制创建并返回一个新的对象实例。
获得实例化的Bean之后,会在AbstractAutowireCapableBeanFactory#doCreateBean方法接着调用AbstractAutowireCapableBeanFactory#populateBean给Bean填充属性***,源码如下***:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {if (bw == null) {if (mbd.hasPropertyValues()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");}else {// Skip property population phase for null instance.return;}}if (bw.getWrappedClass().isRecord()) {if (mbd.hasPropertyValues()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to a record");}else {// Skip property population phase for records since they are immutable.return;}}// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the// state of the bean before properties are set. This can be used, for example,// to support styles of field injection.if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {return;}}}PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}if (hasInstantiationAwareBeanPostProcessors()) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}pvs = pvsToUse;}}boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);if (needsDepCheck) {PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);checkDependencies(beanName, mbd, filteredPds, pvs);}if (pvs != null) {applyPropertyValues(beanName, mbd, bw, pvs);}}
populateBean 方法的主要作用是填充Bean的属性。在Spring中,Bean的属性可以通过多种方式设置,比如通过构造器注入(Constructor Injection)、属性注入(Setter Injection)或者字段注入(Field Injection)。populateBean 方法负责根据Bean的定义和配置,将这些属性正确地设置到Bean实例中。具体步骤如下:
1、检查 BeanWrapper 是否为空:如果BeanWrapper为空且有属性值,则抛出异常;否则跳过属性填充阶段。
2、检查是否为记录类型:如果BeanWrapper包装的是记录类型且有属性值,则抛出异常;否则跳过属性填充阶段。
3、调用 InstantiationAwareBeanPostProcessors :在设置属性之前,允许InstantiationAwareBeanPostProcessors 修改bean的状态。
4、自动装配属性:根据配置的自动装配模式(按名称或按类型),添加相应的属性值。
5、再次调用 InstantiationAwareBeanPostProcessors :在设置属性之后,允许InstantiationAwareBeanPostProcessors修改属性值。
6、依赖检查:如果需要进行依赖检查,则执行依赖检查。
7、应用属性值:将最终的属性值应用到BeanWrapper中。
下面举个例子来说明populateBean 方法的作用。
在类Demo中注入了Depd,执行完BeanUtils.instantiateClass(constructorToUse)后Demo的depd属性为null。
执行populateBean填充属性后,depd属性就获取了注入的值。
Demo和Depd源码:
@Component
public class Demo {@Autowiredprivate Depd depd;private String name = "demo";}@Component
public class Depd {private String name="Dependency";
}
虽然 populateBean 方法负责填充Bean的属性,但是Bean的完全初始化(比如执行自定义的初始化方法)通常是在这个方法之后进行的。
我们回到AbstractAutowireCapableBeanFactory#doCreateBean方法,根据源码,在调用完populateBean方法后,接着调用了AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)方法。
该方法的源码如下:
/*** Initialize the given bean instance, applying factory callbacks* as well as init methods and bean post processors.* <p>Called from {@link #createBean} for traditionally defined beans,* and from {@link #initializeBean} for existing bean instances.* @param beanName the bean name in the factory (for debugging purposes)* @param bean the new bean instance we may need to initialize* @param mbd the bean definition that the bean was created with* (can also be {@code null}, if given an existing bean instance)* @return the initialized bean instance (potentially wrapped)* @see BeanNameAware* @see BeanClassLoaderAware* @see BeanFactoryAware* @see #applyBeanPostProcessorsBeforeInitialization* @see #invokeInitMethods* @see #applyBeanPostProcessorsAfterInitialization*/@SuppressWarnings("deprecation")protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {invokeAwareMethods(beanName, bean);Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);}if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;}
initializeBean 方法的主要作用是完成Bean的初始化工作。这包括执行任何由Bean定义指定的初始化回调(比如 init-method),以及调用任何由用户定义的 BeanPostProcessor 接口实现所指定的初始化方法。步骤如下:
1、调用Aware方法:通过 invokeAwareMethods 方法调用bean的Aware方法,例如 BeanNameAware、BeanClassLoaderAware 和 BeanFactoryAware。
2、应用 BeanPostProcessor 的前置初始化回调:在正式初始化 Bean 之前,Spring 会调用所有注册的 BeanPostProcessor 的 3】3、postProcessBeforeInitialization 方法。这允许在Bean初始化之前对其进行一些自定义的处理。
3、执行 Bean 的初始化回调:如果Bean定义中指定了 init-method,Spring会调用这个方法来完成Bean的初始化工作。此外,如果Bean实现了 InitializingBean 接口,Spring也会调用其 afterPropertiesSet 方法。
4、应用 BeanPostProcessor 的后置初始化回调:在Bean初始化完成后,Spring会调用所有注册的 BeanPostProcessor 的 postProcessAfterInitialization 方法。这允许在Bean初始化之后对其进行一些额外的处理。
5、Bean 初始化完成:经过上述步骤后,Bean的初始化工作就完成了。此时,Bean已经准备好被应用程序使用。
最后,我们将上面Bean实例化过程整理为一张流程图,如下:
八、面试题
介绍一下IOC思想
IOC(Inversion of Control,控制反转)是Spring以及Spring Boot框架的核心理念之一。它极大地改变了传统的开发方式,帮助开发者更高效、更灵活地构建模块化、可测试的应用。IOC是一种设计原则,旨在将程序中对象创建和依赖管理的控制权从手动管理转移到框架或容器(如Spring容器)中。简单来说,程序中的对象不再由开发者手动创建或管理它们之间的依赖,而是交给Spring容器来负责。通过这种方式,IOC实现了模块间的解耦,提高了代码的灵活性和可维护性。
在Spring Boot中,IOC主要通过依赖注入(Dependency Injection,DI)的方式实现。依赖注入是指在运行时由容器动态地将依赖对象注入到组件中。Spring Boot提供了三种常见的依赖注入方式:构造函数注入、setter方法注入和字段注入。其中,构造函数注入是最推荐的方式,因为它可以保证依赖注入的不可变性,并且能让Spring Boot检查依赖是否完整。
Spring Boot有哪些配置Bean的方式?有何区别?
Spring Boot配置Bean的方式主要有两种:使用注解@Bean注入到Spring IOC容器中,以及使用@Component扫描装配Bean到IOC容器中。
- 使用@Bean注解的方式:这种方式需要在配置类中定义一个方法,并使用@Bean注解来标记该方法。该方法的返回值将是一个Bean实例,Spring容器会将其装配到IOC容器中。这种方式通常用于需要手动创建Bean实例的场景。
- 使用@Component注解的方式:这种方式是通过在类上使用@Component(或其子注解如@Service、@Repository等)来标记该类为Spring容器的一个Bean。然后,Spring容器会在启动时自动扫描这些注解,并将对应的类实例化后装配到IOC容器中。这种方式通常用于自动装配的场景,无需手动创建Bean实例。
两者的主要区别在于:使用@Bean注解的方式需要手动编写配置方法,更加灵活但相对繁琐;而使用@Component注解的方式则更加简洁,适用于自动装配的场景。
介绍以下refresh方法的流程
refresh方法是Spring容器初始化过程中的核心方法之一。它负责完成容器的初始化工作,包括Bean的实例化、初始化等。以下是refresh方法的主要流程:
- 准备刷新:进行一些刷新前的准备工作,如设置容器的活动状态、初始化一些属性等。
- 获取Bean工厂:从容器中获取BeanFactory实例,这是Spring容器的基础设施之一,负责Bean的创建和管理。
- 准备Bean工厂:对BeanFactory进行一些配置和初始化工作,如设置Bean的类加载器、属性编辑器等。
- 后处理BeanFactory:允许用户通过实现BeanFactoryPostProcessor接口来自定义BeanFactory的初始化过程。
- 注册Bean后处理器:注册一些Bean后处理器(如InstantiationAwareBeanPostProcessor等),这些后处理器将在Bean的创建和初始化过程中发挥作用。
- 初始化消息源:初始化应用程序的国际化资源。
- 初始化事件多播器:初始化Spring的事件多播器,用于发布和监听事件。
- 创建Bean:根据配置信息创建Bean实例,并将它们装配到IOC容器中。
- 完成刷新:进行一些刷新后的工作,如发布刷新完成事件等。
介绍一下Bean的实例化过程
Bean的实例化过程是Spring容器创建Bean实例的过程。以下是Bean实例化过程的主要步骤:
- 获取Bean定义:从Bean定义仓库中获取目标Bean的定义信息。这些信息包括Bean的类名、作用域、依赖等。
- 检查Bean定义:对Bean定义进行检查,确保Bean可以被正确创建和初始化。
- 实例化Bean:根据Bean的定义信息创建Bean实例。Spring容器提供了多种实例化策略,如直接调用构造方法、使用工厂方法等。
- 设置Bean属性:对Bean实例的属性进行赋值。这包括通过依赖注入将其他Bean注入到当前Bean中,以及通过配置属性为Bean的字段赋值等。
- 初始化Bean:调用Bean的初始化方法(如@PostConstruct注解的方法),完成Bean的初始化工作。
Bean实例化的扩展点及其作用
在Spring容器中,Bean实例化的扩展点主要包括以下几个:
- ApplicationContextInitializer :在容器刷新之前调用,允许用户在整个Spring容器还没被初始化之前做一些事情,如激活配置、动态字节码注入等。
- BeanDefinitionRegistryPostProcessor :在读取项目中的BeanDefinition之后执行,提供一个补充的扩展点,允许用户动态注册自己的BeanDefinition。
- BeanFactoryPostProcessor :在读取BeanDefinition信息之后、实例化Bean之前调用,允许用户通过实现这个扩展接口来自行处理一些东西,如修改已经注册的BeanDefinition的元信息。
- InstantiationAwareBeanPostProcessor :在Bean的实例化阶段和属性注入阶段提供扩展点,允许用户通过实现这个接口来自定义Bean的实例化过程和属性注入过程。
这些扩展点的作用在于提供了灵活的机制,允许用户在Spring容器的不同阶段进行自定义操作,从而满足各种复杂的应用场景需求。