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,仍然建议沿用默认的单例模式。

相关推荐
草莓base1 分钟前
【手写一个spring】spring源码的简单实现--bean对象的创建
java·spring·rpc
drebander26 分钟前
使用 Java Stream 优雅实现List 转化为Map<key,Map<key,value>>
java·python·list
乌啼霜满天24929 分钟前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn35 分钟前
java入门 自定义springboot starter
java·开发语言·spring boot
程序猿阿伟35 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
Grey_fantasy1 小时前
高级编程之结构化代码
java·spring boot·spring cloud
弗锐土豆1 小时前
工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程
java·opencv·安全·检测·面部
Elaine2023911 小时前
零碎04 MybatisPlus自定义模版生成代码
java·spring·mybatis
小小大侠客1 小时前
IText创建加盖公章的pdf文件并生成压缩文件
java·pdf·itext
一二小选手1 小时前
【MyBatis】全局配置文件—mybatis.xml 创建xml模板
xml·java·mybatis