Java外功核心(7)——深入源码,拆解Spring Bean作用域、生命周期与自动装配

Java外功核心(7)——深入源码,拆解Spring Bean作用域、生命周期与自动装配
SpringBoot版本:3.5.8

1.Bean的作用域

Spring Bean的作用域定义了Bean的作用范围,即Bean在哪些上下文中可用
/** * 实体类 */publicclassDog{}/** * 配置类 */@ConfigurationpublicclassDogConfig{//单例@Bean@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)publicDogsingleDog(){returnnewDog();}//原型@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)publicDogprototypeDog(){returnnewDog();}//请求@Bean@RequestScopepublicDogrequestDog(){returnnewDog();}//会话@Bean@SessionScopepublicDogsessionDog(){returnnewDog();}//应用@Bean@ApplicationScopepublicDogapplicationDog(){returnnewDog();}}/** * 启动类 */@SpringBootApplicationpublicclassSpringPrincipleApplication{publicstaticvoidmain(String[] args){SpringApplication.run(SpringPrincipleApplication.class, args);}}/** * 视图层 */@RequestMapping("/test")@RestControllerpublicclassTestController{privatefinalDog single;privatefinalDog prototype;privatefinalDog request;privatefinalDog session;privatefinalDog application;privatefinalApplicationContext context;publicTestController(@Qualifier("singleDog")Dog single,@Qualifier("prototypeDog")Dog prototype,@Qualifier("requestDog")Dog request,@Qualifier("sessionDog")Dog session,@Qualifier("applicationDog")Dog application,ApplicationContext context){this.single = single;this.prototype = prototype;this.request = request;this.session = session;this.application = application;this.context = context;}@RequestMapping("/single")publicStringsingle(){Dog singleDog =(Dog)context.getBean("singleDog");return"dog: "+this.single.toString()+"<br>"+"contextDog: "+ singleDog;}@RequestMapping("/prototype")publicStringprototype(){Dog prototypeDog =(Dog)context.getBean("prototypeDog");return"dog: "+this.prototype.toString()+"<br>"+"contextDog: "+ prototypeDog;}@RequestMapping("/request")publicStringrequest(){Dog requestDog =(Dog)context.getBean("requestDog");return"dog: "+this.request.toString()+"<br>"+"contextDog: "+ requestDog;}@RequestMapping("/session")publicStringsession(){Dog sessionDog =(Dog)context.getBean("sessionDog");return"dog: "+this.session.toString()+"<br>"+"contextDog: "+ sessionDog;}@RequestMapping("/application")publicStringapplication(){Dog applicationDog =(Dog)context.getBean("applicationDog");return"dog: "+this.application.toString()+"<br>"+"contextDog: "+ applicationDog;}}
    • 直接通过context.getBean()获取会触发新实例创建
    • 当使用@Autowired@Resource注入prototype作用域的Bean时:注入操作仅在初始化阶段(Spring容器创建和配置Bean的过程)执行一次,后续通过字段引用访问的是最初注入的实例,不会因后续请求自动重新注入新实例
    • 1.代理注入:当使用@Autowired注入Request作用域的Bean时,Spring实际上注入的是一个代理对象而非真实实例。代理对象在应用启动时就被注入到依赖它的单例Bean中,但真实实例的创建被延迟到HTTP请求发生时
    • 2.方法调用:注入的代理对象内部持有对当前HTTP请求上下文的引用。当调用代理对象的方法时,代理会从当前请求的上下文中查找或创建新的真实实例
    • 3.实例操作:虽然依赖注入发生在容器初始化阶段,但通过代理模式将实例的获取延迟到实际方法调用时,这种延迟查找机制确保每个请求线程都能获得独立的实例

Application(应用):整个Web应用共享一个Bean实例

在这里插入图片描述

Session(会话):每个用户会话创建一个Bean实例,仅在Web应用中有效

在这里插入图片描述

Request(请求):每个HTTP请求创建一个新的Bean实例,仅在Web应用中有效

在这里插入图片描述

Prototype(原型):每次请求Bean时都会创建一个新的实例

在这里插入图片描述

Singleton(单例):默认作用域,每个Spring容器中仅存在一个Bean实例

在这里插入图片描述

2. Bean的生命周期

生命周期指的是一个对象从创建到销毁的整个生命过程。Bean的生命周期分为以下5个部分:

  • 实例化:容器通过反射调用Bean的构造器创建对象实例
  • 属性赋值:容器注入依赖的属性值(例如@Autowired)
  • 初始化:
    • 通知方法调用:通过特定接口(如Spring的BeanNameAware)注入框架相关依赖或上下文信息
    • 前置处理:进行准备工作,例如参数校验、资源加载或权限检查。确保主逻辑具备执行条件,避免运行时错误
    • 初始化回调:在对象初始化阶段触发的自定义逻辑(如实现InitializingBean接口的afterPropertiesSet方法)。用于完成属性设置后的额外初始化操作
    • 后置处理:在流程结束后执行清理或结果处理(如AOP中的@After通知)
  • 使用Bean:Bean进入就绪状态,可被应用程序调用
  • 销毁Bean:容器关闭时触发销毁

2.1 示例

/** * 实体类 */publicclassCat{}/** * 配置类 */@ConfigurationpublicclassCatConfig{@BeanpublicCatcat(){returnnewCat();}}/** * 实现类 */@Component@Slf4jpublicclassBeanLifeComponentimplementsBeanNameAware,BeanPostProcessor,InitializingBean{privateCat cat;//1.实例化:执行构造方法publicBeanLifeComponent(){ log.info("1.实例化:执行构造方法");}//2.属性赋值:执行setter方法@AutowiredpublicvoidsetCat(Cat cat){ log.info("2.属性赋值:执行setter方法");this.cat = cat;}//3.1 通知方法调用@OverridepublicvoidsetBeanName(String name){ log.info("3.1 通知方法调用,bean name is {}", name);}//3.2 前置处理@OverridepublicObjectpostProcessBeforeInitialization(Object bean,String beanName)throwsBeansException{ log.info("3.3 前置处理,bean:{},beanName:{}", bean, beanName);returnBeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}//3.3初始化回调@OverridepublicvoidafterPropertiesSet(){ log.info("3.2初始化回调");}//3.4后置处理@OverridepublicObjectpostProcessAfterInitialization(Object bean,String beanName)throwsBeansException{ log.info("3.4后置处理,bean:{},beanName:{}", bean, beanName);returnBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}//4.使用Beanpublicvoiduse(){ log.info("4.使用Bean");}//5.销毁Bean@PreDestroypublicvoidpreDestroy(){ log.info("5.销毁Bean");}}/** * 测试类 */@SpringBootTestclassSpringPrincipleApplicationTests{privatefinalApplicationContext context;@AutowiredpublicSpringPrincipleApplicationTests(ApplicationContext context){this.context = context;}@Testpublicvoidtest(){BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class); beanLifeComponent.use();}}
在这里插入图片描述
日志显示的顺序是:invokeInitMethods(初始化回调)applyBeanPostProcessorsBeforeInitialization(前置处理)applyBeanPostProcessorsAfterInitialization(后置处理)

这看起来与我上述介绍的生命周期流程相矛盾,具体解释放在源码解析后

2.2 源码解析

AbstractAutowireCapableBeanFactory 类的作用:主要负责Bean的创建、依赖注入以及初始化等生命周期管理
(该思维导图中的图片看不清楚,原图放在下述流程中)
  • 1.搜索AbstractAutowireCapableBeanFactory 类,并找到它的createBean方法
  • 3.doCreateBean方法中依次调用了createBeanInstancepopulateBeaninitializeBean方法

3.3.4

在这里插入图片描述

3.3.3

在这里插入图片描述

3.3.2

在这里插入图片描述

3.3.1

在这里插入图片描述

3.3 初始化:initializeBean方法中依次调用了invokeAwareMethodsapplyBeanPostProcessorsBeforeInitializationinvokeInitMethodsapplyBeanPostProcessorsAfterInitialization方法

在这里插入图片描述

3.2

在这里插入图片描述

3.1

在这里插入图片描述

2.createBean方法中调用了doCreateBean方法

在这里插入图片描述

2.3 日志与源码“冲突”的原因分析

根据源码来看,正确的生命周期流程是:前置处理 → 初始化回调 → 后置处理。与上述我运行输出的日志相矛盾

在这里插入图片描述
关键点:BeanPostProcessor本身也是Bean,当Spring初始化一个BeanPostProcessor实现类(BeanLifeComponent类)时,这个过程是递归的

执行流程分析:

  • 1.创建非BeanPostProcessor Bean(BeanLifeComponent类):
    • 按照源码顺序正常执行:BeforeInitialization → InitMethods → AfterInitialization
  • 2.创建BeanPostProcessor Bean时:
    • Spring需要先让这个BeanPostProcessor对象本身完成初始化(调用invokeInitMethods)
    • 然后才能将它加入到BeanPostProcessor列表中,供后续其他Bean(BeanLifeComponent类)使用
    • 但对于这个BeanPostProcessor Bean自己来说:
      • 它自己的afterPropertiesSet方法会在invokeInitMethods中执行(初始化回调阶段)
      • 但它自己的postProcessBeforeInitialization(前置处理)/postProcessAfterInitialization(后置)方法不会在它自己的创建过程中被调用!

上述日志中的情况解释:日志显示的是 一个BeanPostProcessor实现类(BeanLifeComponent类)的初始化过程:

  • 3.2 初始化回调 - 这个BeanPostProcessor Bean自己的afterPropertiesSet()
  • 3.3 前置处理 - BeanLifeComponent类对这个BeanPostProcessor Bean的处理
  • 3.4 后置处理 - BeanLifeComponent类对这个BeanPostProcessor Bean的处理

3.SpringBoot自动装配

作用:自动注册Bean到Spring容器,不需要手动配置,通过约定大于配置的方式减少手动配置的复杂性。换言之,SpringBoo的自动配置就是将依赖Jar包中的配置类以及Bean加载到Ioc容器的过程

3.1 SpringBoot加载Bean

在pom.xml文件中引入第三方依赖,实际上就是将第三方代码引入到SpringBoot项目中。SpringBoot项目在启动时能识别这些依赖并自动将它们的配置类以及Bean加载到Ioc容器的过程。下面,我将编写代码作为第三方依赖,深入解析SpringBoot加载Bean的原理
  • 错误日志解析:在Java外功精要(2)——Spring IoC&DI一文中有详细介绍。Spring通过五大注解 + @Bean可以将Bean加载到Ioc容器中,前提是这些注解类需要保证和SpringBoot启动类(@SpringBootApplication)在同一目录或者其子目录下

运行项目

在这里插入图片描述

编写代码

/** * 配置类 */@Slf4j@ComponentpublicclassTestConfig{publicvoiddemo(){ log.info("demo");}}/** * 测试类 */@SpringBootTestpublicclassSpringPrincipleApplicationTests{privatefinalApplicationContext context;@AutowiredpublicSpringPrincipleApplicationTests(ApplicationContext context){this.context = context;}@Testpublicvoiddemo(){TestConfig testConfig = context.getBean(TestConfig.class); testConfig.demo();}}

创建目录

在这里插入图片描述
下面介绍几种解决方法

3.1.1 @ComponentScan

作用:告诉Spring容器去哪里扫描那些被@Component、@Service、@Repository、@Controller等注解标记的类,并将它们自动注册为Bean

@SpringBootApplication@ComponentScan("com.example.springprincicle.component")publicclassSpringPrincipleApplication{publicstaticvoidmain(String[] args){SpringApplication.run(SpringPrincipleApplication.class, args);}}
在这里插入图片描述

3.1.2 @Import

作用:用于将一个或多个配置类、组件类或其他类导入到当前的Spring应用上下文中

用法二:ImportSelector接口实现类

@Slf4j@ComponentpublicclassDemoConfig{publicvoiddemo(){ log.info("demo");}}publicclassMyImportSelectorimplementsImportSelector{@OverridepublicString[]selectImports(AnnotationMetadata importingClassMetadata){returnnewString[]{"com.example.springprincicle.component.TestConfig","com.example.springprincicle.component.DemoConfig"};}}@SpringBootTestpublicclassSpringPrincipleApplicationTests{privatefinalApplicationContext context;@AutowiredpublicSpringPrincipleApplicationTests(ApplicationContext context){this.context = context;}@Testpublicvoiddemo(){TestConfig testConfig = context.getBean(TestConfig.class); testConfig.demo();DemoConfig demoConfig = context.getBean(DemoConfig.class); demoConfig.demo();}}
在这里插入图片描述

用法一:导入单个类

@SpringBootApplication@Import(TestConfig.class)publicclassSpringPrincipleApplication{publicstaticvoidmain(String[] args){SpringApplication.run(SpringPrincipleApplication.class, args);}}
在这里插入图片描述

3.1.3 自定义注解

在使用@Import注解导入Bean时,需要程序员熟悉第三方依赖的所有配置类、组件类或其他类,这对于程序员开发程序十分不友好。所以,应该由依赖的开发者来做这件事

比较常见的方案就是第三方依赖给我们提供一个注解,该注解内部封装@Import注解

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Import(MyImportSelector.class)public@interfaceEnableTestConfig{}@SpringBootApplication@EnableTestConfigpublicclassSpringPrincipleApplication{publicstaticvoidmain(String[] args){SpringApplication.run(SpringPrincipleApplication.class, args);}}

3.2 @SpringBootApplication源码解析

@SpringBootApplication是一个组合注解,包含以下三个核心注解的功能

3.2.1 @ComponentScan

在这里插入图片描述
  • 排除不需要扫描的配置类,防止自动配置类被重复扫描或注册
  • @SpringBootApplication注解中,@ComponentScan注解没有指定扫描路径,那么默认扫描路径为 @SpringBootApplication标注的类的类路径

3.2.2 @SpringBootConfiguration

在这里插入图片描述
  • 标记该类为Spring的配置类
  • @Indexed:为Spring的组件扫描提供索引支持,加速应用启动时的类加载过程

3.2.3 @EnableAutoConfiguration


AutoConfigurationImportSelector类:它实现了 DeferredImportSelector 接口,负责在Spring 应用启动时动态加载自动配置类
//JDK源码,AutoConfigurationImportSelector类@OverridepublicString[]selectImports(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){returnNO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry =getAutoConfigurationEntry(annotationMetadata);returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}protectedAutoConfigurationEntrygetAutoConfigurationEntry(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){returnEMPTY_ENTRY;}AnnotationAttributes attributes =getAttributes(annotationMetadata);List<String> configurations =getCandidateConfigurations(annotationMetadata, attributes); configurations =removeDuplicates(configurations);Set<String> exclusions =getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations =getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);returnnewAutoConfigurationEntry(configurations, exclusions);}

fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions):体现了Spring Framework/Spring Boot的向后兼容性设计。即使在 Spring Boot 3.x 中引入了新的 .imports 机制,Spring Framework 仍然保留了 spring.factories 的支持

//JDK源码,AutoConfigurationImportSelector类privatevoidfireAutoConfigurationImportEvents(List<String> configurations,Set<String> exclusions){List<AutoConfigurationImportListener> listeners =getAutoConfigurationImportListeners();if(!listeners.isEmpty()){AutoConfigurationImportEvent event =newAutoConfigurationImportEvent(this, configurations, exclusions);for(AutoConfigurationImportListener listener : listeners){invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event);}}}protectedList<AutoConfigurationImportListener>getAutoConfigurationImportListeners(){returnSpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,this.beanClassLoader);}publicstatic<T>List<T>loadFactories(Class<T> factoryType,@NullableClassLoader classLoader){returnforDefaultResourceLocation(classLoader).load(factoryType);}publicstaticSpringFactoriesLoaderforDefaultResourceLocation(@NullableClassLoader classLoader){returnforResourceLocation(FACTORIES_RESOURCE_LOCATION, classLoader);}publicstaticfinalStringFACTORIES_RESOURCE_LOCATION="META-INF/spring.factories";

遵循 Spring Boot 的"约定优于配置"理念,第三方依赖库需要将其自动配置类定义在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中,这样 Spring Boot 启动时就能自动发现并加载这些配置

在这里插入图片描述

getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes):在Spring Boot 3.5.8(本文所用的版本)中,该方法不仅加载 Spring Boot 默认的自动配置类,还会加载所有第三方库提供的自动配置类

