spring怎么实现单例模式

spring怎么实现单例模式_弹指天下-CSDN博客_spring 单例模式

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

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

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

Spring bean 默认是单例模式

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

实战演示:

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

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

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

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

         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);

结果为:

在这里插入图片描述
结论:二个变量指向一个对象。

将配置文件改为:

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

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

在这里插入图片描述
结论:每次访问bean,均创建一个新实例。

Spring怎么实现单例模式

单例模式也属于创建型模式,所谓单例,顾名思义,所指的就是单个实例,也就是说要保证一个类仅有一个实例。
单例模式有以下的特点:
①单例类只能有一个实例
②单例类必须自己创建自己的唯一实例
③单例类必须给所有其他对象提供这一实例
我们可以使用另外一种特殊化的单例模式,它被称为单例注册表

import java.util.HashMap;

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
————————————————
版权声明:本文为CSDN博主「斗者_2013」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:spring怎么实现单例模式_弹指天下-CSDN博客_spring 单例模式

  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值