当前位置: 首页 > backend >正文

Spring——关于Bean以及自动配置

今天小编来分享下,关于Spring中的Bean的作用域、生命周期、以及Spring的是如何自动配置的。

Bean的作用域

Spring Bean的作用域定义了Bean的生命周期和在Spring容器中的可见范围。

值得注意的是,Bean的生命周期是固有的

这个作用域觉得Bean在哪个地方生效。

即:

  • 何时创建Bean(如每次请求都新建、还是全局共享一个)

  • Bean的存活范围(如仅在当前HTTP请求有效,还是全局单例)

  • 不同作用域之间的隔离性(如一个用户的请求不会影响另一个用户的Bean)

Bean的作用域有以下6种

作用域

描述

适用场景

示例注解

singleton(默认)

整个容器共享一个 Bean 实例,所有依赖注入的都是同一个对象

无状态的全局服务(如工具类、配置类)

@Service、@Responsitory

prototype(原型)

每次请求都创建一个新 Bean 实例,依赖注入时也会生成新对象

需要保持独立状态的Bean(购物车)

@Scope("prototype")

request(web)

每个 HTTP 请求创建一个新 Bean,请求结束后销毁

存储请求相关的数据(如用户表单提交)

@RequestScope

session(Web)

每个用户会话(Session)创建一个 Bean,会话过期后销毁

存储用户登录状态、购物车

@SeesionScope

application(Web)

整个 Web 应用共享一个 Bean(类似 singleton,但范围是 ServletContext)

全局缓存、共享资源

@ApplicationScope

websocket(Web)

每个 WebSocket 会话一个 Bean,连接关闭后销毁

实时通信(如聊天室)

@Scope("websocket")

那么前五种小编就通过简单代码演示、最后一个理解概念即可

代码演示

1.创建一个spring boot项目

引入spring web、lombok依赖即可

2.在Application启动类所在目录,创建config、controller、model、component目录

3.在config目录,创建一个DogConfig类(名字可以随心起)

代码如下:

Java
@Configuration
public class DogConfig {

//下面演示的是Bean的作用域
//单例作用域,spring默认
@Bean
//标记为singleton的方式有两种
//    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope("singleton")
public Dog singletonDog(){
return new Dog();
}
//多例作用域,即原型
@Bean
//同样可以在scope里写prototype
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototypeDog(){
return new Dog();
}
//请求作用域
@Bean
@RequestScope
public Dog requestDog(){
return new Dog();
}
//会话作用域
@Bean
@SessionScope
public Dog sessionDog(){
return new Dog();
}
//全局作用域
@Bean
@ApplicationScope
public Dog applicationDog(){
return new Dog();
}
}

4.在controller目录创建一个 DogController类(名字也可随心起)

TypeScript
@RestController
@RequestMapping("/scope")
public class DogController {
@Resource(name ="singletonDog")
private Dog singletonDog;

@Resource(name ="prototypeDog")
private Dog prototypeDog;

@Resource(name ="requestDog")
private Dog requestDog;

@Resource(name ="sessionDog")
private Dog sessionDog;

@Resource(name ="singletonDog")
private Dog applicationDog;

@Autowired
private ApplicationContext context;

@RequestMapping("/singletonDog")
public String singleton(){
Dog dog=(Dog) context.getBean("singletonDog");
return "注入对象:"+singletonDog.toString()+" "+"上下文对象:"+dog.toString();
}
@RequestMapping("/prototypeDog")
public String prototypeDog(){
Dog dog=(Dog) context.getBean("prototypeDog");
return "注入对象:"+prototypeDog.toString()+" "+"上下文对象:"+dog.toString();
}

@RequestMapping("/requestDog")
public String requestDog(){
Dog dog=(Dog) context.getBean("requestDog");
return "注入对象:"+requestDog.toString()+" "+"上下文对象:"+dog.toString();
}

@RequestMapping("/sessionDog")
public String sessionDog(){
Dog dog=(Dog) context.getBean("sessionDog");
return "注入对象:"+sessionDog.toString()+" "+"上下文对象:"+dog.toString();
}

@RequestMapping("/applicationDog")
public String applicationDog(){
Dog dog=(Dog) context.getBean("applicationDog");
return "注入对象:"+applicationDog.toString()+" "+"上下文对象:"+dog.toString();
}
}

