为网站添加网址图标favicon.ico
相信用过spring-session做session共享的朋友都很喜欢它的精巧易用-不依赖具体web容器、不需要修改已成项目的代码。笔者在使用spring-session的过程中也对spring-session的绝佳包容性、稳定性赞叹不已,spring-session 和 redis 的结合堪称神器,但是两者结合下来真的可以完全代替原本的session管理吗?
一、url rewrite保持Session
相信很多做过文件上传的朋友遇到过这样的需求-在浏览器中显示上传进度条并且要求多浏览器兼容性,特殊国情~兼容IE低版本,OK,只能用上笔者认为已经过时的技术-Flash,做前端比较多的肯定知道SWFUpload、Uploadify这类通过调用Flash上传实现浏览器本身不具备的显示进度条的功能。但是在某些浏览器、某些flash客户端版本下,上传的HTTP请求是不带cookie的,so,session问题如何解决?普遍的做法是通过url rewrite保持Session,即获取cookie中的jsessionid来放到请求url的参数中。那么spring-session支持吗?回答NO,至少spring-session源码中是没有支持的,如何支持呢?
我们阅读代码可以看到spring-session中实现从cookie到session的策略类是CookieHttpSessionStrategy,并且允许自定义策略类,只需要在spring-session中定义bean就行了,所以我们来扩展这个CookieHttpSessionStrategy。
1. 想要直接继承CookieHttpSessionStrategy?那是不可能的,它是final的,为啥?暂时不清楚。
2. 看来只能硬来了,首先把CookieHttpSessionStrategy的源码复制出来,放到自己的项目里一份,去掉final关键字,姑且新类名就叫SessionForCookieStrategy吧。
3. 为了整洁,不建议在这个类下直接修改了,咱还是应该坚持java人的操守不是?新建一个SessionUnionStrategy类,提供了从request域中获取jsessionid的参数。
4. 建立SessionForURLFilter,即处理从url中获取jsessionid然后把值丢给request Attribute中。
5. 配置文件配置Strategy和Filter
上代码:
SessionUnionStrategy类:
public class SessionUnionStrategy extends SessionForCookieStrategy{
@Override
public Map<String, String> getSessionIds(HttpServletRequest request) {
Map<String, String> result = super.getSessionIds(request);
if(result.isEmpty()){
String jsessionId = (String)request.getAttribute(SessionForURLFilter.OLDEST_URL_SESSION_ID_ATTRIBUTE_NAME);
if ((jsessionId != null) && (!"".equals(jsessionId.trim())))
{
result.put(DEFAULT_ALIAS, jsessionId);
}
}
return result;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
SessionForURLFilter类:
public class SessionForURLFilter extends OncePerRequestFilter{
public static final String OLDEST_URL_SESSION_ID_ATTRIBUTE_NAME = "OLDEST_URL_SESSION_ID_ATTRIBUTE_NAME";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if(request.isRequestedSessionIdFromURL()){
String jsessionId = request.getRequestedSessionId();
if ((jsessionId != null) && (!"".equals(jsessionId.trim()))){
request.setAttribute(OLDEST_URL_SESSION_ID_ATTRIBUTE_NAME, jsessionId);
}
}
filterChain.doFilter(request, response);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
spring容器配置文件中:
<bean name="sessionForURLFilter" class="cn.emay.bootstrap.util.SessionForURLFilter"/>
<bean class="cn.emay.bootstrap.util.SessionUnionStrategy">
<property name="cookieSerializer">
<bean class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="cookieName" value="JSESSIONID"/>
</bean>
</property>
</bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
web.xml文件中:
<filter>
<filter-name>sessionForURLFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionForURLFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
二、除了JDK序列化还能用JSON序列化方式吗?
用过spring-session的朋友都知道,它的基本工作原理是把原本session中的对象从单机的内存中剥离出来放到的公共存储中,这就需要序列化了,默认使用JDK序列化方式,并且是支持自定义序列化方式的。很多人知道既然一般一个JAVA对象的JSON的存储量肯定比JDK序列化方式的存储量小的多,那为啥不用JSON来存储?一来可以减轻IO的压力,二来可以直接在redis中直接阅读session数据。
首先在spring-session的文档中找到这么一段:
Custom RedisSerializer
You can customize the serialization by creating a Bean namedspringSessionDefaultRedisSerializerthat implementsRedisSerializer<Object>.
笔者也忍不住也就试了一番,spring容器配置:
<bean id="springSessionDefaultRedisSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
- 1
可以跑起来,不过遇见下列代码就头疼了:
@RequestMapping("/setS")
public String setSession(HttpServletRequest req) {
Long value = 1l;
req.getSession().setAttribute("key", value);
return null;
}
@RequestMapping("/getS")
public String getSession(HttpServletRequest req) {
Long value = (Long)req.getSession().getAttribute("key");
System.out.println(value);
return null;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
触发异常:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
- 1
去redis中找具体存储数据:
7) "sessionAttr:key"
8) "1"
- 1
- 2
了然,JSON的虚化列方式明了是明了,但是连个java类型都没有限定说明,虽然我们可以去获取对象前判断类型再转化,但是也就丧失了spring-session使用的关键优点-不需要修改已有代码。
三、JSP下的session设置坑
这是一个比较难发现的问题,有些朋友在spring-session上手之后可能一帆风顺就没有去关注spring-session的基本工作流程,但是在spring-session何时将放入session中的对象序列化存储到redis中如果没有一个清晰的认识可能会进入这个坑。
如果你在你的代码中有这样存入session对象:
controller中:
@RequestMapping("/setS")
public String setSession(HttpServletRequest req) {
Map<Object,Object> value = new HashMap<Object,Object>();
req.getSession().setAttribute("valid", value);
return "test";
}
@RequestMapping("/getS")
public String getSession(HttpServletRequest req) {
Map<Object,Object> value=(Map<Object,Object>)req.getSession().getAttribute("valid");
System.out.println(value.keySet().size());
return null;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
test.jsp中:
<c:forEach var="v" begin="1" end="100" step="1">
<%-- 任意长文本--%>
<c:set target="${valid}" property="${v}" value="1"/>y
</c:forEach>
- 1
- 2
- 3
- 4
最终getS打印的size未必是100,本地测试在jetty下正常,在tomcat下就不是100了,可能只有一半,只存入了一半数据?调试得出问题所在,看图:
结论是当JSP输出到buffer的时候如果buffer满了的话将flushBuffer,同时将由spring-session提交session,即写入redis。spring-session源码中:
RedisOperationsSessionRepository中部分方法:
public void setAttribute(String attributeName, Object attributeValue)
{
this.cached.setAttribute(attributeName, attributeValue);
this.delta.put(RedisOperationsSessionRepository.getSessionAttrNameKey(attributeName), attributeValue);
flushImmediateIfNecessary();
}
private void saveDelta()
{
...序列化存入redis
this.delta = new HashMap(this.delta.size());
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
由此可见当flushBuffer的时候会将delta重置,此时已经将对象序列化入redis中了,不会管之后这里边的对象会不会改变,除非再次delta.put(...)
最终解决办法及建议:在完成对象修改之后最后将需要设置进session中的对象setAttribute...。
四、redis键空间通知与对象序列化serialVersionUID改变之后
笔者对spring-session的redis键空间通知方面的接触始于一个开发问题,如果在一个web集群下单个web容器中修改了将放入session中的对象的class结构(或者说是serialVersionUID改变),那么在其它web容器在有session失效中,该容器将触发异常-无法反序列化session对象,最终通过抓包发现,当其它服务器有session的重新登录的时候该web容器向redis发出了hgetall (旧sessionid)命令。也就是说web集群中所有的session失效时,其它所有服务器将接受到通知并反序列化这个session中的所有对象。结合spring-session文档可以找到:
Firing SessionDeletedEvent or SessionExpiredEvent is made available through the SessionMessageListener which listens to Redis Keyspace events. In order for this to work, Redis Keyspace events for Generic commands and Expired events needs to be enabled. For example:
redis-cli config set notify-keyspace-events Egx
很明显spring-session实现Session删除事件和Session过期事件需要依赖redis的键空间通知功能,spring-session的源码中直接默认执行这句redis命令(是的,直接执行config set,笔者对这种直接侵入的做法实不敢苟同)。当然会有朋友想到实现这种全局通知对redis的性能影响得多大,在高并发访问情况下尤其影响吧。对此笔者翻阅了spring-session的在线文档,没有一个清晰的解释。只有提到如果使用者的redis是一个安全较高的公共redis(比如阿里云的),可以这样配置:
<util:constant
static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
- 1
- 2
笔者也同样搜索了很久,大多博文对这个的解释模棱两可。通过测试得出这句配置只是说让spring-session不去直接执行config set,并没有说可以不用redis的键空间通知,而且如果你的程序已经运行过了,即已经对redis设置过这个键空间通知了,不去手动在redis种清除这个config那么将依然收到键空间通知。如果需要彻底不接受redis键空间通知,可首先加入这句配置,然后去redis中将键空间通知config置空(笔者只是实现了不通知,是否有其它程序上的问题没有全面的测试,为了稳定暂时只能按照spring-session默认的来)。对于能否取消redis键空间通知以提高web集群的性能笔者没有再深入spring-session源码,有经验的读者可以给予下意见。
五、题外:spring升级后的一个问题
spring-session要求spring基础库版本在3.2.14以上,如果你的web应用的spring框架版本是3.0.x,那么在升级至该版本时,请升级关键配置:
将过时的配置:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
- 1
修改为:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" >
- 1
否则在文件上传至返回json的请求处理器时,web容器将上传成功但返回http错误码,更多的关于这个过时配置的bug读者可自行Google。
六、spring-session测试性能简说
笔者在实际LR压力测试监控过程中,spring-session调用redis方面性能还是挺稳定的,粗略得出的数据有在最高5000人并发访问web集群时redis占用内存6G,redis连接数600(当然这只是个参考,具体web应用的session存储内容不同),redis和web容器在同一个内网的环境下前端打开速度与没有共享session情况下未发生明显的延迟,建议保证redis服务器与web应用间的数据联通速率。对测试数据感兴趣的开发者推荐使用Apache ab工具进行压测。
为网站添加网址图标favicon.ico的更多相关文章
- [转载]网站地址栏小图标favicon.ico的制作方法
有人也许会好奇,有的网址前面有个漂亮的小图标而且有的网站图标还会动,这是怎么做到的呢? 如下图所示: 那个小图标有个名字叫favicon.ico,网站图标虽小但可以起到很好的点缀作用,尤其是当浏览者将 ...
- 如何给网页标题栏上添加图标(favicon.ico)(转)
如何给网页标题栏上添加图标(favicon.ico) favicon.ico详解: favicon是Favorites Icon的缩写,favicon.ico是指显示在浏览器收藏夹.地址栏 ...
- 让网站不去请求favicon.ico图标
让网站不去请求favicon.ico图标 favicon.ico 图标用于收藏夹图标和浏览器标签上的显示,如果不设置,浏览器会请求网站根目录的这个图标,如果网站根目录也没有这图标会产生 404.出于优 ...
- 网站图标 favicon.ico
默认情况下,浏览器访问一个网站的时候,同时还会向服务器请求“/favicon.ico”这个URL,目的是获取网站的图标. 若没有配置的话,Django就会返回一个404错误,并且浏览器接收到这个404 ...
- 如何给网页标题栏上添加图标(favicon.ico)
favicon.ico详解: favicon是Favorites Icon的缩写,favicon.ico是指显示在浏览器收藏夹.地址栏和标签标题前面的个性化图标. 设置步骤: 1. 把做好的f ...
- 站点图标favicon.ico
favicon.ico图标: 网站的favicon.ico需要一次额外的http请求,无论你是否有在html里面添加 link链接 <link rel="shortcut icon&q ...
- 网页左上角图标 favicon.ico
显示网页左上角标志图标 <link rel="shortcut icon" type="image/x-icon" href="images/f ...
- 为网站添加一个图标icon
<link rel="icon" href="/favicon.ico" type="image/x-icon"/> <l ...
- link标签实现给网页标题前加一个小图标favicon.ico
使用方法如下:1.<link rel="shortcut icon " type="images/x-icon" href="./favicon ...
随机推荐
- iOS开发——技术精华Swift篇&Swift 2.0和Objective-C2.0混编之第三方框架的使用
swift 语言是苹果公司在2014年的WWDC大会上发布的全新的编程语言.Swift语言继承了C语言以及Objective-C的特性,且克服了C语言的兼容性问题.Swift语言采用安全编程模式,且引 ...
- 【重走Android之路】【路线篇(一)】路线图
总结归纳了J2SE和Android的知识点,自己制订了一套详细的路线图,其中肯定有考虑不全和不合适的地方,欢迎各位大牛批评指正. 详细路线图如下:
- CentOS软件安装目录查找
注:一般的软件的默认安装目录在/usr/local或者/opt里,可以到那里去找找. 指令名称:whereis 功能介绍:在特定目录中查找符合条件的文件.这些文件的烈性应属于原始代码,二进制文件,或是 ...
- 获取CentOS系统详情的九个uname命令实例
当你在控制台模式下,无法通过“鼠标右键 > 关于”获取操作系统的信息.这时,在Linux下,你可以使用uname命令,帮助你完成这些工作. Uname是unix name的缩写.在控制台中实际使 ...
- dwz ie10一直提示数据加载中
dwz js资源jquery.validate.js 搜索 this.attr('novalidate', 'novalidate'); 在33行左右 用if (typeof (Worker) !== ...
- java.lang.ClassNotFoundException: org.springframework.context.event.GenericApplicationListener ----good
Caused by: java.lang.NoClassDefFoundError: org/springframework/context/event/GenericApplicationListe ...
- PCB阻抗调节
在PCB厂家调节的阻抗指的是:传输线的“特征阻抗”,反映传输线上所走“行波”某点的电压和电流的比值,与线长无关.传输线本身的特性. 线宽:反比 介质厚度:正比
- VS2015中添加新建项,找不到ado .net entity datamodel的解决方法
http://stackoverflow.com/questions/23046081/missing-ado-net-entity-data-model-on-visual-studio-2013 ...
- [CFgym101061C]Ramzi(贪心,双条件最短路)
题目链接:http://codeforces.com/gym/101061/problem/C 题意:一张图,图上的边有两种,一种是车道,一种是人行道.一个人要从A点到B点,可以坐车也可以走人行道.这 ...
- STL笔记(3) copy()之绝版应用
STL笔记(3) copy()之绝版应用 我选用了一个稍稍复杂一点的例子,它的大致功能是:从标准输入设备(一般是键盘)读入一些整型数据,然后对它们进行排序,最终将结果输出到标准输出设备(一般是显示器屏 ...