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即可

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 {
    @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,包含集合类的泛型
     * 

暂只支持最多两个泛型,同时集合内数据必须为同一个实现类,不可将泛型声明成父类

* * @param obj 将要序列化的对象 * @return 没有泛型,形式为java.lang.String<> *

一个泛型,形式为java.lang.String

*

两个个泛型,形式为java.lang.String

*/ 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 *

一个类型,java.lang.String<>={}

*

两个类型,后面为泛型,java.lang.String={}

*

三个类型,后面为泛型,java.lang.String={}

* * @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序列化

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

 
    
        
        
        
        
        
        
         
     
 
    
        
        
        
        
        
    

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

 /**
  * 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 onResponseCommmitted() with the current contents as long as
  * {@link #disableOnResponseCommitted()} was not invoked.
  */
 private void doOnResponseCommitted() {
  if (!this.disableOnCommitted) {
   onResponseCommitted();
   disableOnResponseCommitted();
  }
 } 
} 

doOnResponseCommitted相关依赖出发方法

spring-session自定义序列化方式_第1张图片

解决方法

filter抓下日志

chain.doFilter(wrappedRequest, response); 
}catch (Exception ex){
logger.error("xxf",ex);
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

你可能感兴趣的:(spring-session自定义序列化方式)