5.启动项目

1.测试singleton域

访问地址:http://127.0.0.1:8080/scope/singletonDog

无论刷新几次,对象都是一致的,符合所有对象共享一个实例的描述

2.测试prototype域

访问地址:http://127.0.0.1:8080/scope/prototypeDog

第一次刷新:

第二次刷新:

此时发现,获取上下文对象的时候,每一次刷新,获得的对象是不一样的。

这里的注入对象是不变,是因为访问URL前,已经提前注入,固定好了

3.测试request作用域

访问URL:http://127.0.0.1:8080/scope/requestDog

第一次刷新:

第二次刷新:

显然可以看到,注入对象和获取上下文对象,每一次请求是不同的,但当次请求中,获取的对象是一样的。

这是因为,在DogConfig类中,加了@RequestScope注解后

这个注解是默认开启代理的

Spring会自动加上Scoped Proxy(作用域代理)

注入字段requestDog是一个代理对象

每次访问requestDog,代理会从当前的请求中获取实际的Bean实例

那么什么又是作用域代理呢?

什么是代理?

在 Spring 中,代理(Proxy) 是一种设计模式,用来控制对对象的访问。

Spring 会为某些作用域的 Bean(如 request, session, prototype)生成一个 代理对象,而不是直接注入真实对象。

Scoped Proxy 的作用:

  • 当你注入一个 @RequestScope 的 Bean 时,Spring 并不会直接注入当前请求的 Bean

  • 而是注入一个 代理对象

  • 这个代理对象会在运行时,动态地从当前请求中获取真正的 Bean

同时,context获取的Bean也是从当前请求中创建的Dog实例

所以,两个对象获取到的示例是一致的

4.测试session作用域

访问URL通过Google:http://127.0.0.1:8080/scope/sessionDog

无论多次刷新,获取到的对象是不变的

访问URL通过Edge浏览器:http://127.0.0.1:8080/scope/sessionDog

同样,多次刷新,获取的对象是不变的

这是因为,对于同一个浏览器而言,spring视为同一个客户端,一个客户端,一个session。

5.测试application作用域

访问URL通过Google:http://127.0.0.1:8080/scope/applicationDog

多次刷新,结果是不变的。

访问URL通过Edge浏览器:http://127.0.0.1:8080/scope/sessionDog

同样,多次刷新结果也是不变的。

显然,整个web容器这里共用了这个对象示例,所以无论哪个客户端,获取的时候,都是同一个对象。

WebSocket

对于WebSocket,这里简单的通俗说下:

首先,正经说法:

在 Spring 中,WebSocket 作用域(websocket scope) 是一种专门用于 WebSocket 会话(Session)的自定义作用域,它允许你定义一个 Bean 的生命周期绑定到某个 WebSocket 会话(Session)。

不正经说法:

WebSocket 作用域就像是你在这个聊天窗口里用的一个“小本本”。

  • 你们聊天开始时,系统给你一个新本子(创建一个 Bean)

  • 聊天过程中,你一直用这个本子记东西(Bean 一直存在)

  • 聊完天关掉窗口,这个本子就被扔了(Bean 被销毁)

那么对于这个Bean的作用域,就分享到这里。

接下来小编分享下,关于Bean的生命周期。

Bean的生命周期:

对于一个人而言,他/她是有一个完整的生命周期,简而言之,就是从出生到死亡。

那么对于Bean而言,它也是有一个Bean的生命周期的,它的生命周期如下:

1.实例化

  • 这是BeBean的生命周期第一步,Spring容器通过构造函数或者工厂方法创建Bean的实例

  • 此时对象以及被创建,但是属性没有被赋值,相当于Java中的new操作

