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技能

3bf1b161181ba9ad450b9167060d2250.png

喜欢就点一下「好看」呗~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/271981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记~~~~~TreeMap

TreeMap继承了AbstractMap类,实现了NavigableMap、Cloneable、Serializable 接口 TreeMap也是一个很常用的map实现类,因为他具有一个很大的特点就是会对Key进行排序,使用了TreeMap存储键值对,再使用iterator进行输出时&#xff0c…

程序员别再迷茫,赚钱,方法比你想的更多

每次打开公号,扑面而来一阵阵焦虑:95后毕业3个月就买房,你的同龄人正在抛弃你毕业3年,年薪超100万:赚钱,是一种修行一线城市财务自由门槛2.9亿,看看你还差多少说来说去就是,牛人跑得…

Mac 创建本地Mysql_2018-09-25:mac下创建本地数据库mysql

问题:如何在mac系统下,创建本地数据库mysql?过程:1.安装brew install mysql2.启动mysql过程中遇到的问题:(1)ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)解决过程&am…

.NET Core 学习资料精选:入门

开源跨平台的.NET Core,还没上车的赶紧的,来不及解释了……本系列文章,主要分享一些.NET Core比较优秀的社区资料和微软官方资料。我进行了知识点归类,让大家可以更清晰的学习.NET Core。首先感谢资料原作者的贡献。第一篇&#x…

学习笔记~~~~~Set接口实现

Java中提供了HashSet、TreeSet、LinkedHashSet三种常用的Set实现,以下具体分析它们的用法和性能。 我们使用Set的原因是Set集合不包含重复元素,HashSet、TreeSet和LinkedHashSet三种类型什么时候使用它们,使用哪个这是一个很重要的选择性问题…

15句乔布斯经典语录(中英文)

1.Life is brief, and then you die, you know?人生短暂,过着过着你就没了,明白么?2.Innovation distinguishes between a leader and a follower.领袖和跟风者的区别就在于创新。3.Were here to put a dent in the universe. Oth…

mysql一些常用操作_mysql的一些常用操作(一)

1.启动Mysql服务net start mysql2.进入mysql环境中,由于自己没有设置密码,直接回车进入即可(要将bin加入到环境变量path中)mysql -u root -p3.创建一个数据库create database db_test default character set utf8 collate utf8_general_ci;显示数据库&am…

关于程序员的脑筋急转弯(附答案)

1、程序猿最常去的是哪间酒吧?2、程序猿什么情况下会选择离职?3、0是假,1是真,请问这是真还是假?4、你怎样才能知道一个计算机科学家是内向还是外向的?5、为什么大部分Java程序员都是戴眼镜的?6…

怎么确保一个集合不能被修改

集合(map,set,list)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。 可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛…

以下十种性格的人不适合做程序员,你​赞同吗? ​

目录 1. 宁愿参加培训,也不愿意自学 2. 喜欢正常的上下班时间 3. 喜欢正常加薪而不是跳槽 4. 无法和同事和睦共处 5. 容易垂头丧气 6. 思想保守,不考虑他人建议 7. 不注重细节 8. 没有工作荣誉感 9. 不能做到三思而后行 10. 不喜欢极客类型的人 以下十种…

Iterator与ListIterator有什么区别

Iterator与ListIterator区别如下: 1、Iterator是ListIterator的父接口。 2、Iterator是单列集合(Collection)公共取出容器中元素的方式。 对于List,Set都通用。 而ListIterator是List集合的特有取出元素方式。 3、Iterator中具备的功能只有hashNext(),ne…

IT行业17条经典语录

1. 手表定律:一个人有一只表时,可以知道现在是几点钟,当他拥有两只表时,却无法确定。所以,对于任何一件事情,不能同时设置两个不同的目标;对于一个人,也不能同时选择两种不同的价值…

mysql_install_db卸载_MySQL数据库的卸载与安装

MySQL数据库的卸载与安装MySQL的完全卸载因为不知道什么原因,电脑里同时存在两个版本的mysql,所以决定卸载重新安装,但是大家都说MySQL很难清除干净,所以特地查找完全卸载MySQL的方法。首先,快捷键winr输入regedit进入…

前端:QuickJS到底能干什么

QuickJS 是一个轻量且可嵌入的 JavaScript 引擎,它支持 ES2019 规范,包括 ES module、异步生成器以及 proxies。除此之外,还支持可选的数学扩展,例如大整数(BigInt)、大浮点数(BigFloat)和运算符重载。主要特点:轻量而…

随机存取是什么意思_手机小白必看!12GB+256GB,同样是GB,它们到底有什么不同?...

导语本文适合对电子产品有深度兴趣的小白,详细介绍了信息世界的数据计量单位,以及RAM与ROM的,文末给出了购机建议,建议不了解手机或者说半懂的同学观看。看完如果您觉得还可以的话,点赞关注,给小编一个鼓励…

ES6 解构赋值的用法笔记

1、概念:解构赋值可以理解为对赋值运算符的一种扩展。它主要针对数组或者对象进行模式匹配,然后对模式中的变量进行赋值。2、特性:采用ES6解构赋值的方式可以代码的可读性更高、代码书写更加简洁、清晰。3、解构模型:分为解构源、…

收集12个经典的程序员段子

1bug 跟蚊子的相似之处:1、不知道藏在哪里。2、不知道有多少。3、总是在你即将睡觉休息的时候出现。2A:最近在看《一拳超人》,觉得咱们程序猿跟埼玉老师有点像啊!B:哪里像了?A:越秃越强&#xf…

2020mysql安装教程_2020MySQL安装图文教程

MySQL安装图文教程(Windows10)1、MySQL下载可以去MySQL官网下载,或者在我提供的百度云链接下载。官网下载网速较慢,我从官网下载了将近四个小时,然后把下载好的放在了百度网盘,需要的而已自取。MySQL官网地址:MySQL官网…

说一下 runnable 和 callable 有什么区别?

主要区别 Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息 Runnable Ca…

几种常见的光纤接头(ST,SC,LC,FC)以及PC、APC和UPC的区别

一、几种常见的光纤接头(ST,SC,LC,FC)FC型光纤连接器:外部加强方式是采用金属套,紧固方式为螺丝扣。 一般在ODF侧采用(配线架上用的最多)SC型光纤连接器:连接GBIC光模块或普通光纤收发器的连接器,它的外壳呈矩形,紧固方…