不知道有没有人会问这个序列化它,能有什么用。
假如你和我一样从一个有历史背景的项目中 “脱颖而杀出一条血路” 的话,你可能会觉得这个有用【笑哭了的表情】
项目用的 Shiro,权限 + session 强绑定的技术背景,应用服务通过依赖 shiro sdk 而多点访问 redis 读取session 的业务现状(redisSessionDao 技术兄弟们对这个可能不陌生),多点访问 redis 意味着session 协议升级,与应用服务迭代的相互影响,以及可能会存在小机率的事务性问题(说到这终于想起来为什么经常有人反馈些,顽固的、莫名其妙的、用户状态问题),
直入技术要害:默认的 session 叫 SimpleSession ,它有几个属性是 transient 修饰的,贴出来方便交流:
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;
受 transient 的影响 ,所以如果我们想把 SimpleSession 直接交给 feignclient 去请求传输是行不通的, feignclient 默认的序列化(json序列化)这关就过不了,我们还得借助 org.crazycake.shiro.serializer.ObjectSerializer 来实现序列化,以下是代码片断:
private RedisSerializer valueSerializer = new ObjectSerializer();
public SessionResult readSession(@RequestBody SessionReq req) { SessionResult res = new SessionResult<>(); try { Session session = redisSessionDAO.readSession((Serializable)req.getSid()); byte[] serialize = valueSerializer.serialize(session); String base64str = Base64.getEncoder().encodeToString(serialize); res.setData(base64str); } catch (UnknownSessionException e) { // do sth... } catch (SerializationException e) { // do sth... } log.debug("auth 返回:::::: {}", res); return res; }
如果是用的 feignclient 的话,还需要对指定的 feignclient 做点 config 本文就不展开了。
能把 Session 的完整信息通过网络进行传输, 序列化的问题就解决掉了,当然这样做也有缺点比如效率问题等,最终我也不会让这种状态一直存在,会通过架构升级来解决这些问题。当前这个方案其实还有进一步优化空间,假如架构角度没有风险的话,可以直接从 redis 读出 byte[] 数组,再进行 base64 ,这点我们可以读下 RedisSessionDao 的源码看看它是怎么处理的,实际上 shiro 从 redis 中拿 session,也是用的 org.crazycake.shiro.serializer.ObjectSerializer 做的序列化和反序列化【哈哈笑的表情】:
protected Session doReadSession(Serializable sessionId) { if (sessionId == null) { logger.warn("session id is null"); return null; } Session s = getSessionFromThreadLocal(sessionId); if (s != null) { return s; } logger.debug("read session from redis"); try { s = (Session) valueSerializer.deserialize(redisManager.get(keySerializer.serialize(getRedisSessionKey(sessionId)))); setSessionToThreadLocal(sessionId, s); } catch (SerializationException e) { logger.error("read session error. settionId=" + sessionId); } return s; }
就标红的这一行,基本上已经说明我们的问题了。 valueSerializer 就是一个 org.crazycake.shiro.serializer.ObjectSerializer 。redisManager 得到的是 session 序列化后的 byte[] 数组。
稍微贴下它 ObjectSerializer 的源码一探究竟何方神圣:
@Override public byte[] serialize(Object object) throws SerializationException { byte[] result = new byte[0]; if (object == null) { return result; } ByteArrayOutputStream byteStream = new ByteArrayOutputStream(BYTE_ARRAY_OUTPUT_STREAM_SIZE); if (!(object instanceof Serializable)) { throw new SerializationException("requires a Serializable payload " + "but received an object of type [" + object.getClass().getName() + "]"); } try { ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); objectOutputStream.writeObject(object); objectOutputStream.flush(); result = byteStream.toByteArray(); } catch (IOException e) { throw new SerializationException("serialize error, object=" + object, e); } return result; } @Override public Object deserialize(byte[] bytes) throws SerializationException { Object result = null; if (bytes == null || bytes.length == 0) { return result; } try { ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteStream); result = objectInputStream.readObject(); } catch (IOException e) { throw new SerializationException("deserialize error", e); } catch (ClassNotFoundException e) { throw new SerializationException("deserialize error", e); } return result; }
大呼上当,也没什么神奇的也就是 ByteArrayOutputStream + ObjectOutputStream 而已。。
可能也有人会说为什么不壮士断碗,直接把权限弄到 gateway 里面去。本文就不讨论了,有条件能一步到位的还是鼓励一步到位。