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

Spring原理

Bean的作用域

概念

在Spring IoC & DI阶段,我们学习了Spring是如何帮助我们管理对象的。

1、通过@Controller,@Service,@Reposiytory,@Compnent,@Configuration,@Bean来声明Bean对象

2、通过ApplicationContext 或者 BeanFactory来获取对象

3、通过@Autowired,Setter方法或者构造方法等来应用程序注入所依赖的Bean对象

简单回顾

通过@Bean声明bean,把bean存在Spring容器中。

public class Doge {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
@Configuration
public class DogConfig {@Beanpublic Doge getDoge1(){Doge doge = new Doge();doge.setName("zhangsan");return doge;}
}

2、从Spring中获取Bean

@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {//获取Spring上下⽂对象ApplicationContext context = 
SpringApplication.run(SpringIocApplication.class, args);//从Spring上下⽂中获取对象Dog dog = context.getBean(Dog.class);System.out.println(dog);}
}

也可以通过在代码中注入ApplicationContext的方式来获取到Spring容器,进而获取Bean。

@SpringBootTest
class DemoApplicationTests {@Autowiredprivate ApplicationContext applicationContext; //Spring 容器@Testvoid contextLoads() {DogBean dog1 = applicationContext.getBean(DogBean.class);System.out.println(dog1);}
}

修改代码,比较这两种方式获取Bean:

@SpringBootTest
class DemoApplicationTests {@Autowiredprivate ApplicationContext applicationContext; //Spring 容器@Testvoid contextLoads() {Dog dog1 = applicationContext.getBean(Dog.class);System.out.println(dog1);Dog dog2 = applicationContext.getBean(Dog.class);System.out.println(dog2);}
}

执行结果:

通过执行结果可以发现输出的bean对象的引用时一样的,这就说明每次从Spring容器中取出的对象都是同一个(单例模式)。

默认情况下,Spring容器中的bean都是单例的,这种行为模式,我们就称之为Bean的作用域。

Bean的作用域指的时Bean在Spring框架中的某种行为模式。

当作用域为单例时,整个Spring中是只有这一个Bean的,它是全局共享的。那么当其他人修改了这个值之后,那么另一个人读取到的就是被修改的值。

修改上述代码,给UserController添加属性name。

修改测试代码:

@SpringBootTest
class DemoApplicationTests {@Autowiredprivate ApplicationContext applicationContext; //Spring 容器@Testvoid contextLoads() {Dog dog1 = applicationContext.getBean(Dog.class);dog1.setName("狗狗1");System.out.println(dog1);System.out.println(dog1.getName());Dog dog2 = applicationContext.getBean(Dog.class);System.out.println(dog2);System.out.println(dog2.getName());}
}

观察结果:

因为这里的dog1和dog2实际上为同一个对象,dog2拿到了dog1设置的值。

那么能不能将bean对象设置为非单例的(每次获取到的bean都是一个新对象呢?)

这就是Bean的不同作用域了。

Bean的作用域

在Spring中支持6种作用域,后4种在SpringMVC环境下才生效。

  1. singleton:单例作用域
  2. prototype:原型作用域(多例作用域)
  3. request:请求作用域
  4. session:会话作用域
  5. Application:全局作用域
  6. websocket:HTTP WebSocket作用域

参考文档:Bean 作用域 :: Spring 框架

代码实现:

@Component
public class DogBeanConfig {@Beanpublic Dog dog(){Dog dog = new Dog();dog.setName("旺旺");return dog;}//单例作用域@Bean@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)public Dog singleDog(){Dog dog = new Dog();return dog;}//原型作用域@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public Dog prototypeDog(){Dog dog = new Dog();return dog;}//请求作用域@Bean@RequestScopepublic Dog requestDog() {Dog dog = new Dog();return dog;
}//会话作用域@Bean@SessionScopepublic Dog sessionDog() {Dog dog = new Dog();return dog;}//全局作用域@Bean@ApplicationScopepublic Dog applicationDog() {Dog dog = new Dog();return dog;}
}

测试不同作用域获取到的Bean是否一致(@Autowired注入和从Applicationcontext获取)。

@RestController
public class DogController {//Autowire注入@Autowiredprivate Dog singleDog;@Autowiredprivate Dog prototypeDog;@Autowiredprivate Dog requestDog;@Autowiredprivate Dog sessionDog;@Autowiredprivate ApplicationContext applicationContext;//测试单例作用域@RequestMapping("/single")public String single(){Dog contextDog = (Dog)applicationContext.getBean("singleDog");return "dog:"+singleDog.toString()+",contextDog:"+contextDog;}//测试原型作用域@RequestMapping("/prototype")public String prototype(){Dog contextDog = (Dog)applicationContext.getBean("prototypeDog");return "dog:"+prototypeDog.toString()+",contextDog:"+contextDog;}//测试请求作用域@RequestMapping("/request")public String request(){Dog contextDog = (Dog)applicationContext.getBean("requestDog");return
"dog:"+requestDog.toString()+",contextDog:"+contextDog.toString();}//测试会话作用域@RequestMapping("/session")public String session(){Dog contextDog = (Dog)applicationContext.getBean("sessionDog");return
"dog:"+sessionDog.toString()+",contextDog:"+contextDog.toString();}//测试全局作用域@RequestMapping("/application")public String application(){Dog contextDog = (Dog)applicationContext.getBean("applicationDog");return
"dog:"+applicationDog.toString()+",contextDog:"+contextDog.toString();}
}

