说一说Spring中的单例模式

2023年 8月 18日 93.7k 0

引言:例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单例模式通常用于日志记录、配置管理、缓存等场景,以避免创建过多的对象实例,从而提高系统性能。在Spring框架中,单例模式被广泛应用,Spring使用单例模式来创建和管理应用程序中的各个对象。本文将和大家一起回顾温习一下单例模式,并且了解其在Spring是如何使用的。

image.png

一、单例模式回顾

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是单例还是多例,如何保证并发的安全?这是一个在面试过程中经常遇到的问题,不知道大家有没有思考过这个问题呢。

image.png

   首先我们可以回答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注解

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论