Spring源码解析-Scopes之Singleton Scope(单例)和Prototype Scope(多例) 原 荐

栏目: Java · 发布时间: 5年前

内容简介:对于Spring里面的Bean的作用域(singleton:单例,spring默认的创建bean是个单例,在每个IOC容器里面只有唯一的实例;prototype:多例,spring创建多例时,都会实例化一个新的实例,但是这些实例只有且仅有一个bean的定义对象;

Bean的作用域(scopes)

对于Spring里面的Bean的作用域( scope ),我们在熟悉不过了,在Spring里面,平时开发中最经常遇到的作用域就是单例( singleton )和多例( prototype )了,但是除了单例和多例两个作用域,其实Spring还有其他的作用域,但是这些基本在开发中没用到过的,Spring完整的作用域有:

singleton:单例,spring默认的创建bean是个单例,在每个IOC容器里面只有唯一的实例;

prototype:多例,spring创建多例时,都会实例化一个新的实例,但是这些实例只有且仅有一个bean的定义对象;

request:将单个bean定义范围限定为单个HTTP请求的生命周期。每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。仅在Web感知Spring的上下文中有效ApplicationContext;

session:将单个bean定义范围限定为HTTP的生命周期Session。仅在Web感知Spring的上下文中有效ApplicationContext。

application:将单个bean定义到一个生命周期的范围ServletContext。仅在Web感知Spring的上下文中有效ApplicationContext。

websocket:将单个bean定义到一个生命周期的范围WebSocket。仅在Web感知Spring的上下文中有效ApplicationContext。

当然,除了spring定义的这些,我们自己也可以自定义scope。通过实现 org.springframework.beans.factory.config.Scope 这个接口来自定义我们的scope就可以了。

Singleton Scope(单例)

在spring的IOC容器中,一个单例bean有且仅有一个实例对象。也就是说,当你指定一个bean的定义为单例的话,spring IOC容器只会通过特定的bean定义创建唯一一个bean的实例对象,而这些被创建起来的单例会被缓存起来,在接下来的应用和请求中,都会返回缓存里的实例对象,spring关于单例的概念和我们平时在 设计模式 里面看到单例模式,还是有区别的,平时我们在设计模式书里面看到单例,往往是在每个类加载器上一个单例只会被实例化一次,即有且仅有一个实例对象,但是spring的单例更加侧着的是每个容器里只有一个实例化对象,所以这就要求容器的唯一性才能确保单例。 如何去指定一个bean是单例呢?spring提供了两种方式去实现,第一种,xml配置方式,通过xml来定义bean并指定scope;第二种,通过注解的方式,spring提供了注解@Scope来指定bean的作用域。

  1. xml实现方式:
<bean id="testService" class="com.demo.DefaultTestService"/>
<bean id="testService" class="com.demo.DefaultTestService" scope="singleton"/>

scope不指定默认就是单例,所以上面两种写法都是可以的。

  1. 注解实现方式
@Scope(value = "singleton")
@Service
public class DefaultTestService {
}

通过以上两种方式创建出来的就是单例了。在spring中,我们大部分的类都是单例,在单例中,我们应该尽量不使用有状态的成员变量,单例的成员变量是共享的,这就会导致多线程问题,所以,我们的spring的controller和Struts的action这两种的实现是有差异的,因为spring的controller是单例的所以很少会用到类成员变量,而Struts跟前端交互的参数基本都是类成员变量,这是因为Struts的action是多例才能这样做。所以当我们在使用spring创建bean的时候,我们要慎用有状态的类成员变量,如果要使用必须同步操作,不然就会引起多线程共享变量的问题了。当然你也可以创建多例来解决多线程问题。 我们新建两个service类,这2个service类同时依赖了1个dao类,这个dao类有个变量count,初始化为0。下面看代码:

@Service
public class DefaultTestService {
    @Autowired
    private TestDao testDao;

    public Integer getCount() {
        return testDao.getCount();
    }

    public void IncCount() {
        testDao.setCount(testDao.getCount()+1);
    }
    public TestDao getTestDao() {
        return testDao;
    }

    public void setTestDao(TestDao testDao) {
        this.testDao = testDao;
    }
}

@Service
public class DefaultTestService1 {
    @Autowired
    private TestDao testDao;

    public Integer getCount() {
        return testDao.getCount();
    }

    public TestDao getTestDao() {
        return testDao;
    }

    public void setTestDao(TestDao testDao) {
        this.testDao = testDao;
    }
}
@Component
public class TestDao {

    private Integer count =0;

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }
}

我们新建了上面三个类,然后通过不同的service去修改dao里面的变量,测试代码如下:

