我是靠谱客的博主 冷静鸡,这篇文章主要介绍spring-session自定义序列化方式,现在分享给大家,希望可以做个参考。

spring-session自定义序列化

spring-session默认采用jdk序列化方法,该方法效率低下、内存占用大,且需要额外修改代码。故需要自定义序列化方法

自定义序列方法使用jackson库

首先需要一个类作为序列化的工具,需要实现

RedisSerializer

该接口在反序列化时没有提供对应的class对象,因此使用jackson反序列化时,都会返回成Object对象

因此我的解决思路是,在序列化时,获取对应bean的class,将其和bean序列化后的结果一并返回,存入redis

反序列化时,首先将byte数组转化成字符串,在从中截取存入的class字符串,作为参数传入jackson反序列化方法中

问题:对于带有泛型的bean,无法将其转化成真正适合的类型

解决方案:对于list,map,set等集合类,获取其第一个元素的class,存入redis

缺点:要求集合类元素必须是同一个子类,不能来自于同一个父类

问题:spring-session在删除attribute时,并没有真正从redis中删除,只是将value置为null,此时也会调用该类做序列化

解决方案:查看spring-session源码得知,对于null值得处理方法为直接返回一个个数为0的byte数组,反序列化时直接返回null即可

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import cn.nsu.edu.web.four.config.BaseStatic; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.data.redis.serializer.SerializationUtils; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class SessionSerializer implements RedisSerializer<Object> { @Autowired private ObjectMapper mapper; private Logger logger = LoggerFactory.getLogger(getClass()); private final String separator = "="; private final String classPrefix = "<"; private final String classSuffix = ">"; private final String classSeparator = ","; private Pattern pattern; public SessionSerializer() { pattern = Pattern.compile("<(.*)>"); } /** * 获取class,包含集合类的泛型 * <p>暂只支持最多两个泛型,同时集合内数据必须为同一个实现类,不可将泛型声明成父类</p> * * @param obj 将要序列化的对象 * @return 没有泛型,形式为java.lang.String<> * <p>一个泛型,形式为java.lang.String<java.lang.String></p> * <p>两个个泛型,形式为java.lang.String<java.lang.String,java.lang.String></p> */ private String getBegin(Object obj) { StringBuilder builder = new StringBuilder(obj.getClass().toString().substring(6) + classPrefix); if (obj instanceof List) { List list = ((List) obj); if (!list.isEmpty()) { Object temp = list.get(0); builder.append(temp.getClass().toString().substring(6)); } } else if (obj instanceof Map) { Map map = ((Map) obj); Iterator iterator = map.keySet().iterator(); if (iterator.hasNext()) { Object key = iterator.next(); Object value = map.get(key); builder.append(key.getClass().toString().substring(6)).append(classSeparator).append(value.getClass().toString().substring(6)); } } else if (obj instanceof Set) { Set set = ((Set) obj); Iterator iterator = set.iterator(); if (iterator.hasNext()) { Object value = iterator.next(); builder.append(value.getClass().toString().substring(6)); } } builder.append(classSuffix); return builder.toString(); } @Override public byte[] serialize(Object o) throws SerializationException { if (o == null) return new byte[0]; try { String builder = getBegin(o) + separator + mapper.writeValueAsString(o); return builder.getBytes(BaseStatic.CHARSET); } catch (UnsupportedEncodingException | JsonProcessingException e) { e.printStackTrace(); } return null; } @Override public Object deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length == 0) return null;//已被删除的session try { String temp = new String(bytes, BaseStatic.CHARSET); String cl[] = getClass(temp); if (cl == null) { throw new RuntimeException("错误的序列化结果=" + temp); } if (cl.length == 1) { return mapper.readValue(temp.substring(temp.indexOf(separator) + 1), Class.forName(cl[0])); } else if (cl.length == 2) { TypeFactory factory = mapper.getTypeFactory(); JavaType type = factory.constructParametricType(Class.forName(cl[0]), Class.forName(cl[1])); return mapper.readValue(temp.substring(temp.indexOf(separator) + 1), type); } else if (cl.length == 3) { TypeFactory factory = mapper.getTypeFactory(); JavaType type = factory.constructParametricType(Class.forName(cl[0]), Class.forName(cl[1]), Class.forName(cl[2])); return mapper.readValue(temp.substring(temp.indexOf(separator) + 1), type); } } catch (ClassNotFoundException | IOException e) { e.printStackTrace(); } return null; } /** * 解析字符串,获取class * <p>一个类型,java.lang.String<>={}</p> * <p>两个类型,后面为泛型,java.lang.String<java.lang.String>={}</p> * <p>三个类型,后面为泛型,java.lang.String<java.lang.String,java.lang.String>={}</p> * * @param value 包含class的字符串 * @return 返回所有类的数组 */ private String[] getClass(String value) { int index = value.indexOf(classPrefix); if (index != -1) { Matcher matcher = pattern.matcher(value.subSequence(index, value.indexOf(classSuffix) + 1)); if (matcher.find()) { String temp = matcher.group(1); if (temp.isEmpty()) {//没有泛型 return new String[]{value.substring(0, index)}; } else if (temp.contains(classSeparator)) {//两个泛型 int nextIndex = temp.indexOf(classSeparator); return new String[]{ value.substring(0, index), temp.substring(0, nextIndex), temp.substring(nextIndex + 1) }; } else {//一个泛型 return new String[]{ value.substring(0, index), temp }; } } } return null; } }