//JDK源码,AutoConfigurationImportSelector类protectedList<String>getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes){ImportCandidates importCandidates =ImportCandidates.load(this.autoConfigurationAnnotation,getBeanClassLoader());//扫描类路径下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件List<String> configurations = importCandidates.getCandidates();//从 ImportCandidates 对象中获取所有找到的自动配置类名,这些类名是从 .imports 文件中读取的Assert.state(!CollectionUtils.isEmpty(configurations),"No auto configuration classes found in "+"META-INF/spring/"+this.autoConfigurationAnnotation.getName()+".imports. If you "+"are using a custom packaging, make sure that file is correct.");return configurations;}

4.Gitee

Gitee地址:九转苍翎
本文源码:spring-principle

Read more

Heygem数字人系统部署:Linux环境依赖安装详细步骤

Heygem数字人系统部署:Linux环境依赖安装详细步骤 1. 引言:为什么需要手动安装依赖? Heygem数字人视频生成系统是一个功能强大的AI工具,它能将音频和视频结合,生成口型同步的数字人视频。虽然系统提供了便捷的Web界面,但在实际部署时,尤其是在Linux服务器上,我们常常会遇到各种环境依赖问题。 你可能已经尝试过直接运行系统,却遇到了诸如“缺少某个库”、“Python包版本冲突”或“CUDA驱动不匹配”之类的报错。这些问题往往让人头疼,特别是当你需要在一个干净的系统上从头开始部署时。 这篇文章就是为你准备的。我将手把手带你完成Heygem数字人系统在Linux环境下的所有依赖安装步骤。无论你是要在公司的服务器上部署,还是在云服务器上搭建自己的数字人生成环境,跟着这篇指南走,都能避开那些常见的“坑”,顺利把系统跑起来。 2. 部署前的准备工作 在开始安装之前,我们需要做好充分的准备。这就像盖房子前要打好地基一样,准备工作做得好,后续的安装过程就会顺利很多。 2.1 系统环境检查 首先,我们需要确认你的Linux系统是否符合基本要求。打开终端,执行以下命令