2.设置属性

  • Spring容器通过依赖注入(DI)为Bean设置属性值

  • 包括执行@Autowired注解的自动装配

3.初始化

各种通知

  如若Bean实现了各种Aware接口(比如BeanNameAware、ApplicationContextAware),

  Spring会调用相应的方法

  @PostConstruct

     执行带有@PostConstruct注解的初始化方法

  InitializingBean接口

  如若Bean实现了InitializingBean接口,会调用afterPropertiesSet()方法

  init-method

  最后执行在Bean定义中指定的自定义初始化方法(通过init-method属性指定)

如果Bean实现了

4.使用Bean

  • Bean已经完全初始化,可以被应用程序正常使用

  • 这个阶段Bean处于就绪状态、处理业务逻辑

5.销毁Bean

当容器关闭时,Bean就进入销毁阶段、执行顺序如下

1.PreDestroy

  执行带有@PreDestroy注解的方法

  2.DisposableBean接口:

  如果Bean实现了DisposableBean接口,会调用destroy()方法

  3.destroy-method

  最后执行在Bean定义中指定的自定义销毁方法,通过destroy-method属性指定。

  比如:

@Configuration public class AppConfig { @Bean(destroyMethod = "closeResources") public ResourceHolder resourceHolder() { return new ResourceHolder(); } }

执行流程图如下:

代码演示:

在component包下创建BeanLifeComponent类

Java
@Componen
public class BeanLifeComponent implements BeanNameAware {

private Dog dog;

//以下为模拟Bean的生命周期

public BeanLifeComponent(Dog dog) {
System.out.println("执行构造函数:");
}

@Autowired
public void setDog(Dog dog) {
this.dog = dog;
System.out.println("构造函数执行完毕,完成属性装配:");
}

@PostConstruct
public void init(){
System.out.println("执行初始化方法:");
}

@Override
public void setBeanName(String name) {
System.out.println("执行setBeanName:"+name);
}

public void use(){
System.out.println("执行使用Use方法:");
}
@PreDestroy
public void destroy(){
System.out.println("执行destroy方法");
}
}

在application启动类中,编写如下代码:

Java
@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
BeanLifeComponent bean = context.getBean(BeanLifeComponent.class);
bean.use();
}
}        

启动程序,观察

关闭程序

那么关于Bean的生命周期就分享到这里。

接下来分享下,Spring的自动配置。

那么这里有一个值得注意的是,会自动装配有点混淆。

自动装配

Spring 的自动装配是依赖注入(Dependency Injection, DI)的核心特性之一,它允许 Spring 容器自动识别和满足 bean 之间的依赖关系,而无需显式配置。

自动装配的本质:

自动装配是指 Spring 容器可以:

  1. 自动发现 bean 之间的依赖关系

  2. 自动注入所需的依赖对象

  3. 减少 XML 或 Java 配置中的显式 <property>@Bean 定义

目前,现代Spring自动装配方式,主要有三种方式@Autowired、@Resource、@Inject注解

回归自动配置这里。

Spring的自动配置就是当Spring容器启动后,一些配置类,Bean对象等就自动存入IOC容器中,不需要我们手动声明,从而简化了开发,省去了许多繁琐的配置,还一定程度上降低了使用门槛

简而言之,就是SpringBoot是如何把依赖jar包中的配置类以及Bean加载到SpringIOC容器中.

下面进行演示

需求:使用Spring管理第三方的jar包的配置

在上面创建好的项目下,引入第三方的包,这里为简单演示,所以我们在该项目的其他目录下创建不同的目录模拟引入第三方的包

1.模拟第三方代码文件在com.nanxi.autoconfig下

在这个目录下创建一个autoconfig类

TypeScript
@Component
public class AutoConfig {
public void use(){
System.out.println("autoConfig use……");
}
}

application启动类中写以下代码

TypeScript
@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
AutoConfig bean = context.getBean(AutoConfig.class);
bean.use();
}
}        

