Msgpack简介

MessagePack is an efficient binary serialization format. It lets you exchange data among multiple languages like JSON. But it's faster and smaller. Small integers are encoded into a single byte, and typical short strings require only one extra byte in addition to the strings themselves.

MessagePack是一个基于二进制高效的对象序列化Library用于跨语言通信。它可以像JSON那样,在许多种语言之间交换结构对象;但是它比JSON更快速也更轻巧。 支持Python、Ruby、Java、C/C++、Javascript等众多语言。 比Google Protocol Buffers还要快4倍。

Graddle依赖管理

    // msgpack
    compile 'org.msgpack:msgpack:0.6.12' // 依赖javassist和json-simple
    //slf-logback
    compile 'org.slf4j:jcl-over-slf4j:1.7.7'
    compile 'org.slf4j:jcl-over-slf4j:1.7.7' // 依赖lf4j-api,故lf4j-api可以不单独配置
    compile 'org.slf4j:slf4j-api:1.7.7'
    // javassist
    compile 'org.javassist:javassist:3.20.0-GA' // 如果其他有依赖,则可不单独配置
    compile 'com.googlecode.json-simple:json-simple:1.1.1'

使用Msgpack解析yii2 session中的数据

yii2将session数据写入redis中,为了在java api中可以使用后台用户登录的信息,则需要通过java解析session数据。
先看看如何使用:

// sessionBytes是byte类型
if (null != sessionBytes) {
    MessagePack pack = new MessagePack(); // new一个msgpack对象
    Map map = null;
    try {
        map = (Map) pack.read(sessionBytes);  // 通过Msgpack的read读取字节,强制转换为Map对象
    } catch (IOException e) {
        LOGGER.warn("get request user from redis failed: ", e);
    }

    if (!com.joyven.util.Utils.isEmpty(map)) {
        map.forEach((k, v) -> {
            String key = String.valueOf(k);
            String value = String.valueOf(v);
            if (Const.SESSION_KEY_UID.equals(key)) {
                user.setUid(Long.parseLong(trim(value, USER_STRING_REDUNDANCY)));
            }
            if (Const.SESSION_KEY_USERNAME.equals(key)) {
                user.setUsername(trim(value, USER_STRING_REDUNDANCY));
            }
            if (Const.SESSION_KEY_AVATAR.equals(key)) {
                user.setAvatar(trim(value, USER_STRING_REDUNDANCY));
            }
            if (Const.SESSION_KEY_CST.equals(key)) {
                user.setCraftsmanStatus(Integer.parseInt(trim(value,
                        USER_STRING_REDUNDANCY)) + 1);
            }
            if (Const.SESSION_KEY_MOBILE.equals(key)) {
                user.setMobile(trim(value, USER_STRING_REDUNDANCY));
            }
        });
    }
}

上述代码中常量的定义:

public static final String SESSION_KEY_UID = "\"uid\"";
public static final String SESSION_KEY_USERNAME = "\"un\"";
public static final String SESSION_KEY_AVATAR = "\"avt\"";
public static final String SESSION_KEY_CST = "\"cst\"";
public static final String SESSION_KEY_MOBILE = "\"mbl\"";

public static final String USER_STRING_REDUNDANCY = "\"";

php写入的数据是uid、un、avt、cst、mbl这样的key,经过Msgpack序列化后的key都加上了\",强制转换为Map对象后,map中的key并没有去掉这些转义字符,value也不例外,因此使用trim去掉了value两边的\"。需要注意,如果是yii2的话,一般key都有前缀,此处作者把前缀删掉了,下面所有代码均如此。

此处的trim是经过封装的,和php中trim api类似。

上面的这块代码,Msgpack反序列化可以抽象封装为工具类:

@SuppressWarnings("unchecked")
public static Map getMessagePackValue(final byte[] bytes) throws IOException {
    MessagePack pack = new MessagePack();
    Map map = (Map) pack.read(bytes);
    Map result = new HashMap<>();
    if (null != map) {
        map.forEach((k, v) -> result.put(trim(String.valueOf(k), USER_STRING_REDUNDANCY),
                trim(String.valueOf(v), USER_STRING_REDUNDANCY)));
    }
    return result;
}

优化getMessagePackValue方法,不再使用trim处理数据

