为网站添加网址图标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 namedspringSessionDefaultRedisSerializer
that 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 ...
随机推荐
- Linux Shell学习
https://yunpan.cn/cMxw3i8TkcsWI (提取码:d4e1)
- 如何搭建配置php开发环境
PHP的配置 1.打开解压后的C:\Program Files (x86)\php-5.3.5文件夹 1 将php.ini-development文件并更改名称为php.ini(留个备份,好习惯) 2 ...
- HtmlAgilityPackage XPath学习
最近的开发中要用到htmlAgilityPackage, 所以记录一下XPath相关知识! XPath 简介 XPath 是一门在 XML 文档中查找信息的语言.XPath 可用来在 XML 文档中对 ...
- Linux功能-验证网络配置
显示IP地址.设别和MAC地址等信息# ip addr show br0 订正:以下截图中硬件mac地址为第三行的"link/ether"一行所在的地方. ip命令可用在网络性能方 ...
- oracle服务、客户端 plsql配置
1.oracle服务端安装 Oracle 9i 的安装(图解) 2.oracle客户端安装 http://wenku.baidu.com/view/8be28581f524ccbff0218427.h ...
- spring路径通配符
在应用Spring的工程中,使用class path的方式加载配置文件应该是最常用的做法,然而对大部分人来说,刚开始使用Spring时,几乎都碰到过加载配置文件失败的情况,除了配置上的错误外,很多时候 ...
- 动态库加载出错,cannot restore segment prot after reloc: Permission denied
转自:taolinke的博客 项目中碰到的问题,编译好的so文件,放到其他机器上去加载,报了错误,cannot restore segment prot after reloc: Permission ...
- OpenJDK和OracleJDK的JVM性能有多大差距
首先要先明确OpenJDK和Sun/OracleJDK之间,以及OpenJDK 6.OpenJDK 7.OpenJDK 7u和OpenJDK 8等项目之间是什么关系,这有助于确定接下来编译要使用的JD ...
- [HIHO1328]逃离迷宫(bfs,位压)
题目链接:http://hihocoder.com/problemset/problem/1328 这个题bfs到时候不止要存当前的坐标,还要存当前有哪几把钥匙.因为5把钥匙,所以可以直接用位来存,这 ...
- hibernate.cfg.xml文件的说明
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hi ...