运行报错:

原因:

Spring通过五大注解和@Bean注解,帮助我们将Bean加载到IOC容器中,那么有个前提就是这些被添加这些注解的类需要与SpringBoot的启动类放在统一目录下。

那么此时,我们模拟引入的第三包jar包显然不在这个目录下?

那么有什么方式可以引入呢?

引入第三方包

方式一:

通过这个一个扫描注解@ComponentScan("com.nanxi.autoconfig")

Java
//也可以填入数组
//@ComponentScan({"com.nanxi.autoconfig","com.nanxi.autoconfig2"})
@ComponentScan("com.nanxi.autoconfig")
@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
AutoConfig bean = context.getBean(AutoConfig.class);
bean.use();
}
}        

运行程序结果如下:

可以正确获取Bean,但是,Spring中,会是使用这个方式吗?显然不是。原因是,要是有几十个依赖,也是这样写?显的可读性不好,繁琐!

那么还有另一种方式

方式二:

通过一个@Import注解

TypeScript
//也可以填入数组
//@Import({AutoConfig.class, AutoConfig2.class})
@Import(AutoConfig.class)
@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
AutoConfig bean = context.getBean(AutoConfig.class);
bean.use();
}
}        

运行程序,结果:

显然也是可以获取结果,那么Spring中也是用如此方式?显然也不是。原因同方式一一样

还有方式三,通过自定义注解引入,这个是第三方创建好,帮我们管理这些引入的Bean

方式三:

创建一个自定义注解

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImport.class)
public @interface EnableAutoConfig {

}

Java
@EnableAutoConfig
@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
AutoConfig bean = context.getBean(AutoConfig.class);
bean.use();
}
}        

运行项目如下:

显然Spring也不是通过这个方法引入Bean,那么这里还提供一个方法

方式四:

创建一个以下这个类

TypeScript
public class MyImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.nanxi.autoconfig.AutoConfig"};
}
}

Java
@Import(MyImport.class)
@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
AutoConfig bean = context.getBean(AutoConfig.class);
bean.use();
}
}        

运行结果如下:

显然这个方式也是不妥,那么这里继续提供另一个方法

方法五:

创建以下类

TypeScript
public class MyRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition=new GenericBeanDefinition();
beanDefinition.setBeanClass(AutoConfig.class);
registry.registerBeanDefinition("autoConfig",beanDefinition);
}
}

Java
@Import(MyRegister.class)
@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
AutoConfig bean = context.getBean(AutoConfig.class);
bean.use();
}
}        

运行结果如下:

显然这个方式也不适合,那么这里提供最终的方式。

这个方式就是Spring所使用的。

方式六:

在resources目录下创建以下目录和文件:

文件内容添加:

Java
com.nanxi.autoconfig.AutoConfig
com.nanxi.autoconfig.AutoConfig2

Java

@SpringBootApplication
public class SpringPrincipleApplication {

public static void main(String[] args) {
ApplicationContext context =SpringApplication.run(SpringPrincipleApplication.class, args);
AutoConfig bean = context.getBean(AutoConfig.class);
bean.use();
}
}        

运行结果如下:

那么到这里,对于这个Spring是用哪种方式的,讲到这里。

小编来简单分享下底层原理

底层原理()

自动配置的简单启动流程

自动配置的流程由@SpringBootApplication注解启动,该注解聚合了@EnableAutoConfiguration,后者是实现自动配置的核心。

@SpringBootApplication

@EnableAutoConfiguration

@Import(AutoConfigurationImportSelector.class)

loadFactoryNames 加载 spring.factories / AutoConfiguration.imports

筛选出所有可用的自动配置类

结合 @Conditional 条件注解判断是否加载

向 Spring 容器注册配置类中的 Bean

核心注解简单讲解

  1. @SpringBootApplication

这是一个复合注解,组合了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan,用于启动自动配置流程。

  1. @EnableAutoConfiguration

