前言

调试之前请先关闭Favicon配置

spring:
    favicon:
      enabled: false

不然会发现有2个请求(如果用nginx+ 浏览器调试的话)

序列化工具类【fastjson版本1.2.37】


    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");     private Class<T> clazz;     public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }     @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }     @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);         return (T) JSON.parseObject(str, clazz);     }
}

org.apache.shiro.session.mgt.SimpleSession存储到redis中会发现已经丢失了所有属性

查看SimpleSession源码:

public class SimpleSession implements ValidatingSession, Serializable {

    private transient Serializable id;
    private transient Date startTimestamp;
    private transient Date stopTimestamp;
    private transient Date lastAccessTime;
    private transient long timeout;
    private transient boolean expired;
    private transient String host;
    private transient Map<Object, Object> attributes;
/* Serializes this object to the specified output stream for JDK Serialization.
*
* @param out output stream used for Object serialization.
* @throws IOException if any of this object's fields cannot be written to the stream.
* @since 1.0
*/
private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();
    short alteredFieldsBitMask = getAlteredFieldsBitMask();
    out.writeShort(alteredFieldsBitMask);
    if (id != null) {
        out.writeObject(id);
    }
    if (startTimestamp != null) {
        out.writeObject(startTimestamp);
    }
    if (stopTimestamp != null) {
        out.writeObject(stopTimestamp);
    }
    if (lastAccessTime != null) {
        out.writeObject(lastAccessTime);
    }
    if (timeout != 0l) {
        out.writeLong(timeout);
    }
    if (expired) {
        out.writeBoolean(expired);
    }
    if (host != null) {
        out.writeUTF(host);
    }
    if (!CollectionUtils.isEmpty(attributes)) {
        out.writeObject(attributes);
    }
} /*
* Reconstitutes this object based on the specified InputStream for JDK Serialization.
*
* @param in the input stream to use for reading data to populate this object.
* @throws IOException            if the input stream cannot be used.
* @throws ClassNotFoundException if a required class needed for instantiation is not available in the present JVM
* @since 1.0
*/
@SuppressWarnings({"unchecked"})
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

发现transient修饰,所以Fastjson不会对这些transient属性进行持久化,所以有了方案二,重写可以json序列化的对象

同时发现有writeObject()方法写着“ Serializes this object to the specified output stream for JDK Serialization.”,

所以有了方案一,修改序列化工具( 默认使用JdkSerializationRedisSerializer,这个序列化模式会将value序列化成字节码)

问题我们就好对症下药了

方案一:

修改序列化工具类 (这个方式其实有问题

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    private Class<T> clazz;
    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }
    @Override
    public byte[] serialize(T t) {
        return ObjectUtils.serialize(t);
    }
    @Override
    public T deserialize(byte[] bytes) {
        return (T) ObjectUtils.unserialize(bytes);
    }
}

ObjectUtils的方法如下:

/**
* 序列化对象
* @param object
* @return
*/
public static byte[] serialize(Object object) {
   ObjectOutputStream oos = null;
   ByteArrayOutputStream baos = null;
   try {
      if (object != null){
         baos = new ByteArrayOutputStream();
         oos = new ObjectOutputStream(baos);
         oos.writeObject(object);
         return baos.toByteArray();
      }
   } catch (Exception e) {
      e.printStackTrace();
   }
   return null;
} /**
* 反序列化对象
* @param bytes
* @return
*/
public static Object unserialize(byte[] bytes) {
   ByteArrayInputStream bais = null;
   try {
      if (bytes != null && bytes.length > 0){
         bais = new ByteArrayInputStream(bytes);
         ObjectInputStream ois = new ObjectInputStream(bais);
         return ois.readObject();
      }
   } catch (Exception e) {
      e.printStackTrace();
   }
   return null;
}

此方案会严重依赖对象class,如果反序列化时class对象不存在则会报错 修改为: JdkSerializationRedisSerializer

方案二:

继承SimpleSession并重写

让相关的字段可以被序列化(不被transient修饰)

重写之后一定要重写SessionManager里的方法

@Override
protected Session newSessionInstance(SessionContext context) {
SimpleSession session = new MyRedisSession(context.getHost());
// session.setId(IdGen.uuid());
session.setTimeout(SessionUtils.SESSION_TIME);
return session;
}

由方案二引发的另一个问题就是:

在微服务开发过程中,为了使用方便经常会将频繁访问的信息如用户、权限等放置到SESSION中,便于服务访问,而且,微服务间为了共享SESSION,通常会使用Redis共享存储。但是这样就会有一个问题,在封装Request对象时会将当前SESSION中所有属性对象反序列化,反序列化都成功以后,将SESSION对象生成。如果有一个微服务将本地的自定义Bean对象放置到SESSION中,则其他微服务都将出现反序列化失败,请求异常,服务将不能够使用了,这是一个灾难性问题。

以下是为了解决下面问题提出来的一种思路。

反序列化失败在于Attribute中添加了复杂对象,由此推出以下解决方案:

  1. 将复杂对象的(即非基本类型的)Key进行toString转换(转换之后再MD5缩减字符串,或者用类名代替)
  2. 将复杂对象的(即非基本类型的)Value进行JSON化(不使用不转换的懒加载模式)