配置spring-session序列化

在之前的配置文件上进行修改

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<bean id="springSessionDefaultRedisSerializer" class="cn.nsu.edu.web.four.session.serializer.SessionSerializer"/> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <property name="hostName" value="${redis.host}"/> <property name="port" value="${redis.port}"/> <property name="timeout" value="${redis.timeout}"/> <property name="password" value="${redis.password}"/> <property name="database" value="${redis.database}"/> <property name="usePool" value="true"/> <property name="poolConfig" ref="redisPoolConfig"/> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"/> <property name="defaultSerializer" ref="springSessionDefaultRedisSerializer"/> <!--指定序列化类--> <property name="valueSerializer" ref="springSessionDefaultRedisSerializer"/> <property name="hashValueSerializer" ref="springSessionDefaultRedisSerializer"/> </bean>

spring-session序列化问题排查

严重: Servlet.service() for servlet [spring] in context with path [/] threw exception
org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.mogoroom.service.vo.criteria.QueryBSPromotionListVO]
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:52)
at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:146)
at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:128)
at org.springframework.data.redis.core.DefaultBoundHashOperations.putAll(DefaultBoundHashOperations.java:85)
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveDelta(RedisOperationsSessionRepository.java:778)
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.access$000(RedisOperationsSessionRepository.java:670)
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:388)
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:245)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:245)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:217)
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:170)
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.mogoroom.service.vo.criteria.QueryBSPromotionListVO]
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:67)
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:34)
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:50)
... 29 more
Caused by: java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.mogoroom.service.vo.criteria.QueryBSPromotionListVO]
at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:41)
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:62)
... 31 more

问题

spring session 异常信息没有打印到日志中 用是默认jdk序列化。由于实体没有序列话,导致异常,但是没有输入到日志,导致定位到问题。

在代码中 request.getSession().setAttribute()是不会出现异常的 spring session 一次请求返回的时候,才会commit,才会触发spring session提交。

代码如下:onResponseCommitted

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/** * Allows ensuring that the session is saved if the response is committed. * * @author Rob Winch * @since 1.0 */ private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper { private final SessionRepositoryRequestWrapper request; /** * Create a new {@link SessionRepositoryResponseWrapper}. * @param request the request to be wrapped * @param response the response to be wrapped */ SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) { super(response); if (request == null) { throw new IllegalArgumentException("request cannot be null"); } this.request = request; } @Override protected void onResponseCommitted() { this.request.commitSession(); } } OnCommittedResponseWrapper abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper { /** * Calls <code>onResponseCommmitted()</code> with the current contents as long as * {@link #disableOnResponseCommitted()} was not invoked. */ private void doOnResponseCommitted() { if (!this.disableOnCommitted) { onResponseCommitted(); disableOnResponseCommitted(); } } }

doOnResponseCommitted相关依赖出发方法

解决方法

filter抓下日志

复制代码
1
2
3
4
chain.doFilter(wrappedRequest, response); }catch (Exception ex){ logger.error("xxf",ex); }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持靠谱客。

最后

以上就是冷静鸡最近收集整理的关于spring-session自定义序列化方式的全部内容,更多相关spring-session自定义序列化方式内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(148)

评论列表共有 0 条评论

立即
投稿
返回
顶部