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

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

内容简介:对于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(多例) 原 荐》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Learning jQuery

Learning jQuery

Jonathan Chaffer、Karl Swedberg / Packt Publishing / 2007-7-7 / GBP 24.99

jQuery is a powerful JavaScript library that can enhance your websites regardless of your background. In this book, creators of the popular jQuery learning resource, learningquery.com, share the......一起来看看 《Learning jQuery》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具