Spring Bean的单例和多例配置

在Spring以及Spring Boot开发中,我们常常使用自动装配就可以完成一个类中字段的组装,这是非常方便的,这是因为Spring框架会自动地将特定的类(标注了@Component等注解的)自动实例化为Bean并自动处理其依赖关系,最后存入IoC容器,我们直接取出就可以使用了。

也相信大家都听说过:在Spring中Bean默认是单例的,这究竟是什么意思呢?以及能否配置Bean为多例的呢?

1,Spring Bean的默认单例性

Spring可以帮我们自动创建对象,这些对象都称作Spring Bean,而Spring默认使用单例模式管理Bean,简单来说就是在IoC容器中,默认情况下一个类只会存在一个它的实例,即对应的每个(标注了@Component等注解的)类只会被实例化一次

在IoC容器中,Spring负责管理应用中组件的创建、配置和组装。当我们在Spring应用中声明一个Bean时,容器负责实例化该Bean,并且默认情况下,它会保持这个实例,并在需要的时候将该实例返回给调用者。

我们来看下列一个简单的例子,帮助我们理解Spring Bean的默认单例性。

首先新建一个Spring Boot工程,然后创建一个类MessageService用于后续被装配

java 复制代码
package com.gitee.swsk33.springbootdemo.service;

import org.springframework.stereotype.Component;

@Component
public class MessageService {

	public void print() {
		System.out.println("测试消息服务");
	}

}

然后创建两个测试类,在这两个类中我们使用@Autowired自动装配上述MessageService类,并在这两个测试类中的@PostConstruct标注的方法中打印装配的MessageService类实例的HashCode,查看它们是否是同一个实例。

首先是第一个测试类:

java 复制代码
package com.gitee.swsk33.springbootdemo.launcher;

import com.gitee.swsk33.springbootdemo.service.MessageService;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 测试类1
 */
@Component
public class TestOne {

	@Autowired
	private MessageService messageService;

	@PostConstruct
	private void init() {
		System.out.println("哈希码:" + messageService.hashCode());
	}

}

然后是第二个测试类:

java 复制代码
package com.gitee.swsk33.springbootdemo.launcher;

import com.gitee.swsk33.springbootdemo.service.MessageService;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 测试类2
 */
@Component
public class TestTwo {

	@Autowired
	private MessageService messageService;

	@PostConstruct
	private void init() {
		System.out.println("哈希码:" + messageService.hashCode());
	}

}

运行,得到下列输出:

hashCode方法是Object根类的方法,它默认会输出这个对象的哈希码,而哈希码是使用对象的内部地址信息(对象在内存中的位置)来生成的,这样同一个对象的哈希码始终是相同的,我们也可以通过判断两个变量的哈希码是否相同,来判断它们是否指向同一个对象。

可见上述我们在两个不同的类中自动装配了MessageService对象,但是在两个类中得到的MessageService实例都是同一个实例,这就说明在IoC容器中有且只有一个MessageService实例,并且无论我们在多少个类中自动装配了这个MessageService对象,得到的都是同一个实例。

现在在另一个测试类中,使用BeanFactory来获取多次MessageService的Bean试试:

java 复制代码
package com.gitee.swsk33.springbootdemo.launcher;

import com.gitee.swsk33.springbootdemo.service.MessageService;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TestBeanFactory {

	@Autowired
	private BeanFactory beanFactory;

	@PostConstruct
	private void init() {
		// 获取两次MessageService的Bean
		MessageService s1 = beanFactory.getBean(MessageService.class);
		MessageService s2 = beanFactory.getBean(MessageService.class);
		System.out.println("是否为同一对象:" + (s1 == s2));
		System.out.println("s1哈希码:" + s1.hashCode());
		System.out.println("s2哈希码:" + s2.hashCode());
	}

}

结果:

这也可见无论从IoC容器中取出多少次Bean,取出的始终是同一个。

可见Spring在创建和管理Bean的时候,默认遵循了单例模式,这样做有许多的好处,例如:

  • 性能更好: 单例模式可以减少对象的创建和销毁次数,提高系统性能,在应用启动时,Spring容器会创建并初始化所有单例的Bean,然后在整个应用的生命周期中,只需要使用同一个实例,这避免了频繁创建和销毁对象的开销
  • 节省资源: 单例模式确保所有对该Bean的请求都共享同一个实例,这对于一些资源密集型的操作,比如数据库连接池、线程池等是非常有利的,可以避免资源的浪费
  • 保证一致性: 在某些情况下,多个对象需要共享同一状态或配置信息,通过使用单例模式,可以确保这些对象都引用相同的实例,保持一致性

2,配置多例Bean

虽然Spring Bean默认遵循单例模式,但是这并非总是一成不变的。

我们可以通过@Scope注解配置一个类为多例的Bean,该注解中可以传入以下值:

  • ConfigurableBeanFactory.SCOPE_SINGLETON 配置当前类为单例模式 ,即单例Bean,这是所有Bean的默认配置模式
  • ConfigurableBeanFactory.SCOPE_PROTOTYPE 配置当前类为原型模式 ,即多例Bean

我们现在来配置MessageService为原型模式,使其能够生成多个Bean:

java 复制代码
package com.gitee.swsk33.springbootdemo.service;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

// 配置该类为原型模式(多例Bean模式)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class MessageService {
	// 省略其它...
}

好的,现在我们在之前的两个测试类(TestOneTestTwo)中自动装配该类并打印哈希码试试:

可见由于配置了该类为原型模式,这样只要每进行一次自动装配,就会实例化一个新的MessageService对象,这样不同类中自动装配的MessageService对象也就不再是同一个了。

我们再使用BeanFactory取出两次MessageService的Bean试试:

可见,配置一个类为原型模式,使其能够生成多个Bean是非常简单的。

对于原型模式的类,通常不建议使用@Autowired装配它,否则可能会导致一些资源的浪费,而是建议在需要的时候使用BeanFactory去实例化它并使用。

除非是一些有状态的类要生成为Bean,不然对于绝大多数无状态的类的Bean,仍然建议沿用默认的单例模式。

相关推荐
初晴~18 分钟前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
黑胡子大叔的小屋1 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark1 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
计算机毕设孵化场2 小时前
计算机毕设-基于springboot的校园社交平台的设计与实现(附源码+lw+ppt+开题报告)
spring boot·课程设计·计算机毕设论文·计算机毕设ppt·计算机毕业设计选题推荐·计算机选题推荐·校园社交平台
雷神乐乐2 小时前
Spring学习(一)——Sping-XML
java·学习·spring
苹果醋32 小时前
Golang的文件加密工具
运维·vue.js·spring boot·nginx·课程设计
小林coding3 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
V+zmm101343 小时前
基于小程序宿舍报修系统的设计与实现ssm+论文源码调试讲解
java·小程序·毕业设计·mvc·ssm
文大。3 小时前
2024年广西职工职业技能大赛-Spring
java·spring·网络安全
一只小小翠3 小时前
EasyExcel 模板+公式填充
java·easyexcel