shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑

1、先来说一下 shiro session 序列化到 redis 中的过程

shiro session 放入到 redis 中的 过程(序列化过程):
1、将 shiro 的 SimpleSession 通过 RedisTemple 模板,有两种方案,即 Jackson 和 fastJson ,都是将 Java对象 打成 json,在转成 buty[] 。
补充:在将 Java 对象 打成 Json 的过程中,就是调用我们的 get 方法,拿我们的值,拼出来一个 Json 字符串,然后将 字符串打成 byte[],

2、下面是踩坑的详情

1、错误描述:

(1)通过 redis 缓存 shiro 的 session,取值的时候 session 内容为 null

创建时

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第1张图片

 

读取时:

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第2张图片

 

redis 中读取到的值,可以看到里面空荡荡的:

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第3张图片

2、错误原因

因为 shiro 的 Session 是一个 SimpleSession 类,其中属性用 transient 修饰,即不能被序列化

 

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第4张图片

3、解决方案

(1)在 redis 配置文件中定义一个不指定序列化方式的 RedisTemplate 的 bean,

使用默认的序列化方式

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

        ="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"

                p:connectionFactory-ref="jedisConnectionFactory"

                p:keySerializer-ref="ss"

                p:hashKeySerializer-ref="ss"

                p:hashValueSerializer-ref="fastJson"

                p:stringSerializer-ref="ss"

                p:valueSerializer-ref="fastJson"/>

       

        ="redisTemplate2" class="org.springframework.data.redis.core.RedisTemplate"

              p:connectionFactory-ref="jedisConnectionFactory"/>

(2)查看一下 RedisTemplate 的源码

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

   17

   18

public class RedisTemplate extends RedisAccessor implements RedisOperations, BeanClassLoaderAware {

//。。。

private RedisSerializer defaultSerializer;

//。。。

 

//这是一个回调方法,在注入完成后被调用

public void afterPropertiesSet() {

        super.afterPropertiesSet();

        boolean defaultUsed = false;

        if (this.defaultSerializer == null) {

            this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());

        }

 

       //。。。

        this.initialized = true;

    }

 

}

(3)进入到 JdkSerializationRedisSerializer 类

1)类结构:主要是这两个方法(序列化、反序列化)

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第5张图片

2)查看其构造函数,找到真正序列化的类

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第6张图片

 

3)进入到 SerializingConverter 类中查看序列化过程

说明:这里采用字节数组流的方式(还是有点小疑惑,为什么字节数组流可以)

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

public class SerializingConverter implements Converterbyte[]> {

//。。。

 

public byte[] convert(Object source) {

        ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);

 

        try {

            this.serializer.serialize(source, byteStream);

            return byteStream.toByteArray();

        } catch (Throwable var4) {

            throw new SerializationFailedException("Failed to serialize object using " + this.serializer.getClass().getSimpleName(), var4);

        }

    }

 

}

 

4、紧接着,还会出现另一个问题:

那就是 sessionId 在使用 默认的 JdkSerializationRedisSerializer 序列化 的时候,会在头部和加上一串不明字符,导致在配置文件中开启监听session()时调用 自定义的 SessionDAO 中的 getActiveSessions()方法扫描不到 session(每个sessionID 都加有前缀 session),即使用 *session* 也不行。

1) getActiveSessions()

    1

    2

    3

    4

    5

    6

    7

    8

public Collection getActiveSessions() {

        //redis获取全部或部分session

        System.out.println("get all session");

        Set keys = redisTemplate2.keys("session*");

        System.out.println(keys.size());

        List sessions = redisTemplate2.opsForValue().multiGet(keys);

        return sessions;

    }

2)redis 中的 sessionId 数据

5、导致原因

在 IO 流的 ObjectOutputStream 类中有 ObjectOutputStream 构造,它里面有一个 writeStreamHeader(),原因就是出在这上面,它在写入我们的 key 之前往前面加了两个东西(有点恶心),,,,

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

public ObjectOutputStream(OutputStream out) throws IOException {

        verifySubclass();

        bout = new BlockDataOutputStream(out);

        handles = new HandleTable(10, (float) 3.00);

        subs = new ReplaceTable(10, (float) 3.00);

        enableOverride = false;

        writeStreamHeader();

        bout.setBlockDataMode(true);

        if (extendedDebugInfo) {

            debugInfoStack = new DebugTraceInfoStack();

        } else {

            debugInfoStack = null;

        }

    }

    1

    2

    3

    4

protected void writeStreamHeader() throws IOException {

        bout.writeShort(STREAM_MAGIC);

        bout.writeShort(STREAM_VERSION);

    }

6、解决办法

给 JdkSerializationRedisSerializer 指定 key 的序列化方式,即指定 String 和 Hash 的序列化方式,必须都知道,否则会出错。后面又测了,发现不指定 HashKey 序列化方式也不会错,但是之前就不行(如果与相同情况可只指定 String)

    1

    2

    3

    4

    5

        ="redisTemplate2" class="org.springframework.data.redis.core.RedisTemplate"

              p:connectionFactory-ref="jedisConnectionFactory"

              p:keySerializer-ref="ss"

              p:hashKeySerializer-ref="ss"/>