public class DemoTest extends BaseTest {
    protected static final Logger logger = LoggerFactory.getLogger(DemoTest.class);
    @Autowired
    private DefaultTestService defaultTestService;
    @Autowired
    private DefaultTestService1 defaultTestService1;
    @Test
    public void testGet() {
        defaultTestService.IncCount();
        logger.info("其他service对count加1后,别的service去获取到的结果:"+defaultTestService1.getCount());
        logger.info("defaultTestService:"+defaultTestService.getTestDao());
        logger.info("defaultTestService1:"+defaultTestService1.getTestDao());

    }
}

执行以上代码结果如下:

2018-12-17 14:07:07.816 [main] DemoTest - 其他service对count加1后,别的service去获取到的结果:1
2018-12-17 14:07:07.816 [main] DemoTest - defaultTestService:com.fcbox.pangu.manage.web.TestDao@46678e49
2018-12-17 14:07:07.816 [main] DemoTest - defaultTestService1:com.fcbox.pangu.manage.web.TestDao@46678e49

从结果可以得出,我们在一个service里面去修改dao里的变量count,在另外的service里面获取到了新修改后的值,通过打印可以知道,其实注入到service里的dao是同个实例,所以修改实例里面的变量是全局可见的,这也是为什么我们不能在单例里面使用有状态类成员的原因。当然了,这个例子并不是个严谨的多线程例子,只是为了简单说明单例在不同的调用类里面是同个实例罢了。同理,其实如果我们在单例里面如果使用到了成员变量,对成员变量的操作必须进行同步,如果你有看spring的源码,你会发现spring里面也有很多对成员变量访问进行了同步操作。

Prototype Scope(多例)

正如上面的例子,多例bean,在依赖注入的时候,都会从新建一个实例然后注入进去,多例的实现跟单例一样,只不过关键字从 singleton 变成了 prototype ,这样就定义好一个多例bean了。与其他的scope不同,Spring并不管理多例bean的完整生命周期。当一个多例bean被实例化后,这个实例就交由给客户端了,容器并不会记录这个实例。对上面的例子,如果我们把“dao”设置成多例,我们来看看打印出来的结果,结果如下:

2018-12-17 14:14:21.518 [main] DemoTest - 其他service对count加1后,别的service去获取到的结果:0
2018-12-17 14:14:21.518 [main] DemoTest - defaultTestService:com.fcbox.pangu.manage.web.TestDao@46678e49
2018-12-17 14:14:21.518 [main] DemoTest - defaultTestService1:com.fcbox.pangu.manage.web.TestDao@748e9b20

所以,当一个bean被设置成多例,每次依赖注入的时候,就会去创建新的实例,并注入到具体目标bean里面。可以看出,在DefaultTestService 对TestDao里面的变量修改,不会影响别的service,那是因为,不同的service依赖注入的TestDao是不同的实例,从结果打印就可以清楚的看出两个实例其实是不一样的。

单例bean里依赖多例bean

当一个单例的bean依赖一个多例的bean的时候,这个多例的bean只会在依赖注入的时候被实例化一次,实例化完后,在接下来就不会变了,也就是说只会被实例化一次,如果你想在运行时重复的获取一个多例的话,是不能通过这种依赖注入的方式去执行的,因为多例的实例化只会发生一次,接下来就不会再发生改变了。要想程序每次运行获取的实例都是不一样的,我们会在介绍其他scope里面会介绍,是通过代理的方式来实现的。

DefaultSingletonBeanRegistry简介

DefaultSingletonBeanRegistry可以简单的理解为单例的注册表,所有单例创建完后都会缓存在这个类里面,这个类里有一系列的单例创建,注册等操作。当创建bean的时候如果是单例都会用到这个类里面的方法。看了下里面,有很多cashe map对象如下:

Spring源码解析-Scopes之Singleton Scope(单例)和Prototype Scope(多例) 原 荐

单例的创建完都会保存到这些map中,看下具体的代码:

protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

spring源码里面很多对类成员变量的修改都会进行同步,所以我们的单例要是有成员变量就需要进行同步才行,不然会引起多线程问题的。这个代码其实就是将单例bean的实例对象和bean名字put到map里面保存起来,下次要是有需要注入该bean的话,会先去cache获取,获取不到才会去新建一个单例,新建完后也会放到缓存里面去,具体的单例的创建实例化过程下面讲。

单例和多例的实例化过程

首先,不管是单例还是多例的实例化,都是通过统一的bean的创建流程创建的,具体来看下他们的创建过程,所有的bean的创建都会去调用 getBean 方法,如下:

@Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}

doGetBean就会创建bean,继续看下它里面的实现,这个函数比较长不一一讲解,只讲解主流程,在这里面首先会先去缓存里面获取单例,其中的代码如下

Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		if (logger.isDebugEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	} else{
		//创建单例
	}