By Ne0inhk

DeepSeek-R1-Distill-Llama-8B实战教程:Ollama Web UI定制化提示模板配置方法

DeepSeek-R1-Distill-Llama-8B实战教程:Ollama Web UI定制化提示模板配置方法 你是不是也遇到过这样的问题:用DeepSeek-R1-Distill-Llama-8B模型时,每次都要重复输入相似的提示词,或者想让模型按照特定格式输出结果,但总是达不到理想效果? 今天我就来分享一个实用技巧——通过Ollama Web UI配置定制化提示模板,让你一键调用预设好的提示词,大幅提升使用效率和输出质量。无论你是开发者、研究人员,还是普通用户,这个方法都能让你的AI助手变得更聪明、更听话。 1. 为什么需要定制化提示模板? 在开始具体操作之前,我们先聊聊为什么要做这件事。DeepSeek-R1-Distill-Llama-8B是一个推理能力很强的模型,但要让它的能力充分发挥,提示词的编写质量至关重要。 1.1 常见的使用痛点 我总结了几个大家经常遇到的问题: * 重复劳动:每次都要重新输入相似的提示词结构 * 格式混乱:模型输出格式不统一,需要手动整理 * 效果不稳定:同样的任务,不同时间输入效果差异大 * 新手门槛高:不知道怎么写提示

