spring怎么实现单例模式

简介: spring怎么实现单例模式

在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例)


singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。


prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。


Spring bean 默认是单例模式


简单来说,spring中的单例是通过单例注册表实现的。


实战演示:


代码详见上一篇文章中的代码。


在配置文件中,修改这句代码为:


1 <bean id="hi" class="com.test.Hi" init-method="init" scope="singleton">


在测试类中,修改代码为:


1      

ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");
         Hi hi1 = (Hi) context.getBean("hi");
         Hi hi2 = (Hi) context.getBean("hi");
        System.out.println(hi1);
         System.out.println(hi2);

结果为:

22.png


结论:二个变量指向一个对象。


将配置文件改为:


<bean id="hi" class="com.test.Hi" init-method="init" scope="prototype">

其他的不变,运行测试类,结果为:

23.png


结论:每次访问bean,均创建一个新实例。


Spring怎么实现单例模式


单例模式也属于创建型模式,所谓单例,顾名思义,所指的就是单个实例,也就是说要保证一个类仅有一个实例。

单例模式有以下的特点:

①单例类只能有一个实例

②单例类必须自己创建自己的唯一实例

③单例类必须给所有其他对象提供这一实例

我们可以使用另外一种特殊化的单例模式,它被称为单例注册表。


public class RegSingleton{  
   private static  HashMap registry=new HashMap();  
   //静态块,在类被加载时自动执行  
    static{  
     RegSingleton rs=new RegSingleton();  
     registry.put(rs.getClass().getName(),rs);  
   }  
//受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点  
protected RegSingleton(){}  
//静态工厂方法,返回此类的唯一实例  
public static RegSingleton getInstance(String name){  
    if(name==null){  
      name=” RegSingleton”;  
    }if(registry.get(name)==null){  
      try{  
          registry.put(name,Class.forName(name).newInstance());  
       }catch(Exception ex){ex.printStackTrace();}  
    }  
    return (RegSingleton)registry.get(name);  
}  
}


下面我们来看看Spring中的单例实现,当我们试图从Spring容器中取得某个类的实例时,默认情况下,Spring会才用单例模式进行创建。


(仅为Spring2.0支持)

以上三种创建对象的方式是完全相同的,容器都会向客户返回Date类的单例引用。那么如果我不想使用默认的单例模式,每次请求我都希望获得一个新的对象怎么办呢?很简单,将scope属性值设置为prototype(原型)就可以了


通过以上配置信息,Spring就会每次给客户端返回一个新的对象实例。


那么Spring对单例的底层实现,到底是饿汉式单例还是懒汉式单例呢?呵呵,都不是。

Spring框架对单例的支持是采用单例注册表的方式进行实现的,源码如下:


public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{  
   /** 
    * 充当了Bean实例的缓存,实现方式和单例注册表相同 
    */  
   private final Map singletonCache=new HashMap();  
   public Object getBean(String name)throws BeansException{  
       return getBean(name,null,null);  
   }  
...  
   public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{  
      //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)  
      String beanName=transformedBeanName(name);  
      Object bean=null;  
      //手工检测单例注册表  
      Object sharedInstance=null;  
      //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高  
      synchronized(this.singletonCache){  
         sharedInstance=this.singletonCache.get(beanName);  
       }  
      if(sharedInstance!=null){  
         ...  
         //返回合适的缓存Bean实例  
         bean=getObjectForSharedInstance(name,sharedInstance);  
      }else{  
        ...  
        //取得Bean的定义  
        RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);  
         ...  
        //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关  
        //`<bean id="date" class="java.util.Date" scope="singleton"/>`  
        //如果是单例,做如下处理  
        if(mergedBeanDefinition.isSingleton()){  
           synchronized(this.singletonCache){  
            //再次检测单例注册表  
             sharedInstance=this.singletonCache.get(beanName);  
             if(sharedInstance==null){  
                ...  
               try {  
                  //真正创建Bean实例  
                  sharedInstance=createBean(beanName,mergedBeanDefinition,args);  
                  //向单例注册表注册Bean实例  
                   addSingleton(beanName,sharedInstance);  
               }catch (Exception ex) {  
                  ...  
               }finally{  
                  ...  
              }  
             }  
           }  
          bean=getObjectForSharedInstance(name,sharedInstance);  
        }  
       //如果是非单例,即prototpye,每次都要新创建一个Bean实例  
       //<bean id="date" class="java.util.Date" scope="prototype"/>  
       else{  
          bean=createBean(beanName,mergedBeanDefinition,args);  
       }  
}  
...  
   return bean;  
}  
}


