Skip to content

Commit

Permalink
Resolved problems mentioned in last discussion with kryo serializer (#61
Browse files Browse the repository at this point in the history
)

* Fix unserialize bug when web application restart.
The situation as as following:
Start redis-server
1. Start web application.
2. Execute query and some objects are cached. -> Class is registered
3. Stop web application.
4. Start web application. 
5. KryoSerializer is initialized and no Class is registered yet.
6. Execute theKryoSerializer#unserialize() is invoked, but it fails
because Class is unknown.

* Modify effection problem when switch between kryo serializer and jdk
serializer in situation that certain class can not serialize with kryo
serializer(very rare)

* Abstract serializers to an interface in order to support various
serialize methods.

* Set kryo dependency to optional

* Add serializer interface

* Use JDKSerializer as fallback of KryoSerializer and add related
testcases.

* Make serializer configurable with redis.properties

* Add test case of unserialize object from a file

* Fix test NPE bug in JDK9

* Format java files and set default serializer to jdk.

* Fix test case bug of unserialize with kyro from file.
  • Loading branch information
laddcn authored and hazendaz committed Jan 24, 2018
1 parent e987020 commit 8ad3e11
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 62 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
<artifactId>kryo</artifactId>
<version>4.0.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

<!--
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/org/mybatis/caches/redis/JDKSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@

import org.apache.ibatis.cache.CacheException;