首先,会去spring的容器里面根据名字查找单例,如果获取到单例实例对象,就直接返回该实例,如果获取不到,则进入到else分支,就是创建单例,创建单例之前,首先会把目标bean里面的所有依赖bean(并非@Autowired注解的bean,而是xml用到depend-on标签,或者是参数值引用的bean)全都创建完才创建目标bean,部分代码如下:

//获取目标bean的definition
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);

// 实例化依赖bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
	for (String dep : dependsOn) {
		if (isDependent(beanName, dep)) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
		}
		registerDependentBean(dep, beanName);
		getBean(dep);
	}
}

从源码可以看出,依赖bean会在目标bean之前被创建出来。创建完依赖bean之后,就开始创建目标bean,首先他会先判断bean的定义是否是单例,如果是,会先去缓存查找是否已经创建了单例,如果创建了直接返回,否则创建单例并返回;如果不是则创建多例,创建单例的部分代码如下:

if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
		@Override
		public Object getObject() throws BeansException {
			try {
				//
				return createBean(beanName, mbd, args);
			}
			catch (BeansException ex) {
				// Explicitly remove instance from singleton cache: It might have been put there
				// eagerly by the creation process, to allow for circular reference resolution.
				// Also remove any beans that received a temporary reference to the bean.
				destroySingleton(beanName);
				throw ex;
			}
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

如果是多例则会进入到多例的创建流程,部分代码如下:

else if (mbd.isPrototype()) {
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		afterPrototypeCreation(beanName);
	}
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

单例和多例的创建的不同之处是,单例创建完后spring会记录他的实例状态并保存到缓存里面,下次创建会优先取缓存里的实例,而多例的创建则是直接创建实例化对象,并不会记录他的实例。 从上面的代码可以看到有句代码始终都会出现,不管是从缓存里面获取的单例还是创建的单例又或者是创建的多例,创建的实例对象并不是直接返回的,而是通过这个方法执行后作为结果返回,这句代码 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); 到底有什么作用呢?众所周知,spring里面有一种叫做bean工厂的,就是实现了接口 FactoryBean 的类,他的实例并不是真正的bean,他是一特定类的创建的工厂类,所以工厂的bean实例化完后并不能直接返回,bean工厂的具体的实例化对象是通过接口 FactoryBean 里面的方法 getObject 来实现的,所以如果是工厂类则需要特殊处理,这也是正是这句代码的作用,我们来看下它的实现:

protected Object getObjectForBeanInstance(
	Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

	// Don't let calling code try to dereference the factory if the bean isn't a factory.
	if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
		throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
	}

	// Now we have the bean instance, which may be a normal bean or a FactoryBean.
	// If it's a FactoryBean, we use it to create a bean instance, unless the
	// caller actually wants a reference to the factory.
	if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
		return beanInstance;
	}

	Object object = null;
	if (mbd == null) {
		object = getCachedObjectForFactoryBean(beanName);
	}
	if (object == null) {
		// Return bean instance from factory.
		FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
		// Caches object obtained from FactoryBean if it is a singleton.
		if (mbd == null && containsBeanDefinition(beanName)) {
			mbd = getMergedLocalBeanDefinition(beanName);
		}
		boolean synthetic = (mbd != null && mbd.isSynthetic());
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}

从代码里我们可以看出,要是如果是个普通的bean则就会直接返回实例对象,如果是个工厂bean,则会去执行获取工厂里的实例化对象。

总结

不管是单例还是多例的创建,这只不过是spring初始化bean的一个步骤,spring从准备bean的定义在到根据这些定义实例化bean,并为bean依赖注入各种属性,通过对spring的深入剖析,对spring的一个非常重要的概念IOC容器已经大体能理解。不管是applicationContext的加载还有bean definition的创建,还是到依赖注入和单例多例的创建都是spring的IOC容器里的一些重要的概念和流程。下一篇会对其他的scope进行研究。


以上所述就是小编给大家介绍的《Spring源码解析-Scopes之Singleton Scope(单例)和Prototype Scope(多例) 原 荐》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Design Index 7

Web Design Index 7

Pepin Press / PEPIN PRESS / 20070501 / TWD$1000.00

《網頁設計索引》年刊自2000年誕生起現已發展成同行業最重要的出版物之一,每年都會對網頁設計的最新趨勢給予準確概述。網站可簡單到只有一頁,也可以設計為具有最新數位性能的複雜結構。《網頁設計索引》的篩選標準是根據設計品質、創意及效率-而不管複雜程度如何。因此在本書中你可以找到所有可能的樣式和風格的實例。 每輯《網頁設計索引》都展示了1002個精采的網頁 同時提供了每個網頁的URL。網頁設計和編......一起来看看 《Web Design Index 7》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具