spring-session自定义序列化方式

目录
  • spring-session自定义序列化
    • 自定义序列方法使用jackson库
    • 配置spring-session序列化
  • 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<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序列化

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

 <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

 /**
  * 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抓下日志

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

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

(0)

相关推荐

  • spring-redis-session 自定义 key 和过期时间

    对于分布式应用来说,最开始遇到的问题就是 session 的存储了,解决方案大致有如下几种 使用 spring-session 它可以把 session 存储到你想存储的位置,如 redis,mysql 等 使用 JWTs ,它使用算法来验证 token 的合法性,是否过期,并且 token 无法被伪造,信息也是无法被篡改的 本文内容主要说 spring-session 使用 redis 来存储 session ,实现原理,修改过期时间,自定义 key 等 spring-session 对于内部

  • Spring Session实现分布式session的简单示例

    前面有用 tomcat-redis-session-manager来实现分布式session管理,但是它有一定的局限性,主要是跟tomcat绑定太紧了,这里改成用Spring Session来管理分布式session,Spring Session就完全实现了与具体的容器无关,如果需要了解如何用tomcat-redis-session-manager实现分分布式session,请看我之前的文章,下面正式进入主题,Spring Session项目搭建. 1. 引入Spring Session mav

  • 浅谈SpringMVC中的session用法及细节记录

    前言 初学SpringMVC,最近在给公司做的系统做登录方面,需要用到session. 在网上找了不少资料,大致提了2点session保存方式: 1.javaWeb工程通用的HttpSession 2.SpringMVC特有的@SessionAttributes 我个人比较关注@SessionAttributes的用法,毕竟现在是在用SpringMVC嘛.但是我看网上那些文章,基本都是只说明了基础用法,详细的使用和细节却基本没有,我想这是不够的,所以我自己做了一些测试,然后整理了下代码做了个de

  • spring AOP自定义注解方式实现日志管理的实例讲解

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在applicationContext-mvc.xml中要添加的 <mvc:annotation-driven /> <!-- 激活组件扫描功能,在包com.gcx及其子包下面自动扫描通过注解配置的组件 --> <context:component-scan base-package=&qu

  • 解决Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程问题

    待解决的问题 Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程 解决办法 为spring session添加springSessionRedisTaskExecutor线程池. /** * 用于spring session,防止每次创建一个线程 * @return */ @Bean public ThreadPoolTaskExecutor springSessionRedisTaskExecutor(){ T

  • spring-session自定义序列化方式

    目录 spring-session自定义序列化 自定义序列方法使用jackson库 配置spring-session序列化 spring-session序列化问题排查 问题 解决方法 spring-session自定义序列化 spring-session默认采用jdk序列化方法,该方法效率低下.内存占用大,且需要额外修改代码.故需要自定义序列化方法 自定义序列方法使用jackson库 首先需要一个类作为序列化的工具,需要实现 RedisSerializer 该接口在反序列化时没有提供对应的cla

  • 详解Spring Boot使用Maven自定义打包方式

    前言:本文将告诉你如何将程序Jar与与依赖Jar及配置文件分离打包,以下列举了两种不同Maven打包方式,其打包效果一致! 一.第一种Maven打包方式,将jar及resources下全部配置文件,拷贝到指定目录: <!--配置项--><properties> <!--自定义配置--> <project.jar.output.directory>E:/IDEAFile/file-copy/target/project</project.jar.outp

  • Spring Security OAuth 自定义授权方式实现手机验证码

    Spring Security OAuth 默认提供OAuth2.0 的四大基本授权方式(authorization_code\implicit\password\client_credential),除此之外我们也能够自定义授权方式. 先了解一下Spring Security OAuth提供的两个默认 Endpoints,一个是AuthorizationEndpoint,这个是仅用于授权码(authorization_code)和简化(implicit)模式的.另外一个是TokenEndpoi

  • SpringBoot @JsonDeserialize自定义Json序列化方式

    目录 @JsonDeserialize自定义Json序列化 1.问题 2.现象 3.解决办法 @JsonSerialize与@JsonDeserialize使用 1.以注解方式使用 2.自定义实现类 @JsonDeserialize自定义Json序列化 1.问题 在项目上使用SpringBoot为框架,调用第三方接口时,返回的参数Date类型,需要自定义进行Json序列化,需要进行处理,接受数据 2.现象 调用第三方接口,返回参数类型为Date类型,格式如下: { "created":

  • Spring Boot之Validation自定义实现方式的总结

    目录 Validation自定义实现方式 Spring Boot Validation定制 使用自定义的注解 自定义执行Validator 自定义Validation注解 场景说明 源码 总结 Validation自定义实现方式 Spring Boot Validation定制 虽然在Spring Boot中已经提供了非常多的预置注解,用以解决在日常开发工作中的各类内容,但是在特定情况仍然存在某些场景,无法满足需求,需要自行定义相关的validator.本节将针对自定义的validator进行介

  • session的存储方式和配置方法介绍

    1.Session的存储方式. session其实分为客户端Session和服务器端Session. 当用户首次与Web服务器建立连接的时候,服务器会给用户分发一个 SessionID作为标识.SessionID是一个由24个字符组成的随机字符串.用户每次提交页面,浏览器都会把这个SessionID包含在 HTTP头中提交给Web服务器,这样Web服务器就能区分当前请求页面的是哪一个客户端.这个SessionID就是保存在客户端的,属于客户端Session. 其实客户端Session默认是以co

  • spring security自定义登录页面

    在项目中我们肯定不能使用Spring自己生成的登录页面,而要用我们自己的登录页面,下面讲一下如何自定义登录页面,先看下配置 <sec:http auto-config="true"> <sec:intercept-url pattern="/app.jsp" access="ROLE_SERVICE"/> <sec:intercept-url pattern="/**" access="

  • Spring session整合到Redis过程解析

    为何要用Spring-session 在传统单机web应用中,一般使用tomcat/jetty等web容器时,用户的session都是由容器管理.浏览器使用cookie中记录sessionId,容器根据sessionId判断用户是否存在会话session.这里的限制是,session存储在web容器中,被单台服务器容器管理. 但是网站主键演变,分布式应用和集群是趋势(提高性能).此时用户的请求可能被负载分发至不同的服务器,此时传统的web容器管理用户会话session的方式即行不通.除非集群或者

随机推荐