聚焦IOC容器刷新环节postProcessBeanFactory(BeanFactory后置处理)专项

张彦峰ZYF 2024-08-20 15:05:03 阅读 78

目录

一、IOC容器的刷新环节快速回顾

二、postProcessBeanFactory源码展示分析

(一)模版方法postProcessBeanFactory

(二)AnnotationConfigServletWebServerApplicationContext

调用父类的 postProcessBeanFactory

包扫描

注解类注册

(三)postProcessBeanFactory 主要功能

三、调用父类方法以继承基础设置和逻辑

(一)重要任务回顾

(二)注册 WebApplicationContextServletContextAwareProcessor

(三)忽略 ServletContextAware 接口的依赖

(四)注册 Web 应用程序范围

ExistingWebApplicationScopes 的角色

注册 Web 应用程序作用域

四、执行包扫描

五、注解类注册

(一)扫描和注册注解类的过程步骤

扫描注解类

创建 BeanDefinition

注册 BeanDefinition

(二)对注解类注册的理解新思路

新的思路

六、总结


干货分享,感谢您的阅读!

在很早之前我们单独写过一篇文章《分析SpringBoot启动配置原理》,具体可见:

分析SpringBoot启动配置原理_spring启动加载顺序及原理-CSDN博客文章浏览阅读1.6w次,点赞15次,收藏43次。分析SpringBoot启动配置原理:给出整体初步分析和对应流程图,并从三方面进行展开分析(SpringApplication构造过程分析+SpringApplication启动过程分析+SpringBoot自动配置分析)_spring启动加载顺序及原理

https://blog.csdn.net/xiaofeng10330111/article/details/130903779?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171829487016800213028572%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171829487016800213028572&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-130903779-null-null.nonecase&utm_term=%E5%88%86%E6%9E%90SpringBoot%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E5%8E%9F%E7%90%86&spm=1018.2226.3001.4450其中IOC容器的刷新环节可当重点分析,值得在读源码时进行深入分析,我们会从多个方向上再次进行分析回顾和学习。历史其他专项展示:

具体内容 具体链接
探究Spring BeanFactory 重看Spring聚焦BeanFactory分析_java beanfactory 实现类-CSDN博客
探究Spring ApplicationContext 重看Spring聚焦ApplicationContext分析_applicationcontext消息资源处理-CSDN博客
ApplicationContext vs BeanFactory 解锁ApplicationContext vs BeanFactory: 谁更具选择性?-CSDN博客
探究Spring Environment 重看Spring聚焦Environment分析-CSDN博客
探究Spring BeanDefintion 重看Spring聚焦BeanDefinition分析和构造-CSDN博客
对焦后置处理器 聚焦Spring后置处理器分析对比_spring的后置处理器分析-CSDN博客
BeanFactory - obtainFreshBeanFactory专项 聚焦IOC容器刷新环节obtainFreshBeanFactory初始化BeanFactory专项_refreshbeanfactory-CSDN博客
prepareBeanFactory专项 聚焦IOC容器刷新环节prepareBeanFactory专项-CSDN博客

一、IOC容器的刷新环节快速回顾

我们将AbstractApplicationContext的refresh方法源码提取并进行重点代码标注说明如下:

<code>public abstract class AbstractApplicationContext implements ApplicationContext {

@Override

public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

// 准备上下文环境,包括初始化工厂、后置处理器等

prepareRefresh();

// 创建并初始化 BeanFactory

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 设置 BeanFactory 的类加载器、资源加载器等

prepareBeanFactory(beanFactory);

try {

// 允许子类对 BeanFactory 进行进一步的自定义处理

postProcessBeanFactory(beanFactory);

// 调用 BeanFactoryPostProcessors 进行后置处理

invokeBeanFactoryPostProcessors(beanFactory);

// 注册 BeanPostProcessors,用于对 Bean 实例进行后置处理

registerBeanPostProcessors(beanFactory);

// 初始化消息源

initMessageSource();

// 初始化事件广播器

initApplicationEventMulticaster();

// 初始化其他特殊 Bean

onRefresh();

// 注册关闭钩子

registerListeners();

// 初始化所有剩余的单例 Bean

finishBeanFactoryInitialization(beanFactory);

// 完成上下文刷新

finishRefresh();

} catch (BeansException ex) {

if (logger.isWarnEnabled()) {

logger.warn("Exception encountered during context initialization - " +

"cancelling refresh attempt: " + ex);

}

// 销毁已创建的 Bean,关闭容器

destroyBeans();

// 重置容器刷新标志,允许再次刷新

cancelRefresh(ex);

// 把异常重新抛出,允许调用者处理

throw ex;

} finally {

// 重置已注册的 JVM 关闭钩子

resetCommonCaches();

}

}

}

}