3、基于上面字节数组流序列化session,还是有些疑惑,来测测试一下

1、测试普通流与字节数组能否完整读到 transient 修饰的属性(由于上面的源码所以打算都测一下)

(1)JdkSerializationRedisSerializer  中使用的序列化方式

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第7张图片

 

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第8张图片

 

(2)定义的类

 

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第9张图片

(3)普通流序列化

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第10张图片

(4)字节数组流

 

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第11张图片

(5)思考:为什么会出现这种情况,JdkSerializationRedisSerializer  序列化也是这样写的,决定跟一下源码

1)首先进入到 ObjectOutputStream 类的 writeObject()

 

shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑_第12张图片

2)中间过程不说了,最后进入到 SimpleSession 类中,没错,就是 package org.apache.shiro.session.mgt; 下的 SimpleSession ,也就是我们需要序列化的那个 session,直接调用它的 writeObject() 方法

    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

/**

     * Serializes this object to the specified output stream for JDK Serialization.

     * 将此对象序列化为JDK序列化的指定输出流。

     * @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 {

   //IO 流中 ObjectOutputStream 中的一个方法

        out.defaultWriteObject();

   //判断哪些字段已经被序列化

        short alteredFieldsBitMask = getAlteredFieldsBitMask();

        //IO 流中 ObjectOutputStream 中的一个方法

      out.writeShort(alteredFieldsBitMask);

        //下面就是见证奇迹的时刻,他采用把属性分开序列化的方式达到序列化 transient 关键字修饰的字段

      //SimpleSession 属性字段上面有

   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);

        }

    }

从上面的 short alteredFieldsBitMask = getAlteredFieldsBitMask(); 进入

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

   17

   18

   19

   20

/**

 (大概意思就是判断哪些字段已经被序列化,翻译:返回序列化期间使用的位掩码,指示哪些字段已序列化

     * Returns a bit mask used during serialization indicating which fields have been serialized. Fields that have been

     * altered (not null and/or not retaining the class defaults) will be serialized and have 1 in their respective

     * index, fields that are null and/or retain class default values have 0.

     *

     * @return a bit mask used during serialization indicating which fields have been serialized.

     * @since 1.0

     */

    private short getAlteredFieldsBitMask() {

        int bitMask = 0;

        bitMask = id != null ? bitMask | ID_BIT_MASK : bitMask;

        bitMask = startTimestamp != null ? bitMask | START_TIMESTAMP_BIT_MASK : bitMask;

        bitMask = stopTimestamp != null ? bitMask | STOP_TIMESTAMP_BIT_MASK : bitMask;

        bitMask = lastAccessTime != null ? bitMask | LAST_ACCESS_TIME_BIT_MASK : bitMask;

        bitMask = timeout != 0l ? bitMask | TIMEOUT_BIT_MASK : bitMask;

        bitMask = expired ? bitMask | EXPIRED_BIT_MASK : bitMask;

        bitMask = host != null ? bitMask | HOST_BIT_MASK : bitMask;

        bitMask = !CollectionUtils.isEmpty(attributes) ? bitMask | ATTRIBUTES_BIT_MASK : bitMask;

        return (short) bitMask;

    }

3)顺便把它的读 SimpleSession 方法也写出来,可以看一下

    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

 /**

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

        in.defaultReadObject();

        short bitMask = in.readShort();

 

        if (isFieldPresent(bitMask, ID_BIT_MASK)) {

            this.id = (Serializable) in.readObject();

        }

        if (isFieldPresent(bitMask, START_TIMESTAMP_BIT_MASK)) {

            this.startTimestamp = (Date) in.readObject();

        }

        if (isFieldPresent(bitMask, STOP_TIMESTAMP_BIT_MASK)) {

            this.stopTimestamp = (Date) in.readObject();

        }

        if (isFieldPresent(bitMask, LAST_ACCESS_TIME_BIT_MASK)) {

            this.lastAccessTime = (Date) in.readObject();

        }

        if (isFieldPresent(bitMask, TIMEOUT_BIT_MASK)) {

            this.timeout = in.readLong();

        }

        if (isFieldPresent(bitMask, EXPIRED_BIT_MASK)) {

            this.expired = in.readBoolean();

        }

        if (isFieldPresent(bitMask, HOST_BIT_MASK)) {

            this.host = in.readUTF();

        }

        if (isFieldPresent(bitMask, ATTRIBUTES_BIT_MASK)) {

            this.attributes = (Map) in.readObject();

        }

    }

根据上面的源码,可以看出它是将属性字段分别序列化,疑惑烟消云散。

(小弟写的不好或者不正确的地方,请原谅并斧正,小弟会尽快修改的)

 

你可能感兴趣的:(shiro session 放入到 redis 中不被序列化以及 SimpleSession 的 transient 属性序列化方式踩到的坑)