By Ne0inhk
AIGC带来数据革命:R语言如何成为数据科学家的秘密武器?

AIGC带来数据革命:R语言如何成为数据科学家的秘密武器?

文章目录 * 一、R语言的基础特性 * 1.1 R语言的起源与发展 * 1.2 R语言的核心优势 * 二、R语言在AIGC中的应用场景 * 2.1 数据预处理与清洗 * 2.2 文本分析与生成 * 2.3 机器学习与模型构建 * 2.4 数据可视化与报告生成 * 三、R语言在AIGC中的具体案例 * 3.1 金融数据分析与预测 * 3.2 医疗数据分析与建模 * 3.3 社交媒体数据分析与情感分析 * 四、R语言在AIGC中的未来展望 * 4.1 与深度学习框架的集成 * 4.2 与云计算平台的集成 * 4.3 与自动化工具的集成 * 《R语言统计分析与可视化从入门到精通宣传文案》 * 亮点 * 内容简介 * 作者简介 * 目录

By Ne0inhk

llama.cpp性能优化全景指南:从诊断到部署的系统优化方法论

llama.cpp性能优化全景指南:从诊断到部署的系统优化方法论 【免费下载链接】llama.cppPort of Facebook's LLaMA model in C/C++ 项目地址: https://gitcode.com/GitHub_Trending/ll/llama.cpp 问题诊断:定位llama.cpp启动性能瓶颈 本部分将帮助你:1.识别性能瓶颈 2.制定优化优先级 3.建立性能基准线 在优化llama.cpp性能之前,我们首先需要系统性地诊断启动过程中的关键瓶颈。启动缓慢通常表现为以下症状: * 模型加载时间超过30秒 * 首次推理延迟超过5秒 * 内存占用过高导致系统卡顿 * CPU/GPU资源利用率异常 性能瓶颈诊断工具 llama.cpp提供了多种内置工具帮助定位性能问题: 1. 基准测试工具: ./llama-bench -m

By Ne0inhk