注意: 日期对象的处理(单独处理)

  /**
     * 通过类型转换,将String反序列化成对象
     * @param key
     * @param value
     * @return
     */
    public Object getObjectValue(String key,String value){
        if(key == null || value == null){
           return null;
        }
        String clz = key.replace(FLAG_STR,"");
        try {
           Class aClass = Class.forName(clz);
           if(aClass.equals(Date.class)){
               return DateUtils.parseDate(value);
           }
          return   JSONObject.parseObject(value,aClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
//        如果反序列化失败就进行json化处理
        return JSONObject.parseObject(value);
    }

经过如此处理可以在所有系统里共享缓存

唯一缺点就是太复杂了,可能引起其他系统的修改导致反序列化失败(这个下次再讨论或者实验,因为有这么复杂的功夫,就可以考虑用JWT)

还有一种方案是将复杂对象放到redis中去,实行懒加载机制(不用的复杂对象,不从redis里获取,暂未实现测试)

Spring Boot分布式系统实践【扩展1】shiro+redis实现session共享、simplesession反序列化失败的问题定位及反思改进的更多相关文章

  1. Spring Boot分布式系统实践【1】-架构设计

    前言 [第一次尝试去写一个系列,肯定会有想不到的地方,欢迎大家留言指正] 本系列将介绍如果从零构建一套分布式系统.同时也是对自己过去工作的一个梳理过程. 本文先整理出构建系统的主要技术选型,以及技术框 ...

  2. Spring Boot分布式系统实践【基础模块构建3.3】注解轻松实现操作日志记录

    日志注解 前言 spring切面的编程,spring中事物处理.日志记录常常与pointcut相结合 * * Pointcut 是指那些方法需要被执行"AOP",是由"P ...

  3. Spring Boot分布式系统实践【2】-框架搭建

    前言 技术选型已经做完,那就来搭建框架了. 首先基于mvc思想,设计这套框架也是基于此,也会设计Dao层.Service层.Controller层.视图层等,同时也要考虑到dubbo的调用原理.   ...

  4. shiro+redis实现session共享

    shiro配置内容

  5. spring-session+Redis实现Session共享

    关于session共享的方式有多种: (1)通过nginx的ip_hash,根据ip将请求分配到对应的服务器 (2)基于关系型数据库存储 (3)基于cookie存储 (4)服务器内置的session复 ...

  6. Spring Boot 2 实践记录之 使用 ConfigurationProperties 注解将配置属性匹配至配置类的属性

    在 Spring Boot 2 实践记录之 条件装配 一文中,曾经使用 Condition 类的 ConditionContext 参数获取了配置文件中的配置属性.但那是因为 Spring 提供了将上 ...

  7. spring boot集成redis实现session共享

    1.pom文件依赖 <!--spring boot 与redis应用基本环境配置 --> <dependency> <groupId>org.springframe ...

  8. Spring Boot 2 实践记录之 封装依赖及尽可能不创建静态方法以避免在 Service 和 Controller 的单元测试中使用 Powermock

    在前面的文章中(Spring Boot 2 实践记录之 Powermock 和 SpringBootTest)提到了使用 Powermock 结合 SpringBootTest.WebMvcTest ...

  9. Spring Boot 2 实践记录之 MyBatis 集成的启动时警告信息问题

    按笔者 Spring Boot 2 实践记录之 MySQL + MyBatis 配置 中的方式,如果想正确运行,需要在 Mapper 类上添加 @Mapper 注解. 但是加入此注解之后,启动时会出现 ...

随机推荐

  1. 跟大家谈一谈:涛舅舅家的微信域名检测api的心路历程

    微信域名检测,这是近一年来兴起来的一种网络服务,可以通过api接口来对域名进行批量检测,以确认该域名有没有被微信拦截(见红),然后通过编程来实现域名切换保障链接可以正常打开. 涛舅舅工作室从事微信域名 ...

  2. APIO2018 被屠记

    占坑 day0 10:40才起床 感觉一点也不好 下午去了趟80中拿牌子然而没有到,白浪费我颓废时间. day0.5 早上第一课讲二分凸优化,有点瞌睡 第二课讲匹配相关,感觉这篇文章涵盖了大部分内容 ...

  3. 1064 Financial Management

    http://acm.hdu.edu.cn/showproblem.php?pid=1064 思路:看懂英文就很简单,就是12个数相加求平均数就ok了. 扩展: C++ 标准输入输出流的控制符 #in ...

  4. Java 平时作业四

    编写一个Java程序实现返回指定目录及其子目录下扩展名为*.pdf的所有文件名. 扩展: isFile public boolean isFile() 测试此抽象路径名表示的文件是否为普通文件. 如果 ...

  5. 如何使用$.each()与$().each()以及他们的区别

    1.首先,说下$.each(Arry/Object,function(index,val){ //index表示下标,val表示下标对应的值 }) 下面是使用$.each()的几种类型,其中arr2与 ...

  6. nginx配置前端代理

    #user nobody;worker_processes 1; #error_log logs/error.log;#error_log logs/error.log notice;#error_l ...

  7. spring-cloud-gateway负载普通web项目

    spring-cloud-gateway负载普通web项目 对于普通的web项目,也是可以通过spring-cloud-gateway进行负载的,只是无法通过服务发现. 背景 不知道各位道友有没有使用 ...

  8. C语言实型常量

    实型常量又称实数或浮点数.在C语言中可以用两种形式来表示一个实型常量. 一.小数形式 小数形式的实型常量由两部分组成:数字和小数点.如:0.12.12...12都是合法的实型常量. 二.指数形式 在C ...

  9. Java后台使用Websocket教程

    在开发一个项目的时候使用到了WebSocket协议 什么是WebSocket? WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket A ...

  10. ip锁死怎么设置ip地址

    单击电脑网络连接图标,打开网络和共享中心   点击本地连接   点击详细信息,即可看到IP地址.子网掩码.默认网关.DNS服务器信息   再点击本地连接状态下的属性   找到Internet 协议版本 ...