简介:
首先spring里有个比较关键的概念叫做上下文 也就是ApplicationContext。这是个什么玩意儿呢?
ApplicationContext
(经常被称为上下文或Spring上下文)是Spring框架的关键部分。它是Spring的容器,用于存储和管理Bean的实例。ApplicationContext
负责实例化、配置和组装beans,即所谓的依赖注入。
当你在应用程序中调用context.getBean()
方法时,你是在给定的Spring上下文(context
)中获取一个命名的Bean,Spring会查找并返回与所提供的名称匹配的Bean。
例如:context.getBean("studentService")
,这个方法调用会返回一个名为studentService
的Bean。
注意不一样的点来了!!
- 如果这个Bean在Spring配置文件中定义为单例模式,
getBean()
方法会返回容器中的唯一实例。 - 如果这个Bean定义为的是原型类型,
getBean()
方法会为每个调用创建一个新的实例。
那么我们更具体的说:单例模式 (Singleton) 是Spring的默认的bean作用域(在springboot框架中可以通过注释@Scope去切换模式,默认状态下的都是单例模式)。在spring的ioc容器中,每个ID只对应一个bean实例,该实例在第一次创建后会被缓存,以后每次请求该bean,都会直接返回第一次创建的实例。这样可以确保在一个Spring IoC容器的上下文中,每个bean都是唯一的。
原型模式 (Prototype) 在每次请求它的时候都会创建一个新的bean实例,即使同一个ID,由于作用域是原型,所以返回的也是不同的实例。下面通过示例来演示一下
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public StudentService studentService() {
return new StudentService();
}
}
在这个例子中,StudentService
的作用域通过@Scope("prototype")
设置为原型,这意味每次调用studentService()
方法,都将获得一个新的StudentService
实例。
分析一下底层的原理我们可以知道
@Configuration
public class AppConfig {
@Bean
//@Scope("prototype") // 按需取消注释这行来观察效果变化
public MyBean myBean() {
return new MyBean();
}
}
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean bean1 = context.getBean(MyBean.class);
MyBean bean2 = context.getBean(MyBean.class);
System.out.println(bean1.hashCode());
System.out.println(bean2.hashCode());
}
}
在单例模式下(即没有取消注释 @Scope("prototype")
行),bean1
和 bean2
实际上指向同一个对象,它们的哈希码也因此相同。
在原型模式下(即取消注释 @Scope("prototype")
行),每次通过 context.getBean(MyBean.class);
获取的都将是不同的对象,因此,bean1
和 bean2
的哈希码将会不同,表明它们是不同的对象。
通过打印对象的哈希码来验证单例和原型模式下对象是否相同。哈希码通常是对象在内存中的地址转换成的一个数值,如果两个对象的哈希码相同,那么这两个对象通常就是同一个对象。那么就不难发现,实际上每次调用原型模式生成的实例之间其内存地址不同。熟悉hashcode方法的情况下就很容易去理解
光知道这些有什么用呢 ?
对于设计模式来说,我们只需要对其了解就已经足够了,原型模式实际上正常的开发流程中也比较少见,由于他的特点,在实际的开发流程中对原型模式的使用对开发者的技术要求比较高,如果对实例的管理机制不够了解,容易导致一些难以被发现的报错。
原型模式的应用场景可以大致的参考一下以下案例
状态不可共享bean
-
假设我们有一个
ShoppingCart
类,这个类包含了当前用户的购物车信息:用户ID,商品列表等。由于每个用户的购物车都是不同的,这就需要为每个用户创建一个新的购物车实例。这样每一个实例就可以拥有自己的状态(用户ID,商品列表),并且这些状态之间不会互相干扰。在这种情况下,为ShoppingCart
设置成原型模式就很合适
重要资源对象
- 对于一些创建和销毁成本较高的对象,如果每次需要时都去创建和销毁,那么系统的开销会非常大。例如,在一些大型软件应用中,可能会为了提供服务而创建许多对数据库的连接(数据库连接的建立和销毁成本高),这个时候可以将数据库连接设置为原型模式,这样每次需要数据库连接时,都从Spring容器获取一个新的实例,替代新建一个数据库连接。
在运行时动态确定,创建哪一类的实例
- 在有些情况下,你可能需要在运行时动态决定创建哪类对象。例如,你有多种排序算法类,这些算法类都实现自同一个接口。你可以在运行时,基于不同的输入或者条件,选择使用哪种排序算法,这时,可以将每种排序算法类设为原型Bean,这样就可以动态地从Spring中获取特定的排序算法实例。
以上场景不一定需要原型模式来进行设计,总的来说还是需要更具需求再来讨论具体的技术方案的,抛开需求不谈一切都是在耍流氓。具体使用哪个模式,取决于你的具体需求。如果你的 Bean 是有状态的,或者你需要每次都得到一个全新的对象实例,那么原型模式可能是一个好选择。但如果你的 Bean 是无状态的,或者多个组件共享一个 Bean 实例没有问题,那么单例模式可能更有优势,因为它可以减少对象创建的开销。