引言:例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单例模式通常用于日志记录、配置管理、缓存等场景,以避免创建过多的对象实例,从而提高系统性能。在Spring框架中,单例模式被广泛应用,Spring使用单例模式来创建和管理应用程序中的各个对象。本文将和大家一起回顾温习一下单例模式,并且了解其在Spring是如何使用的。
一、单例模式回顾
1.1 概念
单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这个全局访问点通常是一个静态方法或静态变量,用于获取类的唯一实例。
单例模式通常使用延迟初始化技术,即在第一次获取实例时才创建对象。此外,单例模式还提供了一种机制来控制实例的生命周期,以防止资源泄漏。
1.2 实现方式
单例模式的实现方式有多种,以下是几种常见的实现方式:
饿汉模式
在这种实现方式中,实例在类加载时创建,因此称为“饿汉式”。以下是代码示例:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
懒汉模式
在这种实现方式中,实例在第一次使用时创建,因此称为“懒汉式”。以下是代码示例:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检查锁定(Double Checked Locking)
在这种实现方式中,使用了双重检查锁定来确保只有一个线程能够初始化实例。这种实现方式结合了饿汉式和懒汉式的优点,既可以实现延迟加载,又可以在多线程环境下保证线程安全。以下是代码示例:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
// 第一次检查,避免不必要的同步
if (instance == null) {
// 加锁,保证只有一个线程可以进入创建实例
synchronized (Singleton.class) {
// 第二次检查,防止在第一个线程释放锁之后,其它线程再次创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
枚举实现
在Java中,枚举类型是天然的单例模式。通过将类定义为枚举类型,并在其中定义唯一的实例,可以很容易地实现单例模式。以下是代码示例:
public enum Singleton {
INSTANCE;
}
1.3 单例模式的优点
(1) 只有一个实例,避免了创建过多的对象实例,提高了系统性能。
(2)提供了一个全局访问点,方便对单例进行管理和访问。
(3)可以方便地实现延迟加载和生命周期管理。
(4)适用于需要频繁访问实例的场景。
(5)可以防止对实例的过度使用。
(6)可以防止对实例的意外修改。
(7)可以防止对实例的重复创建。
二、 Spring框架中单例模式
在Spring框架中,单例模式是通过工厂模式实现的。具体而言,Spring通过一个工厂类(通常是BeanFactory或ApplicationContext)来管理所有的对象实例,该工厂类负责创建、初始化和缓存所有的单例对象。Spring框架中的单例模式是一种非常重要的设计模式,下面我们来看一下几个比较重要的应用。
(1)ApplicationContext
ApplicationContext是Spring框架的核心容器,用于管理Bean的生命周期和依赖注入。ApplicationContext在初始化的过程中会创建多个Bean实例,但是ApplicationContext本身是以单例模式实现的。
(2)BeanFactory
BeanFactory是Spring框架中的一个接口,它定义了获取Bean的方法。在Spring中,可以通过ApplicationContext获取Bean,而ApplicationContext实际上是BeanFactory的一个实现类。
几乎所有的ApplicationContext实现都是以单例模式实现的,因为ApplicationContext的初始化代价较高,同时它被用来获取Bean,应保证整个应用中只有一个实例。
(3)BeanPostProcessor
BeanPostProcessor是Spring框架中的一个接口,它定义了在Bean初始化前后进行自定义处理的方法。BeanPostProcessor提供了一种拦截器机制,允许我们在Bean被实例化和初始化的过程中进行额外的处理。
BeanPostProcessor通常是以单例模式实现的。因为在整个容器的生命周期中,BeanPostProcessor的实例很少发生变化,而且它们通常没有状态。
(4)DefaultListableBeanFactory
DefaultListableBeanFactory是Spring框架中的一个类,它是BeanFactory的一个默认实现。DefaultListableBeanFactory管理了一个Bean定义的注册表,并提供了获取Bean的功能。 DefaultListableBeanFactory使用单例模式实现,保证整个应用中只有一个实例。
(5)AbstractAutowireCapableBeanFactory
AbstractAutowireCapableBeanFactory是Spring框架中的一个类,它是BeanFactory的抽象实现,并且提供了自动装配的能力。
在获取Bean的过程中,AbstractAutowireCapableBeanFactory会检查Bean定义的自动装配模式,然后通过反射机制实例化Bean,为其属性进行依赖注入。 AbstractAutowireCapableBeanFactory使用单例模式实现,确保整个应用中只有一个实例。
(6)HandlerMapping
HandlerMapping是Spring框架中的一个接口,用于将请求映射到处理程序并返回处理程序对象。在Web应用中,HandlerMapping在容器启动时会被实例化,并设置到DispatcherServlet中。
HandlerMapping通常以单例模式实现。因为在整个应用的运行过程中,映射关系不会发生改变,只需要一个实例来进行请求映射即可。
以上只是列举了一部分Spring框架中使用单例模式实现的类,由于Spring框架十分庞大,涵盖的功能很多,还有很多其他类也是以单例模式实现的。如果大家感兴趣可以深入研究一下源码。
三、小tips
Spring的Controller是单例还是多例,如何保证并发的安全?这是一个在面试过程中经常遇到的问题,不知道大家有没有思考过这个问题呢。
首先我们可以回答Controller默认是单例的,并且在方法中不要使用非静态的成员变量,否则会发生数据逻辑混乱,如下代码所示:
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
private int num = 0;
@RequestMapping("/test1")
public void test1() {
System.out.println(++num);
}
@RequestMapping("/test2")
public void test2() {
System.out.println(++num);
}
}
首先访问 http://localhost:8080/test1
,得到的答案是1
; 然后我们再访问 http://localhost:8080/test2
,得到的答案是 2
。得到的不同的值,这是线程不安全的。
接下来我们再来给Controller
增加作用多例 @Scope("prototype")
。
然后首先访问 http://localhost:8080/test1
,得到的答案是1
; 然后我们再访问 http://localhost:8080/test2
,得到的答案还是 1
。我们不难了解到单例是不安全的,会导致属性重复使用。
那如何解决这个问题呢?
1、不要在Controller中定义成员变量。
2、万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式。
3、在Controller中使用ThreadLocal变量
五、总结
Spring框架中的单例模式是一种非常重要的设计模式,它可以有效地管理和控制对象的创建和生命周期。通过合理应用单例模式,可以提高程序的性能、稳定性和安全性,并减少资源的占用。同时,单例模式也为依赖注入等功能提供了强大的支持。因此,掌握和理解Spring框架中的单例模式对于开发高质量的Java应用程序是非常重要的
refs
# 35岁愿你我皆向阳而生
# Spring Boot整合XXL-JOB保姆级教程
# 深入解读Docker的Union File System技术
# 说一说注解@Autowired @Resource @Reference使用场景
# 编写Dockerfile和构建自定义镜像的步骤与技巧
# 学习自定义Spring注解