@SuppressWarnings("unchecked")
public static Map getMessagePackValue(final byte[] bytes) throws IOException {
    MessagePack pack = new MessagePack();
    Map<String, Object> map = (Map) pack.read(bytes, Templates.tMap(Templates.TString,
            Templates.TValue));

    Map<String, Object> result = new HashMap<>();
    if (map != null) {
        map.forEach((k, v) -> {
            Object object = rawObjToObj(v);
            result.put(k, object);
        });
    }
    return result;
}

Msgpack有很多数据模板,这里使用TString模板和TValue模板。相关模板均在org.msgpack.template.Template类中定义,诸如:TValue、TByte、TShort、TInteger、TLong、TCharacter、TBigInteger、TBigDecimal、TFloat、TBoolean、TString、TByteArray、TByteBuffer、TDate、tNotNullable、tList、tMap、tCollection、tOrdinalEnum。

private static Object rawObjToObj(Object obj) {
    Object objResu = null;
    if (obj instanceof IntegerValue) {
        objResu = ((IntegerValue) obj).asIntegerValue();
    } else if (obj instanceof ArrayValue) {
        ArrayValue v = ((ArrayValue) obj).asArrayValue();
        List<Object> ret = new ArrayList<>(v.size());
        v.forEach(x -> ret.add(x));
        objResu = ret;
    } else if (obj instanceof BooleanValue) {
        objResu = ((BooleanValue) obj).asBooleanValue();
    } else if (obj instanceof FloatValue) {
        objResu = ((FloatValue) obj).asFloatValue();
    } else if (obj instanceof MapValue) {
        MapValue value = ((MapValue) obj).asMapValue();
        Map map = new HashMap<>(value.size());
        value.forEach((k, v) -> {
            map.put(k, rawObjToObj(v));
        });
        objResu = map;
    } else {
        objResu = ((RawValue) obj).asRawValue().getString();
    }
    return objResu;
}

优化后的使用方法

// appId 为php中的session id
public User getAdminUser(String appId) {
    byte[] sessionKey = getSessionKey(appId).getBytes();
            byte[] sessionAdmin;
            User user = new User();
            try (Jedis jedis = jedisPool.getResource()) {
                jedis.select(RedisDB.DB12);
                sessionAdmin = jedis.get(sessionKey);
                user.setExpire(jedis.ttl(sessionKey));
            }
    if (sessionAdmin != null) {
        Map map = new HashMap<>();
        try {
            map = Utils.getMessagePackValue(sessionAdmin);
        } catch (IOException e) {
            LOGGER.warn("parse session admin error: " + e.getMessage());
        }
        if (!isEmpty(map)) {
            if (map.containsKey("id")) {
                if (map.get("id") instanceof IntegerValue) {
                    user.setAid(((IntegerValue) map.get("id")).longValue());
                } else if(!isEmpty((String) map.get("id"))){
                    user.setAid(Long.parseLong((String) map.get("id")));
                } else {
                    user.setAid(0L);
                }
            }
            if (map.containsKey("un")) {
                user.setUsername((String) map.get("un"));
            }
            if (map.containsKey("rn")) {
                user.setRealname((String) map.get("rn"));
            }
        }
    }
    return user;
}

错误:org.msgpack.type.intValueImpl cannot be cast to java.lang.Long

先看看这段代码:

if (map.containsKey("id")) {
    if (map.get("id") instanceof IntegerValue) {
        user.setAid(((IntegerValue) map.get("id")).longValue());
    } else if(!isEmpty((String) map.get("id"))){
        user.setAid(Long.parseLong((String) map.get("id")));
    } else {
        user.setAid(0L);
    }
}

反序列化后获取id,因为User中id是Long类型,这里面id可能是String,也可能是Integer,因此需要注意。最开的代码是这样写的:

if (map.containsKey("id") && !isEmpty((String) map.get("id"))) {
    user.setAid(Long.parseLong((String) map.get("id")));
}

通过Long.parseLong解析id报错,错误:org.msgpack.type.intValueImpl cannot be cast to java.lang.Long,于是修改为下面这般,如果是IntegerValue的实例,则强制转换为Long类型,但是依然报错:

if (map.containsKey("id")) {
    if (map.get("id") instanceof IntegerValue) {
        user.setAid((Long) map.get("id"));
    } else if(!isEmpty((String) map.get("id"))){
        user.setAid(Long.parseLong((String) map.get("id")));
    } else {
        user.setAid(0L);
    }
}

最后强制类型转换IntegerValue,再获取longValue,即为上面函数中的那般才解决此错误。

参考资料


Joyven
630 声望34 粉丝

不学无(用之)术,不安于现状,总想鼓捣点什么,或者总想尝试一些什么新鲜事物。