在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 {
// 省略其它...
}
好的,现在我们在之前的两个测试类(TestOne
和TestTwo
)中自动装配该类并打印哈希码试试:
可见由于配置了该类为原型模式,这样只要每进行一次自动装配,就会实例化一个新的MessageService
对象,这样不同类中自动装配的MessageService
对象也就不再是同一个了。
我们再使用BeanFactory
取出两次MessageService
的Bean试试:
可见,配置一个类为原型模式,使其能够生成多个Bean是非常简单的。
对于原型模式的类,通常不建议使用@Autowired
装配它,否则可能会导致一些资源的浪费,而是建议在需要的时候使用BeanFactory
去实例化它并使用。
除非是一些有状态的类要生成为Bean,不然对于绝大多数无状态的类的Bean,仍然建议沿用默认的单例模式。