前言
调试之前请先关闭 Favicon 配置
- spring:
- ????favicon:
- ??????enabled: false
不然会发现有 2 个请求 (如果用 nginx+ 浏览器调试的话)
序列化工具类 [fastjson 版本 1.2.37]
- ```public class FastJson2JsonRedisSerializerimplements RedisSerializer{
- ????public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
- ????private Classclazz;
- ????public FastJson2JsonRedisSerializer(Classclazz) {
- ????????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 中会发现已经丢失了所有属性 `
- ![Image [1].PNG](https://upload-images.jianshu.io/upload_images/231328-ab9c9ca3c2b43710.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
- #### 查看 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 FastJson2JsonRedisSerializerimplements RedisSerializer{
- ????private Classclazz;
- ????public FastJson2JsonRedisSerializer(Classclazz) {
- ????????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
- `***
- ![Image [2].PNG](https://upload-images.jianshu.io/upload_images/231328-900964ebbd4757e2.png?imageMogr2/auto-orient/strip|imageView2/2/w/900)
- ### 方案二:
*** 继承 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 里获取, 暂未实现测试)
来源: http://www.bubuko.com/infodetail-2991578.html