以上内容请多次翻看并理解(如果忘记了最好在次读一下之前的原文博客进行基本的回顾),我们本次讲聚焦其中的postProcessBeanFactory专项展开分析。

二、postProcessBeanFactory源码展示分析

postProcessBeanFactory 是一个允许子类在 BeanFactory 完成初始化之后但在 Bean 实例化之前对其进行进一步自定义的钩子方法。虽然在 AbstractApplicationContext 中该方法是一个空实现(该方法就是一个模版方法),但子类可以覆盖此方法以添加特定的逻辑。

(一)模版方法postProcessBeanFactory

AbstractApplicationContext 类中,postProcessBeanFactory 方法定义如下:

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

// 默认实现为空,子类可以覆盖此方法进行自定义处理

}

直观的看下有哪些子类进行了具体的实现:

以上是几个常见子类对 <code>postProcessBeanFactory 方法的具体实现,我们一般探究其中一个即可理解体会,其他可当作扩展去学习。

(二)AnnotationConfigServletWebServerApplicationContext

我们以AnnotationConfigServletWebServerApplicationContext为基本去体会一下

@Override

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

super.postProcessBeanFactory(beanFactory);

if (this.basePackages != null && this.basePackages.length > 0) {

this.scanner.scan(this.basePackages);

}

if (!this.annotatedClasses.isEmpty()) {

this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));

}

}

调用父类的 postProcessBeanFactory

super.postProcessBeanFactory(beanFactory);

确保父类 postProcessBeanFactory 方法中的所有逻辑都会被执行。通过调用父类的方法,继承了父类的初始化和处理逻辑,常包括一些基本的设置和注册操作,为后续的自定义逻辑打下基础。

包扫描

if (this.basePackages != null && this.basePackages.length > 0) {

this.scanner.scan(this.basePackages);

}

this.scanner 是一个 ClassPathBeanDefinitionScanner 实例,它负责扫描指定的包路径 (basePackages),从而找到并注册符合条件的 Bean,它会扫描指定的包路径,寻找符合条件的组件类(如带有 @Component@Service@Repository@Controller 注解的类),并将它们注册为 Bean 定义。

注解类注册

if (!this.annotatedClasses.isEmpty()) {

this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));

}

this.reader 是一个 AnnotatedBeanDefinitionReader 实例,它负责将指定的注解类 (annotatedClasses,如带有 @Configuration@Component 注解的类) 注册到 BeanFactory 中,从而使这些类能够参与到 Spring 的依赖注入和生命周期管理中。ClassUtils.toClassArray将一个 Set<Class<?>> 转换为一个 Class<?>[] 数组,这个转换在将注解类注册到 AnnotatedBeanDefinitionReader 中时非常有用。 

(三)postProcessBeanFactory 主要功能

从上述分析可以看出,在 Spring IOC 容器中,postProcessBeanFactory 方法的主要功能可归纳为:

调用父类方法以继承基础设置和逻辑: 确保基本的设置和注册操作在子类中得到执行。

进行包扫描: 扫描指定的包路径,自动发现并注册符合条件的组件类,可以通过注解配置的方式管理 Bean,减少手动注册的工作量。

注册注解类: 将特定的注解类注册到 BeanFactory 中,使其参与到 Spring 的依赖注入和生命周期管理中。

三、调用父类方法以继承基础设置和逻辑

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));

beanFactory.ignoreDependencyInterface(ServletContextAware.class);

this.registerWebApplicationScopes();

}

(一)重要任务回顾

postProcessBeanFactory 方法主要完成了以下几个重要任务:

注册 WebApplicationContextServletContextAwareProcessor: 添加一个 BeanPostProcessor,处理实现了 ServletContextAware 接口的 bean,注入 ServletContext 对象。

忽略 ServletContextAware 接口的依赖: 告诉 BeanFactory 忽略对 ServletContextAware 接口的自动依赖注入。

注册 Web 应用程序范围: 注册与 Web 应用程序相关的作用域,使得 bean 可以在 requestsessionapplication 范围内被管理。