继续分析单例的获取流程:


@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            Map var4 = this.singletonObjects;
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }


这里涉及到三个单例容器:

singletonObjects

earlySingletonObjects

singletonFactories

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);


三级缓存


通过分析源码:

单例的获取顺利是singletonObjects ——》earlySingletonObjects ——》singletonFactories 这样的三级层次。


我们发现,在singletonObjects 中获取bean的时候,没有使用synchronized关键字,而在singletonFactories 和earlySingletonObjects 中的操作都是在synchronized代码块中完成的,正好和他们各自的数据类型对应,singletonObjects 使用的使用ConcurrentHashMap线程安全,而singletonFactories 和earlySingletonObjects 使用的是HashMap,线程不安全。


从字面意思来说:singletonObjects指单例对象的cache,singletonFactories指单例对象工厂的cache,earlySingletonObjects指提前曝光的单例对象的cache。以上三个cache构成了三级缓存,Spring就用这三级缓存巧妙的解决了循环依赖问题。


另外,源码中这三个容器的初始容积也做了配置,分别是256,16,16,也是值得分析和借鉴的。


单例的注册:

DefaultSingletonBeanRegistry类中:


public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
        Assert.notNull(beanName, "Bean name must not be null");
        Assert.notNull(singletonObject, "Singleton object must not be null");
        Map var3 = this.singletonObjects;
        synchronized(this.singletonObjects) {
            Object oldObject = this.singletonObjects.get(beanName);
            if (oldObject != null) {
                throw new IllegalStateException("Could not register object [" + singletonObject + "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
            } else {
                this.addSingleton(beanName, singletonObject);
            }
        }
    }


protected void addSingleton(String beanName, Object singletonObject) {
        Map var3 = this.singletonObjects;
        synchronized(this.singletonObjects) {
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }


向一级缓存中加入单例对象,同时,移除二级缓存,三级缓存中的单例对象。

并向注册登记表registeredSingletons中,记录单例的名称beanName。

由于容器对象都是map对象,所以能自动保存通一个beanName保存的对象唯一。


这里也可以知道了容器是什么?

spring中的存放bean的容器就是ConcurrentHashMap

目录
相关文章
|
设计模式 Java 开发者
Spring框架中JavaBean的生命周期及单例模式与多列模式
Spring框架中JavaBean的生命周期及单例模式与多列模式
133 0
|
Java Spring
Spring 循环依赖之单例模式
Spring 循环依赖之单例模式
39 0
|
设计模式 安全 Java
Spring中的单例模式使用
1 spring单例 V.S 设计模式的单例 • 设计模式单例,在整个应用中只有一个实例 • spring单例,在一个IoC容器中只有一个实例 Spring框架对单例的支持是采用单例注册表 但spring中的单例也不影响应用并发访问。大多数时候客户端都在访问我们应用中的业务对象,为减少并发控制,不应在业务对象中设置那些容易造成出错的成员变量。
214 0
Spring中的单例模式使用
|
前端开发 Java Spring
spring 的 controller 是单例模式,但是是多线程,各个线程之间不影响
spring 的 controller 是单例模式,但是是多线程,各个线程之间不影响
425 0
|
XML 安全 Java
面试官:spring单例模式,多例模式,懒汉模式,饿汉模式(一)?
面试官:spring单例模式,多例模式,懒汉模式,饿汉模式(一)?
面试官:spring单例模式,多例模式,懒汉模式,饿汉模式(一)?
|
安全 Java Spring
源码专题之spring设计模式:单例模式
单例模式 1.饿汉式(静态常量)[可用] public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return INSTANCE; } } 优点:这种写法比较简单,就是在类装载的时候就完成实例化。
1839 0
|
Java 容器 Spring
spring 的单例模式
singleton---单例模式 单例模式,在spring 中其实是scope(作用范围)参数的缺省设定值每个bean定义只生成一个对象实例,每次getBean请求获得的都是此实例单例模式...
780 0
|
3月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
187 2