public class JDKSerializer {
public enum JDKSerializer implements Serializer {
//Enum singleton, which is preferred approach since Java 1.5
INSTANCE;

private JDKSerializer() {
// prevent instantiation
}

public static byte[] serialize(Object object) {
public byte[] serialize(Object object) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
Expand All @@ -41,7 +43,7 @@ public static byte[] serialize(Object object) {
}
}

public static Object unserialize(byte[] bytes) {
public Object unserialize(byte[] bytes) {
if (bytes == null) {
return null;
}
Expand Down
87 changes: 70 additions & 17 deletions src/main/java/org/mybatis/caches/redis/KryoSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,93 @@
*/
package org.mybatis.caches.redis;

import java.io.Serializable;

This comment has been minimized.

Copy link
@hazendaz

hazendaz Jan 24, 2018

Member

@laddcn Is this import unused? I suspect it is. I can remove in another commit, just wanted to point it out.

This comment has been minimized.

Copy link
@hazendaz

hazendaz Jan 24, 2018

Member

Thanks, cleared that one and pull request on other two now.

import java.util.Arrays;
import java.util.HashSet;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.ExternalizableSerializer;
import com.esotericsoftware.kryo.serializers.JavaSerializer;

/**
* SerializeUtil with Kryo, which is faster and space consuming.
* SerializeUtil with Kryo, which is faster and more space consuming.
*
* @author Lei Jiang(ladd.cn@gmail.com)
*/
public final class KryoSerializer {
public enum KryoSerializer implements Serializer {
//Enum singleton, which is preferred approach since Java 1.5
INSTANCE;

private Kryo kryo;
private Output output;
private Input input;
/**
* Classes which can not resolved by default kryo serializer,
* which occurs very rare(https://github.com/EsotericSoftware/kryo#using-standard-java-serialization)
* For these classes, we will use fallbackSerializer(use JDKSerializer now) to resolve.
*/
private HashSet<Class> unnormalClassSet;

static Kryo kryo;
static Output output;
static Input input;
static {
/**
* Hash codes of unnormal bytes which can not resolved by default kryo serializer,
* which will be resolved by fallbackSerializer
*/
private HashSet<Integer> unnormalBytesHashCodeSet;
private Serializer fallbackSerializer;

private KryoSerializer() {
kryo = new Kryo();
output = new Output(200, -1);
input = new Input();
unnormalClassSet = new HashSet<Class>();
unnormalBytesHashCodeSet = new HashSet<Integer>();
fallbackSerializer = JDKSerializer.INSTANCE;//use JDKSerializer as fallback
}

private KryoSerializer() {
// prevent instantiation
}

public static byte[] serialize(Object object) {
kryo.register(object.getClass());
public byte[] serialize(Object object) {
output.clear();
kryo.writeClassAndObject(output, object);
return output.toBytes();
if (!unnormalClassSet.contains(object.getClass())) {
/**
* In the following cases:
* 1. This class occurs for the first time.
* 2. This class have occured and can be resolved by default kryo serializer
*/
try {
kryo.writeClassAndObject(output, object);
return output.toBytes();
} catch (Exception e) {
// For unnormal class occurred for the first time, exception will be thrown
unnormalClassSet.add(object.getClass());
return fallbackSerializer.serialize(object);//use fallback Serializer to resolve
}
} else {
//For unnormal class
return fallbackSerializer.serialize(object);
}
}

public static Object unserialize(byte[] bytes) {
input.setBuffer(bytes);
return kryo.readClassAndObject(input);
public Object unserialize(byte[] bytes) {
int hashCode = Arrays.hashCode(bytes);
if (!unnormalBytesHashCodeSet.contains(hashCode)) {
/**
* In the following cases:
* 1. This bytes occurs for the first time.
* 2. This bytes have occured and can be resolved by default kryo serializer
*/
try {
input.setBuffer(bytes);
return kryo.readClassAndObject(input);
} catch (Exception e) {
// For unnormal bytes occurred for the first time, exception will be thrown
unnormalBytesHashCodeSet.add(hashCode);
return fallbackSerializer.unserialize(bytes);//use fallback Serializer to resolve
}
} else {
//For unnormal bytes
return fallbackSerializer.unserialize(bytes);
}
}

}
9 changes: 9 additions & 0 deletions src/main/java/org/mybatis/caches/redis/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class RedisConfig extends JedisPoolConfig {
private SSLSocketFactory sslSocketFactory;
private SSLParameters sslParameters;
private HostnameVerifier hostnameVerifier;
private String serializer = "jdk";

public boolean isSsl() {
return ssl;
Expand Down Expand Up @@ -133,4 +134,12 @@ public void setSoTimeout(int soTimeout) {
this.soTimeout = soTimeout;
}

public String getSerializer() {
return serializer;
}

public void setSerializer(String serializer) {
this.serializer = serializer;
}

}
39 changes: 14 additions & 25 deletions src/main/java/org/mybatis/caches/redis/SerializeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,30 @@

public final class SerializeUtil {

private static Serializer serializer;

static {
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
if ("kryo".equals(redisConfig.getSerializer())) {
serializer = KryoSerializer.INSTANCE;
} else {
serializer = JDKSerializer.INSTANCE;
}
}

private SerializeUtil() {
// prevent instantiation
}

public static byte[] serialize(Object object) {
try {
//use kryo serialize first
return KryoSerializer.serialize(object);
} catch (Exception e) {
//if kryo serialize fails, user jdk serialize as a fallback
try {
return JDKSerializer.serialize(object);
} catch (CacheException cacheException) {
throw cacheException;
}

}
return serializer.serialize(object);
}

public static Object unserialize(byte[] bytes) {
if (bytes == null) {
if (bytes == null || bytes.length == 0) {
return null;
}
try {
//use kryo unserialize first
return KryoSerializer.unserialize(bytes);
} catch (Exception e) {
//if kryo unserialize fails, user jdk unserialize as a fallback
try {
return JDKSerializer.unserialize(bytes);
} catch (CacheException cacheException) {
throw cacheException;
}

}
return serializer.unserialize(bytes);
}

}
34 changes: 34 additions & 0 deletions src/main/java/org/mybatis/caches/redis/Serializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright 2015-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mybatis.caches.redis;

public interface Serializer {

/**
* Serialize method
* @param object
* @return serialized bytes
*/
public byte[] serialize(Object object);

/**
* Unserialize method
* @param bytes
* @return unserialized object
*/
public Object unserialize(byte[] bytes);

}
Loading

0 comments on commit 8ad3e11

Please sign in to comment.