spring-session自定義序列化方式
spring-session自定義序列化
spring-session默認(rèn)采用jdk序列化方法,該方法效率低下、內(nèi)存占用大,且需要額外修改代碼。故需要自定義序列化方法
自定義序列方法使用jackson庫(kù)
首先需要一個(gè)類作為序列化的工具,需要實(shí)現(xiàn)
RedisSerializer
該接口在反序列化時(shí)沒(méi)有提供對(duì)應(yīng)的class對(duì)象,因此使用jackson反序列化時(shí),都會(huì)返回成Object對(duì)象
因此我的解決思路是,在序列化時(shí),獲取對(duì)應(yīng)bean的class,將其和bean序列化后的結(jié)果一并返回,存入redis
反序列化時(shí),首先將byte數(shù)組轉(zhuǎn)化成字符串,在從中截取存入的class字符串,作為參數(shù)傳入jackson反序列化方法中
問(wèn)題:對(duì)于帶有泛型的bean,無(wú)法將其轉(zhuǎn)化成真正適合的類型
解決方案:對(duì)于list,map,set等集合類,獲取其第一個(gè)元素的class,存入redis
缺點(diǎn):要求集合類元素必須是同一個(gè)子類,不能來(lái)自于同一個(gè)父類
問(wèn)題:spring-session在刪除attribute時(shí),并沒(méi)有真正從redis中刪除,只是將value置為null,此時(shí)也會(huì)調(diào)用該類做序列化
解決方案:查看spring-session源碼得知,對(duì)于null值得處理方法為直接返回一個(gè)個(gè)數(shù)為0的byte數(shù)組,反序列化時(shí)直接返回null即可
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>暫只支持最多兩個(gè)泛型,同時(shí)集合內(nèi)數(shù)據(jù)必須為同一個(gè)實(shí)現(xiàn)類,不可將泛型聲明成父類</p>
*
* @param obj 將要序列化的對(duì)象
* @return 沒(méi)有泛型,形式為java.lang.String<>
* <p>一個(gè)泛型,形式為java.lang.String<java.lang.String></p>
* <p>兩個(gè)個(gè)泛型,形式為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("錯(cuò)誤的序列化結(jié)果=" + 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>一個(gè)類型,java.lang.String<>={}</p>
* <p>兩個(gè)類型,后面為泛型,java.lang.String<java.lang.String>={}</p>
* <p>三個(gè)類型,后面為泛型,java.lang.String<java.lang.String,java.lang.String>={}</p>
*
* @param value 包含class的字符串
* @return 返回所有類的數(shù)組
*/
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()) {//沒(méi)有泛型
return new String[]{value.substring(0, index)};
} else if (temp.contains(classSeparator)) {//兩個(gè)泛型
int nextIndex = temp.indexOf(classSeparator);
return new String[]{
value.substring(0, index),
temp.substring(0, nextIndex),
temp.substring(nextIndex + 1)
};
} else {//一個(gè)泛型
return new String[]{
value.substring(0, index),
temp
};
}
}
}
return null;
}
}
配置spring-session序列化
在之前的配置文件上進(jìn)行修改
<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序列化問(wèn)題排查
嚴(yán)重: 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
問(wèn)題
spring session 異常信息沒(méi)有打印到日志中 用是默認(rèn)jdk序列化。由于實(shí)體沒(méi)有序列話,導(dǎo)致異常,但是沒(méi)有輸入到日志,導(dǎo)致定位到問(wèn)題。
在代碼中 request.getSession().setAttribute()是不會(huì)出現(xiàn)異常的 spring session 一次請(qǐng)求返回的時(shí)候,才會(huì)commit,才會(huì)觸發(fā)spring session提交。
代碼如下:onResponseCommitted
/**
* 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相關(guān)依賴出發(fā)方法

解決方法
filter抓下日志
chain.doFilter(wrappedRequest, response);
}catch (Exception ex){
logger.error("xxf",ex);
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
大模型chat/completions和completions區(qū)別解析
OpenAI的completions和chat/completions是兩個(gè)不同的端點(diǎn),completions用于單次文本補(bǔ)全,而chat/completions用于多輪對(duì)話生成,選擇哪個(gè)端點(diǎn)取決于你的具體需求,本文介紹大模型chat/completions和completions區(qū)別,感興趣的朋友一起看看吧2025-03-03
SpringBoot2+Netty+WebSocket(netty實(shí)現(xiàn)websocket支持URL參數(shù))問(wèn)題記錄
Netty?是一個(gè)利用?Java?的高級(jí)網(wǎng)絡(luò)的能力,隱藏其背后的復(fù)雜性而提供一個(gè)易于使用的?API?的客戶端/服務(wù)器框架,這篇文章主要介紹了SpringBoot2+Netty+WebSocket(netty實(shí)現(xiàn)websocket,支持URL參數(shù)),需要的朋友可以參考下2023-12-12
mybatis查詢實(shí)現(xiàn)返回List<Map>類型數(shù)據(jù)操作
這篇文章主要介紹了mybatis查詢實(shí)現(xiàn)返回List<Map>類型數(shù)據(jù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
MyBatis XML方式的基本用法之多表查詢功能的示例代碼
這篇文章主要介紹了MyBatis XML方式的基本用法之多表查詢功能的示例代碼,本文通過(guò)示例文字相結(jié)合的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07
springboot vue完成發(fā)送接口請(qǐng)求顯示響應(yīng)頭信息
這篇文章主要為大家介紹了springboot+vue完成發(fā)送接口請(qǐng)求顯示響應(yīng)頭信息,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
Java數(shù)據(jù)結(jié)構(gòu)之雙向鏈表圖解
這篇文章主要為大家詳細(xì)介紹了Java數(shù)據(jù)結(jié)構(gòu)之雙向鏈表,文中圖解分析的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
解決Error occurred during initialization o
這篇文章主要介紹了解決Error occurred during initialization of VM Java虛擬機(jī)初始化失敗問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
從零搭建腳手架之集成Spring?Retry實(shí)現(xiàn)失敗重試和熔斷器模式(實(shí)戰(zhàn)教程)
在我們的大多數(shù)項(xiàng)目中,會(huì)有一些場(chǎng)景需要重試操作,而不是立即失敗,讓系統(tǒng)更加健壯且不易發(fā)生故障,這篇文章主要介紹了從零搭建開(kāi)發(fā)腳手架之集成Spring?Retry實(shí)現(xiàn)失敗重試和熔斷器模式,需要的朋友可以參考下2022-07-07
Triple協(xié)議支持Java異?;貍髟O(shè)計(jì)實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Triple協(xié)議支持Java異常回傳設(shè)計(jì)實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12