观察Bean的作用域

单例作用域

多次访问,@Autowired和ApplicationContext得到的都是同一个对象。

原型作用域

通过Autowired注入和ApplicationContext获取的对象不同,且ApplicationContext获得的对象每次访问都是不一样的(注入的对象因为在Spring容器启动时,就已经注入了,所以多次访问获得的对象不会发生变化)。

请求作用域

在一次请求中,@Autowired和ApplicatonContext获取的对象是一样的,但是每次请求(每次访问)获取的对象都是不一样的。

会话作用域

在一个session种,多次请求,获取的对象都是同一个:

换一个浏览器继续访问(session不同):

全局作用域

在一个应用中,多次访问得到的都是同一个对象。

全局作用域对于整个web容器来说,bean的作用域是ServerletContext级别的。和singleton类似,区别在于:Appliation scope 是ServerletContext的单例,singleton是一个ApplicationContext的单例。在一个web容器中ApplicationContext可以有多个。

Bean的生命周期

生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程叫做一个对象的生命周期。

Bean的生命周期分为以下5个部分:

1、实例化(为Bean分配内存空间)

2、属性注入(Bean注入和装配,如@Autowired)

3、初始化

a、执行各种通知,如:BeanNameAware,BeanFactoryAware,ApplicationContextAware的接口方法。

b、执行初始化方法

  • xml定义的init-method
  • 使用注解的方式@PostConstruct
  • 执行初始化后置方法(BeanPostProcessor)

4、使用Bean

5、销毁Bean

  • 销毁容器的各种方法,如@PreDestory,DisposableBean接口方法,destory-method

实例化和属性赋值对应构造方法和setter方法的注入。初始化和销毁时用户能自定义扩展的两个阶段。可以在实例化之后,类加载完成之前进行自定义“事件”处理。

比如现在我们需要买一套房子,流程如下:

1、先买房(实例化,从无到有)

2、装修(设置属性)

3、买家电,如:洗衣机、空调、电视等(初始化,可以入住)

4、入住(使用Bean)

5、买房(Bean销毁)

执行流程:

代码演示
@Component
public class BeanLifeComponent implements BeanNameAware {private UserComponent userComponent;public BeanLifeComponent() {System.out.println("执⾏构造函数");}@Autowiredpublic void setUserComponent(UserComponent userComponent) {System.out.println("设置属性userComponent");this.userComponent = userComponent;}@Overridepublic void setBeanName(String s) {System.out.println("执⾏了 setBeanName ⽅法:" + s);}/*** 初始化*/@PostConstructpublic void postConstruct() {System.out.println("执⾏ PostConstruct()");}public void use() {System.out.println("执⾏了use⽅法");}/*** 销毁前执⾏⽅法*/@PreDestroypublic void preDestroy() {System.out.println("执⾏:preDestroy()");}
}

执行结果:

源码分析

可参考其他博主的博文:

Spring深度学习:Bean生命周期及源码解析_bean的生命周期源码解读-CSDN博客

Spring Boot自动装配

SpringBoot的自动装配是当Spring容器启动后,一些配置类,bean对象等就自动存入到了IoC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

SpringBoot自动配置,就是指SpringBoot是如何将依赖jar包中的配置以及Bean加载到SpringIoC容器中的。

问题描述

需求:我们在编写程序的过程中常常需要使用Spring管理第三方的jar包的配置。

数据准备:

1、创建新的目录org.test.xmy

2、创建模拟第三方文件(文件在新建目录下):

第三方文件代码:


@Component
public class XmyConfig {public void use(){System.out.println("xmy 使用");}
}

3、获取XmyConfig这个Bean。

写测试代码:

@SpringBootTest
class SpringAutoconfigApplicationTests {@Autowiredprivate ApplicationContext applicationContext;@Testvoid contextLoads() {XmyConfig XmyConfig = applicationContext.getBean(XmyConfig.class, 
"XmyConfig");System.out.println(XmyConfig);}
}

4、运行程序:

观察日志:No qualify bean of type‘org.test.xmy.XmyConfig’available

没有org.test.xmy.XmyConfig这个类型的Bean。

原因分析