这些步骤使得 Spring 容器能够更好地支持 Web 应用程序的特定需求,为处理 HTTP 请求、会话管理和应用程序范围内的 bean 提供了必要的基础设施。

(二)注册 WebApplicationContextServletContextAwareProcessor

WebApplicationContextServletContextAwareProcessor 类实现了 BeanPostProcessor 接口,用于在 bean 初始化之前和之后进行自定义的处理。

public class WebApplicationContextServletContextAwareProcessor implements BeanPostProcessor {

private final WebApplicationContext webApplicationContext;

public WebApplicationContextServletContextAwareProcessor(WebApplicationContext webApplicationContext) {

this.webApplicationContext = webApplicationContext;

}

@Override

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

if (bean instanceof ServletContextAware) {

((ServletContextAware) bean).setServletContext(this.webApplicationContext.getServletContext());

}

return bean;

}

@Override

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

return bean;

}

}

这个类主要的作用是处理实现了 ServletContextAware 接口的 bean,并将 ServletContext 注入到这些 bean 中。

(三)忽略 ServletContextAware 接口的依赖

ignoreDependencyInterface 方法的作用是告诉 BeanFactory 在处理自动装配时忽略特定接口的依赖注入。也就是说,当 BeanFactory 遇到实现了该接口的 bean 时,不会自动尝试注入该接口的依赖。

public interface ServletContextAware {

void setServletContext(ServletContext servletContext);

}

ServletContextAware 是一个标记接口,用于获取 ServletContext 对象。实现了该接口的 bean 需要 ServletContext,这在 Web 应用程序中非常常见。

如果不忽略 ServletContextAware 接口的依赖,Spring 容器在创建 bean 时会尝试自动注入 ServletContext 对象。然而,在 Spring 的设计中,ServletContext 的注入通常是通过 BeanPostProcessor 来处理的,而不是通过自动装配。因此,需要忽略该接口的自动依赖注入,防止 Spring 容器在自动装配时出错。

(四)注册 Web 应用程序范围

private void registerWebApplicationScopes() {

ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(this.getBeanFactory());

WebApplicationContextUtils.registerWebApplicationScopes(this.getBeanFactory());

existingScopes.restore();

}

Spring 提供了几种标准的 Web 应用程序作用域:

Request Scope(请求作用域):每个 HTTP 请求都会创建一个新的 bean 实例,该 bean 在请求结束时被销毁。适合存储与单个请求相关的数据,如表单数据或请求参数处理器。

Session Scope(会话作用域):每个 HTTP 会话期间创建一个 bean 实例,该 bean 在会话结束时被销毁。适合存储需要在用户会话期间保持状态的数据,如用户登录信息或购物车内容。

Application Scope(应用程序作用域):整个 Web 应用程序中仅创建一个 bean 实例,该 bean 与 ServletContext 绑定。适合存储全局配置信息或共享的资源,如全局缓存或系统配置。

ExistingWebApplicationScopes 的角色

public static class ExistingWebApplicationScopes {

private static final Set<String> SCOPES;

private final ConfigurableListableBeanFactory beanFactory;

private final Map<String, Scope> scopes = new HashMap();

public ExistingWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) {

this.beanFactory = beanFactory;

Iterator var2 = SCOPES.iterator();

while(var2.hasNext()) {

String scopeName = (String)var2.next();

Scope scope = beanFactory.getRegisteredScope(scopeName);

if (scope != null) {

this.scopes.put(scopeName, scope);

}

}

}

public void restore() {

this.scopes.forEach((key, value) -> {

if (ServletWebServerApplicationContext.logger.isInfoEnabled()) {

ServletWebServerApplicationContext.logger.info("Restoring user defined scope " + key);

}

this.beanFactory.registerScope(key, value);

});

}

static {

Set<String> scopes = new LinkedHashSet();

scopes.add("request");

scopes.add("session");

SCOPES = Collections.unmodifiableSet(scopes);

}

}

ExistingWebApplicationScopes 类的主要作用在于管理和保护已有的自定义作用域配置。它通过以下方式实现:

保存当前状态: 在初始化时,ExistingWebApplicationScopes 会获取当前注册的作用域信息并保存到内部的 scopes 映射中。这包括了所有已定义的作用域,如 "request""session"

恢复状态: 在需要时,比如在注册标准 Web 应用程序作用域之后,ExistingWebApplicationScopes 可以恢复之前保存的作用域配置。这样可以确保注册标准作用域不会覆盖或修改已有的自定义作用域定义。

