🔥个人主页: 中草药
🔥专栏:【Java】登神长阶 史诗般的Java成神之路
一、Bean的作用域
在 Java Spring 框架中,Bean 的作用域是一个关键概念,它决定了 Bean 的生命周期和实例化方式,对应用的性能、资源利用以及数据一致性等方面有着深远影响。本文将详细探讨 Spring Bean 的各种作用域。
举例 设置Bean的作用域
package com.bite.principle.config;
import com.bite.principle.model.Dog;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.annotation.ApplicationScope;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;
@Configuration
public class DogConfig {
@Bean
public Dog dog(){
Dog dog = new Dog();
dog.setName("旺旺");
return dog;
}
@Bean
// @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope("singleton")
public Dog singleDog(){
return new Dog();
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
// @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();
}
}
1、singleton 单例作用域
Singleton 是 Spring 默认的作用域。当一个 Bean 被定义为 Singleton 时,在整个 Spring 容器的生命周期内,只会创建一个该 Bean 的实例。
例如,在一个处理数据库操作的应用中,数据库连接池通常被设置为 Singleton 作用域。因为创建数据库连接是一个资源密集型操作,多个组件共享同一个连接池实例可以避免重复创建连接带来的开销,提高资源利用率。
Spring 实现 Singleton 作用域的原理是通过容器级别的缓存。容器在启动时创建 Bean 实例并将其存储在缓存中,后续对该 Bean 的请求直接从缓存中获取,从而保证始终返回同一个实例。
可用以下代码测试
package org.example.principle;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/scope")
@RestController
public class ScopeController {
@Resource(name = "singleDog")
private Dog singleDog;
@RequestMapping("/single")
public String single(){
//从context中获取对象
Dog dog = (Dog)context.getBean("singleDog");
return "注入对象:"+ singleDog.toString() + ",context Dog:"+ dog.toString();
}
}
测试结果:
即使进行多次刷新,显示的结果还是同一个,这就是 单例模式
2、prototype 原型作用域
与 Singleton 不同,Prototype 作用域的 Bean 在每次被使用时都会创建一个新的实例。
考虑一个用户订单处理服务,每个订单可能具有不同的处理逻辑和状态,将订单处理服务设置为 Prototype 作用域,就能确保每个订单处理过程都使用独立的服务实例,避免不同订单之间的状态干扰。
然而,由于每次请求都会创建新实例,开发人员需要谨慎使用 Prototype 作用域,避免因频繁创建复杂对象而导致性能问题。
可用以下代码测试:
package org.example.principle;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/scope")
@RestController
public class ScopeController {
@Resource(name = "prototypeDog")
private Dog prototypeDog;
@RequestMapping("/prototype")
public String prototype(){
//从context中获取对象
Dog dog = (Dog)context.getBean("prototypeDog");
return "注入对象:"+ prototypeDog.toString() + ",context Dog:"+ dog.toString();
}
}
测试结果:
如测试结果,注入的对象不发生变化,但是提取出来的Bean会变化,每一次的刷新即使用都会变化,创建新的实例
3、Request 请求作用域
Request 作用域适用于 Web 应用场景。在一次 HTTP 请求的生命周期内,同一个 Request 作用域的 Bean 是共享的,不同请求会创建不同的实例。
比如在一个基于 Spring MVC 的应用中,用于存储请求参数的 Bean 可以设置为 Request 作用域。从请求开始,到各个控制器和服务层方法处理请求参数,都使用同一个参数 Bean 实例,保证了请求数据在处理过程中的一致性,当请求结束时,该 Bean 实例被销毁。
Spring 借助与 Web 容器的集成,在请求开始时创建 Bean 实例并绑定到请求上下文,请求结束时自动销毁实例来实现 Request 作用域。
package org.example.principle;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/scope")
@RestController
public class ScopeController {
@Resource(name = "requestDog")
private Dog requestDog;
@RequestMapping("/request")
public String request(){
//从context中获取对象
Dog dog = (Dog)context.getBean("requestDog");
return "注入对象:"+ requestDog.toString() + ",context Dog:"+ dog.toString();
}
}
结果:
如上测试结果,每一次刷新都会创建新的实例,但是注入对象,和使用Bean保持统一,并不会随着Bean的使用而创建新的实例
4、Session 会话作用域
Session 作用域的 Bean 在一个用户会话期间是同一个实例。从用户首次访问应用建立会话,到会话结束(如用户注销或会话超时),对该 Bean 的引用都指向同一个对象。
在电子商务应用中,购物车 Bean 常被设置为 Session 作用域。用户在一次购物会话中,无论浏览多少商品页面,添加或修改购物车内容,操作的都是同一个购物车实例,方便用户购物体验的同时,也保证了购物车数据在会话内的一致性。
Spring 通过利用 Web 容器的会话管理机制,在会话创建时实例化 Bean 并绑定到会话上下文,会话结束时销毁实例来实现 Session 作用域。
测试代码:
package org.example.principle;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/scope")
@RestController
public class ScopeController {
@Resource(name = "sessionDog")
private Dog sessionDog;
@RequestMapping("/session")
public String session(){
//从context中获取对象
Dog dog = (Dog)context.getBean("sessionDog");
return "注入对象:"+ sessionDog.toString() + ",context Dog:"+ dog.toString();
}
}
测试结果:
在 浏览器1 反复刷新下
在 浏览器2 反复刷新下
这就相当于在不同的session下,才会去创建新的实例
5、Application 全局作用域
Application 作用域的 Bean 在整个 Web 应用范围内只有一个实例,类似于 Singleton,但更侧重于 Web 应用级别。
例如,应用的全局配置信息 Bean,如应用名称、版本号等配置信息,整个 Web 应用只需一个实例即可,各个组件都可以共享这个配置信息,方便统一管理和维护。
总之,正确理解和运用 Spring Bean 的作用域对于构建高效、稳定且可维护的 Java 应用至关重要。开发人员需要根据不同的业务需求和场景,合理选择 Bean 的作用域,以优化应用的性能和功能。
6、websocket HTTP WebSocket 作用域
WebSocket 的 “作用域” 更倾向于长期的、持续的连接场景。一旦建立连接,这个连接就可以在整个通信过程中持续存在,直到连接被关闭。
在一个 WebSocket 连接的范围内,服务器和客户端可以进行多次数据交互,这些交互都基于同一个连接,并且可以根据业务需求灵活地处理消息,如在一个在线游戏中,玩家的操作和游戏状态的更新都可以通过这个连接来完成。
二、Bean的生命周期
Bean 的生命周期是一个核心概念,它涵盖了从 Bean 的创建到销毁的整个过程。
我们可以大致将他划分为5个阶段,分别是实例化阶段,属性赋值阶段,初始化阶段,使用阶段,销毁阶段
我们可以用此代码去形象理解这个生命周期的五个过程,代码如下
package org.example.principle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BeanLifeComponent implements BeanNameAware {
private Dog dog;
//相当于实例化阶段
public BeanLifeComponent() {
System.out.println("执行构造函数...");
}
//属性装配
@Autowired
public void setDog(Dog dog) {
this.dog = dog;
System.out.println("使用setter方法, 完成属性装配...");
}
//初始化
@PostConstruct
public void init(){
System.out.println("执行init方法...");
}
@Override
public void setBeanName(String name) {
System.out.println("执行setBeanName, name:"+name);
}
//使用
public void use(){
System.out.println("执行use方法....");
}
//利用此注解去表示销毁阶段
@PreDestroy
public void destroy(){
System.out.println("执行destroy方法...");
}
}
测试代码:
package org.example.principle;
import org.example.autoconfig.TestConfig1;
import org.example.autoconfig.TestConfig2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrincipleApplication.class, args);
BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
beanLifeComponent.use();
}
}
执行结果:
由此日志信息我们 可以大致来观察Bean的生命周期
1、实例化阶段
当 Spring 容器启动时,首先会根据配置信息(如 XML 配置、Java 配置或基于注解的配置)确定需要创建的 Bean。容器会使用反射机制调用 Bean 的构造函数来创建一个实例。例如,如果有一个简单的 UserService
Bean,Spring 会在这个阶段创建 UserService
的对象实例,就像执行 new UserService()
操作一样。
2、属性赋值阶段
在实例创建之后,Spring 会根据配置将相应的值赋给 Bean 的属性。这可能包括从配置文件中读取属性值、注入其他依赖 Bean 等。比如,若 UserService
依赖于一个 UserRepository
Bean,Spring 会在此时将对应的 UserRepository
实例注入到 UserService
中,使得 UserService
能够使用 UserRepository
的功能。
3、初始化阶段
Bean 的初始化是一个关键步骤。在这个阶段,Bean 可以执行一些自定义的初始化逻辑。Spring 提供了两种主要的方式来定义初始化方法:一是在 XML 配置中使用 init-method
属性指定一个方法,二是在 Bean 类中使用 @PostConstruct
注解标注一个方法
实例化和初始化的区别
实例化是指创建一个类的对象的过程。在 Java 中,通过使用
new
关键字或者通过反射等机制,为对象分配内存空间,使得这个对象从类的定义变成一个可以使用的实体。这就好比是盖房子,实例化是按照蓝图(类的定义)搭建出房子的框架(对象),但此时房子还没有进行内部装修等后续工作。初始化是在对象已经实例化之后,对这个对象进行的一些准备工作,使得它能够正常使用。这包括给对象的属性赋值(如果还没有赋值)、建立必要的资源连接(如数据库连接、网络连接等)、加载配置信息等操作。继续用盖房子的比喻,初始化就像是对房子进行内部装修,安装水电、家具等,让房子具备居住的条件。
4、使用阶段
经过前面的步骤,Bedan 已经完全准备好,可以被应用程序使用了。在整个应用运行期间,Spring 容器会管理这些 Bean 实例,根据需要将它们注入到其他组件中,以提供各种服务。例如,在一个 Web 应用中,UserService
可能会被控制器调用,以处理用户相关的业务逻辑,如用户注册、登录、信息查询等操作。
5、销毁阶段
当应用程序关闭或 Spring 容器被销毁时,Bean 也会被销毁。与初始化类似,Spring 提供了两种定义销毁方法的方式:在 XML 配置中使用 destroy-method
属性,或者在 Bean 类中使用 @PreDestroy
注解。在销毁方法中,可以进行资源的释放操作,如关闭数据库连接、释放文件句柄等。
三、Spring Boot的自动装配
Spring Boot的自动配置就是当Spring容器启动后,一些配置类,bean对象就自动存入到了IoC容器之中,不需要我们手动声明。从而简化了开发,省去了繁琐的配置操作
1、Spring 加载 Bean
我们可以模拟去理解第三方jar包的使用
在 autoconfig 下创建 TestConfig 作为第三方资源
如上,启动类与第三方资源不在同一包下,因此,通过启动类
package org.example.principle;
import org.example.autoconfig.TestConfig1;
import org.example.autoconfig.TestConfig2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrincipleApplication.class, args);
TestConfig1 bean = context.getBean(TestConfig1.class);
bean.use();
TestConfig2 bean2 = context.getBean(TestConfig2.class);
bean2.use();
// BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
// beanLifeComponent.use();
}
}
会发生报错
No qualifying bean of type 'org.example.autoconfig.TestConfig1' available
没有类型为“org.example.autoconfig”的合格bean
原因分析:
Spring 通过五大注解和 @Bean 注解可以帮助我们把Bean加载到SpringIoC容器中,但前提是,这些注解类与SpringBoot启动类在同一个目录之下。
而此时,启动类即@SpringBootApplication在principle的包之下,他只会扫描此路径下的所有包,并不包括autoconfig之下的第三方资源
解决方案:
1、@ComponentScan
通过该注解去指定Spring的扫描路径
@ComponentScan({"org.example.autoconfig","org.example.principle"})//当表现多个路径时,要用集合{}
Spring 并没有采取这种方式 (当我们引入第三方框架时,并没有额外添加扫描路径,比如mybatis)
如果采取这种方式,当我们引入大量第三方依赖,比如Mybatis,jackson等时,就需要在启动类上配置不同的依赖需要扫描的包,这种方式会非常繁琐
2、@Import
使用该注解导入该类
@Import({TestConfig1.class,TestConfig2.class})
Spring 同样没有采取这种方式,理由同上,过于繁琐
3、导入ImportSelector接口实现类
先写一个接口实现类
package org.example.autoconfig;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
public class MyImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"org.example.autoconfig.TestConfig1","org.example.autoconfig.TestConfig2"};
}
}
再通过@Import去引入这个接口实现类
@Import(MyImport.class)
这些都存在一个明显问题,需要使用者需要知道第三方依赖中需要那些配置和Bean对象,如果我们漏掉了一个类,会使项目出现巨大问题
4、给第三方依赖提供一个注解
package org.example.autoconfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImport.class)
public @interface EnableTestConfig {
}
注解中封装@Import这个注解,并且导入MyImport.class这个类
再启动类上加入这个注解
@EnableTestConfig
Spring采取的方式便是这种
以上所有方式最后都能顺利使用TestConfig
2、自动装配的源码阅读
pring Boot 应用通常以一个带有 @SpringBootApplication(实现自动装配的核心)
注解的主类作为入口。这个注解实际上是一个组合注解,它包含了 @Configuration
、@EnableAutoConfiguration
和 @ComponentScan
。其中,@EnableAutoConfiguration
是实现自动装配的关键。
当 Spring Boot 应用启动时,@EnableAutoConfiguration
注解会触发自动装配机制。它会在类路径下查找 META-INF/spring.factories
文件,并加载其中配置的自动装配类。
阅读源码:
1、元注解
JDK中提供了4个标准的用来来对注解类型进⾏注解的注解类, 我们称之为 meta-annotation(元注
点开该注解,只是对@Configuration进行了一层封装
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
3、@EnableAutoConfiguration (开启自动配置)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
该注解首先使用@Import注解,导入了实现 ImportSelector 接口的实现类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//省略代码
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//获取自动配置的配置类信息
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//获取在配置⽂件中配置的所有⾃动配置类的集合
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
//获取所有基于
//METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports⽂件
//META-INF/spring.factories⽂件中配置类的集合
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
Assert.notEmpty(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;
}
在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载@Conditional是Spring 的底层注解,意思就是根据不同的条件,进行自己不同的条件判断 ,如果满足指定的条件,那么配置类里面的配置才会生效
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
SpringBoot的自动配置原理源码口是 @SpringBootApplication 注解,这个注解封装了3个注解@SpringBootConfiguration 标志当前类为配置类
@ComponentScan 进行包扫描(默认扫描的是启动类所在的当前包及其子包)
@EnableAutoConfiguration
- @Import 注解:读取当前项目下所有依赖jar包中 META-INF/spring.factories,META-INF/spring/org.springframework.boot.autoconfigure.Autoconfiguration.imports 两个文件里面定义的配置类(配置类中定义了 @Bean 注解标识的方法)
- @AutoconfigurationPackage:把启动类所在的包下面所有的组件都注入到Spring容器中
“读书不是为了雄辩和驳斥,也不是为了轻信和盲从,而是为了思考和权衡。”—— 培根
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