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 map = (Map) pack.read(bytes, Templates.tMap(Templates.TString,
Templates.TValue));
Map 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
优化后的使用方法
// 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,即为上面函数中的那般才解决此错误。
参考资料
1.让java版msgpack支持Object类型
2.MessagePack
3.msgpack