Spring Boot distributed systems extended practice [1] shiro + redis realize session sharing, simplesession deserialization failed to locate the problem and reflective improvement ...

Original link: http://www.cnblogs.com/Halburt/p/10552582.html

Foreword

Turn off Favicon configuration before commissioning

spring:
    favicon:
      enabled: false

Or you'll find two requests (if using nginx + browser debugging words)
Image.png

Serialization Tools [fastjson version 1.2.37]

```public class FastJson2JsonRedisSerializer implements RedisSerializer {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class clazz;

    public FastJson2JsonRedisSerializer(Class 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中会发现已经丢失了所有属性`

![Image [1].png](https://upload-images.jianshu.io/upload_images/231328-ab9c9ca3c2b43710.png?imageMogr2/auto-orient/strip%7CimageView2/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 FastJson2JsonRedisSerializer implements RedisSerializer {
    private Class clazz;
    public FastJson2JsonRedisSerializer(Class 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的方法如下:

/**

  • Serialized object
  • @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;
    }

/**

  • Deserialized object
  • @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%7CimageView2/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();
        }
// If failure deserialization process proceeds json
        return JSONObject.parseObject (value);
    }

`` `
After such a treatment can be shared across all system cache
The only drawback is too complex, may lead to modifications of other systems lead to deserialization failure (or discuss this again next experiment, because there is such a complicated effort, we can consider by JWT)

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

Reproduced in: https: //www.cnblogs.com/Halburt/p/10552582.html

Guess you like

Origin blog.csdn.net/weixin_30521161/article/details/94785384