在技术实现上,ExistingWebApplicationScopes 利用 Spring 框架提供的 ConfigurableListableBeanFactory 接口来管理作用域信息。它通过迭代已定义的标准作用域集合(如 "request""session")并与实际注册的作用域进行匹配,来确保作用域配置的一致性和正确性。

注册 Web 应用程序作用域

WebApplicationContextUtils.registerWebApplicationScopes() 方法在 Spring 框架中用于注册与 Web 应用程序相关的标准作用域,如 "request""session""application"

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) {

registerWebApplicationScopes(beanFactory, (ServletContext)null);

}

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, @Nullable ServletContext sc) {

beanFactory.registerScope("request", new RequestScope());

beanFactory.registerScope("session", new SessionScope());

if (sc != null) {

ServletContextScope appScope = new ServletContextScope(sc);

beanFactory.registerScope("application", appScope);

sc.setAttribute(ServletContextScope.class.getName(), appScope);

}

beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());

beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());

beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());

beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());

if (jsfPresent) {

WebApplicationContextUtils.FacesDependencyRegistrar.registerFacesDependencies(beanFactory);

}

}

registerWebApplicationScopes() 方法的主要任务是确保 BeanFactory 支持这些标准的 Web 应用程序作用域:

注册作用域实例: 方法会向 BeanFactory 中注册相应的作用域实例,如 RequestScopeSessionScopeServletContextScope

配置作用域支持: 如果 BeanFactory 尚未配置支持这些作用域,方法可能会进行配置以确保它们能够正确地管理和维护这些作用域。

确保作用域生命周期管理: 对于每种作用域,Spring 确保在适当的时机创建、销毁和管理 bean 实例。例如,在请求结束后销毁请求作用域的 bean 实例,以避免内存泄漏和资源浪费。

 registerWebApplicationScopes() 方法的实现会调整 BeanFactory,以便能够正确地管理和控制这些作用域,确保了在多用户、多请求的环境中,每个 bean 实例都能按预期的方式进行创建和销毁,从而保证了应用程序的稳定性和性能。

四、执行包扫描

// 执行包扫描,自动注册符合条件的组件类

if (this.basePackages != null && this.basePackages.length > 0) {

this.scanner.scan(this.basePackages);

}

 通过包扫描,Spring 可以自动发现项目中符合特定条件的类,这些类通常被标注了诸如 @Component@Service@Controller 等注解,或者是配置类,其避免了手动在配置文件中一一列出每个需要注册的 Bean,减少了配置的工作量。

根据配置的条件(如 this.basePackages),扫描器可以只扫描特定的包路径,而非整个类路径,从而精确地控制哪些类需要被注册为 Bean。

scanner 是一个专门用于扫描指定包路径下类的工具或组件。它能够递归地搜索指定包及其子包中的类文件。一旦扫描器发现符合条件的类,Spring 将调用相应的注册方法将这些类注册为 Bean。

对于每个扫描到的类,Spring 将创建一个对应的 BeanDefinition,用于后续的 Bean 实例化和依赖注入。 

 应用场景和实际意义

模块化开发:通过自动注册,不同模块的组件可以自动装配,降低了模块间耦合度。动态可扩展性:允许开发者通过简单地添加新的组件类(如新的 @Component 类)来扩展应用的功能,而不需要修改现有的配置文件。 

执行包扫描并自动注册符合条件的组件类是 Spring 框架中支持依赖注入和面向组件的核心功能之一,通过简化配置提高开发效率。

五、注解类注册

// 注册额外的注解配置类

if (!this.annotatedClasses.isEmpty()) {

this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));

}

注册注解类的主要目的是让 Spring 容器能够识别和管理这些类,将其纳入到整个应用程序的 Bean 管理体系中。通过这种方式,注解类可以享受 Spring 提供的各种功能,如依赖注入(DI)、面向方面编程(AOP)、生命周期回调等。

(一)扫描和注册注解类的过程步骤

postProcessBeanFactory 方法中,通过扫描和注册注解类的过程可以分为以下几个步骤:

扫描注解类

首先,Spring 会扫描指定包路径下的类,找到所有带有特定注解的类。Spring 中的扫描器通常是 ClassPathBeanDefinitionScanner,它能够递归地扫描指定包路径下的类文件,并根据配置的过滤条件(如注解)决定是否将其注册为 Bean。

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);