Spring通过五大注解和@Bean注解可以帮助我们把Bean加载到SpringIoC容器中,但有个前提条件:这些注解类需要和SpringBoot启动类在同一个目录下。(XmyConfig所在目录为org.test.xmy,而启动类所在目录在com.example.springprincle,因此SpringBoot没有扫描到XmyConfig)。

解决方案

我们需要指定路径或者引入文件,告诉Spring,让Spring进行扫描到。

常见解决方案:

  1. @ComponentScan组件扫描
  2. @Import导入(使用@Import导入的类会被Spring加载到IoC容器中)

代码如下:

@ComponentScan:
@SpringBootApplication
@ComponentScan("org.test.xmy")
public class SpringPrincleApplication {public static void main(String[] args) {SpringApplication.run(SpringPrincleApplication.class, args);}}

@ComponentScan里的参数可以是数组,可以指定扫描多个包。

运行程序:

可以看到,这次XmyConfig这个Bean被Spring获取到了。

@Import

@Import导入主要有以下几种形式:

  1. 导入类
  2. 导入ImportSelector接口实现类
导入类
@SpringBootApplication
@Import(XmyConfig.class)
public class SpringPrincleApplication {public static void main(String[] args) {SpringApplication.run(SpringPrincleApplication.class, args);}}

运行程序:

问题:如果我多了一个配置项XmyConfig2呢?

@Component
public class XmyConfig2 {public void use(){System.out.println("xmy2 使用");}
}

我们可以采用导入多个类:


@SpringBootApplication
@Import({XmyConfig.class,XmyConfig2.class})
public class SpringPrincleApplication {public static void main(String[] args) {SpringApplication.run(SpringPrincleApplication.class, args);}}

测试结果:

虽然这种方式能够实现,但却太过繁琐了。

导入ImportSelector接口实现类

ImportSelector实现类:

public class MyImportSelect implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {//要导入的类的全限定类名return new String[]{"org.test.xmy.XmyConfig","org.test.xmy.XmyConfig2"};}
}

启动类:

@SpringBootApplication
@Import(MyImportSelect.class)
public class SpringPrincleApplication {public static void main(String[] args) {SpringApplication.run(SpringPrincleApplication.class, args);}}

运行程序:

可以看到,我们采用这种方式也可以导入第三方依赖提供的Bean。

但他们都有一个问题,就是使用者需要指导第三方依赖中有哪些Bean对象或者配置类,如果漏掉其中一些Bean,很可能导致我们的项目出现大的事故。

那么如何解决呢?

比较常见的解决方案就是第三方依赖给我们提供一个注解,这个注解一般都以@Enablexxxx开头的注解,注解中封装的就是@Import注解。

第三方依赖提供注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelect.class)
public @interface EnableAutoXmyConfig {
}

启动类引入第三方提供的注解:

@SpringBootApplication
@EnableAutoXmyConfig
public class SpringPrincleApplication {public static void main(String[] args) {SpringApplication.run(SpringPrincleApplication.class, args);}}

运行程序:

可以看到,这种方式也可以导入第三方依赖提供的Bean。并且这种方式更加优雅一些,SpringBpot采用的也是这种方式。

SpringBoot自动装配源码分析

参考博文:【Spring Boot】自动配置源码解析_springboot自动配置源码解析-CSDN博客

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

相关文章:

  • 最近 | 黄淮教务 | 小工具合集
  • 世界模型一种能够对现实世界环境进行仿真,并基于文本、图像、视频和运动等输入数据来生成视频、预测未来状态的生成式 AI 模型
  • Maxscript如何清理3dMax场景?
  • 打工人日报20250822
  • More Effective C++ 条款01:仔细区别 pointers 和 references
  • Java设计模式-外观模式
  • 滑动窗口+子串+普通数组算法
  • Elasticsearch搜索原理
  • HEVC(H.265)与HVC1的关系及区别
  • Unreal Engine UProjectileMovementComponent
  • 异步开发的三种实现方式
  • Unreal Engine USceneComponent
  • Unreal Engine Simulate Physics
  • 线段树01
  • 20250822 组题总结
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘uvicorn’问题
  • 北京-测试-入职甲方金融-上班第三天
  • 嵌入式第三十五天(网络编程(UDP))
  • GPS欺骗式干扰的产生
  • DSPy框架:从提示工程到声明式编程的革命性转变
  • 声网SDK更新,多场景抗弱网稳定性大幅增强
  • GaussDB GaussDB 数据库架构师修炼(十八)SQL引擎(1)-SQL执行流程
  • week3-[二维数组]小方块
  • ArrayList线程不安全问题及解决方案详解
  • 硬件驱动---linux内核驱动 启动
  • 云原生俱乐部-k8s知识点归纳(7)
  • RCE的CTF题目环境和做题复现第4集
  • Unreal Engine UActorComponent
  • base64认识实际使用
  • #Datawhale 组队学习#8月-工作流自动化n8n入门-2