|
1、错误描述:
(1)通过 redis 缓存 shiro 的 session,取值的时候 session 内容为 null
创建时
|
读取时:
|
redis 中读取到的值,可以看到里面空荡荡的:
因为 shiro 的 Session 是一个 SimpleSession 类,其中属性用 transient 修饰,即不能被序列化
|
使用默认的序列化方式
1 2 3 4 5 6 7 8 9 10 11 |
p:connectionFactory-ref="jedisConnectionFactory" p:keySerializer-ref="ss" p:hashKeySerializer-ref="ss" p:hashValueSerializer-ref="fastJson" p:stringSerializer-ref="ss" p:valueSerializer-ref="fastJson"/>
p:connectionFactory-ref="jedisConnectionFactory"/> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class RedisTemplate //。。。 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; }
} |
1)类结构:主要是这两个方法(序列化、反序列化)
2)查看其构造函数,找到真正序列化的类
|
3)进入到 SerializingConverter 类中查看序列化过程
说明:这里采用字节数组流的方式(还是有点小疑惑,为什么字节数组流可以)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class SerializingConverter implements Converter //。。。
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); } }
} |
那就是 sessionId 在使用 默认的 JdkSerializationRedisSerializer 序列化 的时候,会在头部和加上一串不明字符,导致在配置文件中开启监听session(
1) getActiveSessions()
1 2 3 4 5 6 7 8 |
public Collection //从redis获取全部或部分session System.out.println("get all session"); Set keys = redisTemplate2.keys("session*"); System.out.println(keys.size()); List return sessions; } |
2)redis 中的 sessionId 数据
在 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); } |
给 JdkSerializationRedisSerializer 指定 key 的序列化方式,即指定 String 和 Hash 的序列化方式,必须都知道,否则会出错。后面又测了,发现不指定 HashKey 序列化方式也不会错,但是之前就不行(如果与相同情况可只指定 String)
1 2 3 4 5 |
p:connectionFactory-ref="jedisConnectionFactory" p:keySerializer-ref="ss" p:hashKeySerializer-ref="ss"/> |
(1)JdkSerializationRedisSerializer 中使用的序列化方式
|
|
(2)定义的类
|
(3)普通流序列化
(4)字节数组流
|
(5)思考:为什么会出现这种情况,JdkSerializationRedisSerializer 序列化也是这样写的,决定跟一下源码
1)首先进入到 ObjectOutputStream 类的 writeObject()
|
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 } } |
根据上面的源码,可以看出它是将属性字段分别序列化,疑惑烟消云散。
(小弟写的不好或者不正确的地方,请原谅并斧正,小弟会尽快修改的)