scanner.scan(basePackages);

常见的注解包括:

@Component@Service@Repository@Controller其他自定义注解

这些注解用于标识该类是一个 Spring 管理的组件。这部分其实是上一节中讲解的。

创建 BeanDefinition

对于每一个扫描到的注解类,Spring 会创建一个 BeanDefinition 对象。BeanDefinition 描述了 Bean 的各种属性和元数据,如 Bean 的类名、作用域、初始化和销毁方法等。

扫描器在找到符合条件的类后,会调用 AnnotatedBeanDefinitionReader 或类似的类来创建 BeanDefinition

AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(beanFactory);

reader.register(annotatedClasses.toArray(new Class[0]));

注册 BeanDefinition

创建好 BeanDefinition 之后,Spring 会将其注册到 BeanFactory 中。这一步骤通常由 BeanDefinitionRegistry 接口的 registerBeanDefinition 方法来完成。

BeanDefinitionRegistry 接口提供了 registerBeanDefinition 方法,用于将创建好的 BeanDefinition 注册到 BeanFactory 中:

beanFactory.registerBeanDefinition(beanName, beanDefinition);

postProcessBeanFactory 方法通过注册注解类,使这些类能够参与到 Spring 的依赖注入和生命周期管理中。这一过程包括扫描指定包路径、创建并注册 BeanDefinition 等步骤,通过自动化的组件发现和注册机制,Spring 极大地简化了应用程序的配置和管理工作。

(二)对注解类注册的理解新思路

注解类的注册不仅仅是简单的扫描和注册,其实可以通过扩展和优化,比如可以在这里增加:

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

super.postProcessBeanFactory(beanFactory);

// 执行包扫描,自动注册符合条件的组件类

if (this.basePackages != null && this.basePackages.length > 0) {

this.scanner.scan(this.basePackages);

}

// 动态注册额外的注解配置类

if (!this.annotatedClasses.isEmpty()) {

for (Class<?> annotatedClass : this.annotatedClasses) {

if (shouldRegister(annotatedClass)) {

this.reader.register(annotatedClass);

}

}

}

}

// 判断是否注册某个注解类的逻辑

private boolean shouldRegister(Class<?> annotatedClass) {

// 根据某些条件决定是否注册

// 例如,检查注解类上是否有特定注解,或者根据配置文件中的设置

return true;

}

新的思路

支持更多注解类型:除了常见的 Spring 注解,还可以支持更多的自定义注解或第三方注解。这可以通过扩展 AnnotatedBeanDefinitionReader 来实现,增加对其他注解类型的解析和处理。动态注解类注册:在应用运行过程中,根据某些条件动态决定是否注册某些注解类。比如,可以通过配置文件或者数据库表来决定需要注册哪些类,从而实现更加灵活的 Bean 管理。注解类的优先级管理:对于不同的注解类,可以设置不同的优先级。在注册时,根据优先级决定注册顺序,从而控制 Bean 的初始化顺序。这在一些复杂应用中非常有用,可以避免由于 Bean 初始化顺序引起的问题。条件性注解注册:可以根据当前的环境或者配置条件决定是否注册某些注解类。例如,只有在特定的 Profile(如开发环境或生产环境)下才注册某些类,从而实现环境隔离和配置灵活性。

六、总结

在 Spring IOC 容器中,postProcessBeanFactory 方法作为一个关键的扩展点,允许子类在 BeanFactory 初始化后、Bean 实例化前进行进一步自定义处理。通过对 AnnotationConfigServletWebServerApplicationContext 类的分析,我们理解了该方法的主要功能和实现,包括调用父类方法以继承基础设置和逻辑、执行包扫描、自动注册符合条件的组件类以及注解类注册等关键步骤。

包扫描和注解类注册是 postProcessBeanFactory 的核心功能,能够自动发现并注册带有特定注解的类,这不仅简化了配置,还增强了应用的模块化和动态可扩展性。通过扩展和优化,如支持更多注解类型、动态注解类注册、注解类优先级管理和条件性注解注册,可以进一步提升 Spring 框架的灵活性和功能性,适应复杂应用的需求。

这些机制确保了 Spring 容器能够更好地支持 Web 应用程序的特定需求,提供必要的基础设施,从而保证了应用程序的稳定性和性能。在实际开发中,通过合理运用这些功能,可以大大简化配置、提高开发效率,并增强应用的可维护性和扩展性。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。