javascript
spring boot2.x设置session有效时间_Spring 源码解析 Scopes 之 Request 、Session 、Application...
(給ImportNew加星標,提高Java技能)
轉自:開源中國,作者:麥克斯
鏈接:my.oschina.net/wang5v/blog/3017934
Request、Session、Application概念
在這篇Spring源碼解析-Singleton Scope(單例)和Prototype Scope(多例)博客中介紹了2個比較常用的scope同時也簡單的介紹了本篇博客要講的這三個不常用的scope的概念,今天來詳細揭開這3個很不常用的scope。 這三個只能用于web應用中,即要用于Web的Spring應用上下文(如:XmlWebApplicationContext),如果你用于非web應用中(如ClassPathXmlApplicationContext)是會拋出異常的。
Request Scope
第一個要介紹的就是Request了,顧名思義,如果bean定義了這個scope,標示著這個bean的生命周期就是每個HTTP Request請求級別的,換句話說,在不同的HTTP Request請求中,Request Scope的bean都會根據bean的definition重新實例化并保存到RequestAttribute里面。這也就是說,這個實例只會在一次請求的全過程中有效并可見,當請求結束后,這個bean就會被丟棄,生命周期很短的。又因為每次請求都是獨立的,所以你修改Request Scope的bean是只對內部可見,其他的通過相同的bean definition創建的實例是察覺不到的。 怎么去定義Request Scope呢?Spring提供了兩種方式: 一種XML方式配置:
"testRequest" class="com.demo.TestRequest" scope="request"/>另外一種就是注解的方式:
@RequestScope@Component
public class TestRequest{
// ...
}
這樣我們就指定了這個Bean的scope為Request的。但是,Request、Session和Application的用法不只是這樣就可以了,后面在應用中會更加詳細介紹怎么去用這三個東西。
Session Scope
接下來談下這個Session Scope,會話級別的。scope指定為Session的bean,Spring容器在單個HTTP會話的生命周期中使用bean定義來創建bean的新實例,也就是說,在每次會話中,Session Bean 會實例化,并保存到RequestAttribute里面,跟Request不同的是,每個會話只會實例化一次,而request是每次請求都會實例化一次。當我們,定義了Session的bean,那么標記著這個bean的生命周期就是在一次完整的會話中,所以在特定的HTTP Session 中bean的內部狀態修改了,在另外的HTTP Session 實例中根據一樣的bean definition創建的實例是感知不到的。當session結束時,這個bean也會隨之丟棄掉。 定義Session Scope也有兩種方式: 一種XML方式:
"testRequest" class="com.demo.TestRequest" scope="session"/>另外一種就是注解的方式:
@SessionScope@Component
public class TestRequest{
// ...
}
Application Scope
被Application標記的bean指示了Spring容器通過對整個web應用程序一次性使用bean定義來創建bean的新實例。也就是說,Application Scope bean的作用域在ServletContext級別,并存儲為一個常規的ServletContext屬性里面。這個跟單例有點類似,但是卻是不同的,每個ServletContext里是單例,但是對于Spring的每個ApplicationContext就不一定了。 定義Application Scope也有兩種方式: 一種XML方式:
"testRequest" class="com.demo.TestRequest" scope="application"/>另外一種就是注解的方式:
@ApplicationScope@Component
public class TestRequest{
// ...
}
將短生命周期的bean依賴注入到長生命周期的bean
當我們想要在一個長生命周期的bean如Singleon,注入一個短生命周期的bean如Request的時候,不能像我們定義單例一樣去注入實例,眾所周知,依賴注入只會發生在bean實例化后,依賴注入后的實例就不會再發生改變,也就是說,bean只會被實例化一次,然后就不會發生改變了,這很明顯違背了Request和Session等這些短生命周期的的原理。因為類似Request這種,是要在每次請求中都要去重新實例化一個對象的。如果單純的使用簡單的bean定義,這很明顯是不符合的。所以,接下來我們來介紹 ,在這些短周期的bean定義中要加上這個標簽,這個標簽的作用就是將你這個bean注冊成代理實例,并不是真正的實例對象,在依賴注入的時候,注入的是代理的實例,當用到這個代理的時候,才會起獲取被代理的對象的實例,這就很好的解決了上面的問題。所以真正要用到Request或者Session的時候是這樣定義的:
"testRequest" class="com.demo.TestRequest" scope="session">加入這個標簽后有什么變化呢,在下面的源碼剖析里會介紹。其實在實際運用中,要是你在長生命周期的bean中依賴了短生命周期的bean要是沒有加上這個標簽就會拋出異常的。注解@RequestScope和@SessionScope以及@ApplicationScope不需要增加什么,默認效果跟xml加上標簽是一樣的。
Request、Session、Application簡單應用
只有在使用支持web的Spring ApplicationContext實現(如XmlWebApplicationContext)中,才能使用Request、Session、Application作用域。如果將這些作用域與常規的Spring IoC容器一起使用,例如ClassPathXmlApplicationContext,就會拋出一個IllegalStateException異常。所以,我們只能在web應用中才能使用以上三個scope。 為了能夠使web應用支持Request、Session、Application級別的作用域,需要在定義bean之前進行一些較小的初始配置,標準作用域,singleton和prototype不需要這個初始設置。如果我們用的是Spring MVC的話則無需設置什么,就可以使用上面3個scope了,這是因為DispatcherServlet 暴露了這三個scope了。但是如果你用的是Strust等web應用的話,就需要配置下,在web.xml里面加入下面這段即可:
requestContextFilterclass>org.springframework.web.filter.RequestContextFilterclass>requestContextFilter/*
Request、Session、Application源碼剖析
scoped-proxy改變了什么
普通的bean的定義加入這個標簽后,spring會將這個bean轉成代理工廠的bean定義,具體代碼如下:
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
// 為原始bean名稱創建一個作用域代理定義,
//在內部目標定義中“隱藏”目標bean。
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
}
else {
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}
// Copy autowire settings from original bean definition.
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
// 忽略目標bean,代之以作用域代理。
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
上面代碼,可以看出,加上標簽后,spring初始化bean定義的時候會將目標bean轉成代理類的定義,而這個代理通過ScopedProxyFactoryBean工廠來創建,所以關鍵代碼在ScopedProxyFactoryBean工廠bean里面。簡單來看,當spring實例化這個proxy definition的時候,因為這個proxy definition是個工廠類,所以會去調用工廠的getObject方法,我們看下這個實現:
@Overridepublic Object getObject() {if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
實例化會返回一個proxy的實例,而proxy是在哪里創建的呢,繼續看,由于ScopedProxyFactoryBean實現了BeanFactoryAware接口,proxy實例就是在BeanFactoryAware接口的setBeanFactory方法里面實現了,我們看下具體實現:
@Overridepublic void setBeanFactory(BeanFactory beanFactory) {if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Class> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
"': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped proxy
// itself is not subject to auto-proxying! Only its target bean is.
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
這樣新建一個代理,這是通過編程方式的來編寫AOP,具體細節不討論了,我們只要知道這個代理,代理的源是pf.setTargetSource(this.scopedTargetSource);代理執行的時候,都會去獲取這個targetSource并執行里面的一個方法getTarget,方法如下:
@Overridepublic Object getTarget() throws Exception {return getBeanFactory().getBean(getTargetBeanName());
}
所以可以看出,加入標簽后,在依賴了這個bean的類里面其實依賴注入進來的是他的代理對象,當我們每次用到這個代理的時候,代理就會被攔截并執行getTarget方法來獲取被代理對象的實例,就會重新的執行spring實例化bean的操作。 當在單例中依賴注入了Request scope的bean的話,其實依賴注入的是代理的實例并不是真正的實例的,所以每次都會去實例化被代理的對象實例。當然這里,如果我們的在單例里面去依賴注入一個多例的話,如果要每次運行的時候獲取的多例每次都不一樣的話,我們也可以用這種方法來實現。
實例化Request、Session、Application Bean
實例化Request、Session等就會進入的Spring的實例化過程,在Spring源碼解析-Singleton Scope(單例)和Prototype Scope(多例)講到了單例和多例的創建,其實還有第三個流程就是除了單例和多例外的scopes的實例化流程,部分代碼如下:
else {//如果不是單例且不是多例則會進入到這個分支
String scopeName = mbd.getScope();
//首先獲取支持的sope
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//執行實例化并存放到RequestAttribute或者ServletContext屬性里面
Object scopedInstance = scope.get(beanName, new ObjectFactory() {
@Overridepublic Object getObject() throws BeansException {
beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});//跟上篇文章提到的處理工廠對象
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}catch (IllegalStateException ex) {throw new BeanCreationException(beanName,"Scope '" + scopeName + "' is not active for the current thread; consider " +"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
重點代碼是scope的get方法,接下來,來分別看下他們的具體實現: Request Scope的實現:
@Overridepublic Object get(String name, ObjectFactory> objectFactory) {RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
}
return scopedObject;
}
從代碼可以看出,首先會去attribute里面獲取相對于的scope的對象實例,如果獲取到了直接返回,獲取不到則從新實例化對象,不管Request還是Session都會執行上面的代碼,上面的代碼是在抽象類AbstractRequestAttributesScope里面的實現。 接下來看下,Session的具體實現,如下:
@Overridepublic Object get(String name, ObjectFactory> objectFactory) {Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
synchronized (mutex) {
return super.get(name, objectFactory);
}
}
首先,他會先獲取當前的session互斥鎖,進行同步操作,保證會話創建實例只會創建一次,其他都是從session里面去取,雖然都是RequestAttribute來存放其實內部實現并不是,來看代碼:
@Overridepublic void setAttribute(String name, Object value, int scope) {//scope 是 request
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}
//scope 是session
else {
HttpSession session = getSession(true);
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}
setAttribute里面其實根據scope不同做了不同的處理,Session是放到http session里面,而request則是放http request的attribute里面。所以從這里可以看出,request是針對每次請求都會實例化,在單次請求中是同個實例,當請求結束后,就會被銷毀了;而session則是在一次完整的會話只會實例化一次,實例化完后就會緩存在session里面。
總結
至此,Spring的所有的scope基本就解釋完了,我們基本上能夠知道這些scope如何去用,以及他們各自的原理。我們可以根據業務需求去使用不同的scope,除了單例外的其他scope的使用還是需要謹慎的去用,不然非但沒有其效果,可能會適得其反。
推薦閱讀
(點擊標題可跳轉閱讀)
Spring 中獲取 request 的幾種方法,及其線程安全性分析
從 Spring Cloud 看一個微服務框架的「 五臟六腑 」
Spring AOP 的實現原理
看完本文有收獲?請轉發分享給更多人
關注「ImportNew」,提升Java技能
喜歡就點一下「好看」唄~
總結
以上是生活随笔為你收集整理的spring boot2.x设置session有效时间_Spring 源码解析 Scopes 之 Request 、Session 、Application...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win 7 各版本的含义
- 下一篇: Spring的开幕式——Spring概述