启用自动配置的注解,使用 @Import 导入 AutoConfigurationImportSelector 类。

  1. AutoConfigurationImportSelector

该类实现了 DeferredImportSelector 接口,在 Spring 容器启动时被调用,用于加载自动配置类。其核心方法如下:

JSON
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
AutoConfigurationEntry entry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(entry.getConfigurations());
}

这个方法最终会调用 SpringFactoriesLoader 来读取配置文件中的自动配置类列表。

  1. SpringFactoriesLoader

负责从 classpath 中读取 META-INF/spring.factories 或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,加载所有配置类。

spring.factories 与 AutoConfiguration.imports

在 Spring Boot 2.x 中,自动配置类声明在 META-INF/spring.factories 文件中,例如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

Spring Boot 3.x 中,改用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

自动配置类结构与条件注解

一个典型的自动配置类通常如下:

Java
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

@Bean
public DataSource dataSource(DataSourceProperties properties) {
return new HikariDataSource(properties);
}
}

Spring Boot 使用一组 @Conditional 注解控制配置类是否生效:

常用条件注解如下:

  • @ConditionalOnClass:某类存在时生效

  • @ConditionalOnMissingBean:容器中没有某个 Bean 时生效

  • @ConditionalOnProperty:配置文件中某属性存在且满足条件时生效

  • @ConditionalOnBean:容器中存在某个 Bean 时生效

  • @Conditional:自定义条件,灵活控制配置生效

自定义自动配置类示例

  1. 编写配置类并加上 @Configuration 注解

  2. 加入条件注解控制是否生效

  3. 在 META-INF/spring.factories 中注册配置类

TypeScript
@Configuration
@ConditionalOnProperty(name = "my.feature.enabled", havingValue = "true")
public class MyFeatureAutoConfiguration {

@Bean
public MyService myService() {
return new MyServiceImpl();
}
}

spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.autoconfig.MyFeatureAutoConfiguration

总结

Spring Boot 的自动配置机制建立在:

  • • @EnableAutoConfiguration 启用配置

  • • AutoConfigurationImportSelector 动态加载配置类

  • • SpringFactoriesLoader 基于 SPI 加载资源文件

  • • @Conditional 条件注解控制配置是否生效

通过这些机制,Spring Boot 实现了强大且灵活的自动化配置系统,使开发者能够专注于业务逻辑开发而非繁琐配置。

http://www.xdnf.cn/news/17975.html

相关文章:

  • FTP上传文件错误
  • BM25算法和传统的TF-IDF算法的区别
  • IEEEtaes.cls解析
  • Trae中`settings.json`文件的Java配置项功能详解(二)
  • 343整数拆分
  • 双椒派E2000D开发板LED驱动开发实战指南
  • 随机整数列表处理:偶数索引降序排序
  • 杂记 03
  • 软件需求工程详解
  • 【自用】JavaSE--特殊文件Properties与XML、日志技术
  • 项目管理进阶——解读大型IT系统集成项目实施要点培训【附全文阅读】
  • 主从复制+哨兵
  • GPFS集群性能压测
  • MySQL的下载安装(MSI和ZIP版本都有)
  • Linux上配置环境变量
  • UDP/TCP套接字编程简单实战指南
  • 【总结型】c语言中的位运算
  • Hugging Face 与 NLP
  • Express开发快速学习
  • Spring Cloud系列—Alibaba Seata分布式事务
  • B站 韩顺平 笔记 (Day 20)
  • 创建maven module中的override
  • MySQL的《Buffer-pool》和《连接池》介绍
  • windows扩展(外接)显示器位置调节
  • CVE-2021-4300漏洞复现
  • 树的直径(树形DP)
  • 云计算-Kubernetes+Istio 实现金丝雀发布:流量管理、熔断、流量镜像、ingreess、污点及pv案例实战
  • 新手向:Python异常处理(try-except-finally)详解
  • LangChain4j:基于 SSE 与 Flux 的 AI 流式对话实现方案
  • Apereo CAS靶场渗透练习