1 /*
2 * Copyright 2015-2023 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.mybatis.caches.redis;
17
18 import com.esotericsoftware.kryo.kryo5.Kryo;
19 import com.esotericsoftware.kryo.kryo5.io.Input;
20 import com.esotericsoftware.kryo.kryo5.io.Output;
21
22 import java.util.Arrays;
23 import java.util.Set;
24 import java.util.concurrent.ConcurrentHashMap;
25
26 /**
27 * SerializeUtil with Kryo, which is faster and less space consuming.
28 *
29 * @author Lei Jiang(ladd.cn@gmail.com)
30 */
31 public enum KryoSerializer implements Serializer {
32 // Enum singleton, which is preferred approach since Java 1.5
33 INSTANCE;
34
35 /**
36 * kryo is thread-unsafe, use ThreadLocal.
37 */
38 private ThreadLocal<Kryo> kryos = ThreadLocal.withInitial(Kryo::new);
39
40 /**
41 * Classes which can not resolved by default kryo serializer, which occurs very
42 * rare(https://github.com/EsotericSoftware/kryo#using-standard-java-serialization) For these classes, we will use
43 * fallbackSerializer(use JDKSerializer now) to resolve.
44 */
45 private Set<Class<?>> unnormalClassSet;
46
47 /**
48 * Hash codes of unnormal bytes which can not resolved by default kryo serializer, which will be resolved by
49 * fallbackSerializer
50 */
51 private Set<Integer> unnormalBytesHashCodeSet;
52 private Serializer fallbackSerializer;
53
54 private KryoSerializer() {
55 unnormalClassSet = ConcurrentHashMap.newKeySet();
56 unnormalBytesHashCodeSet = ConcurrentHashMap.newKeySet();
57 fallbackSerializer = JDKSerializer.INSTANCE;// use JDKSerializer as fallback
58 }
59
60 @Override
61 public byte[] serialize(Object object) {
62 if (unnormalClassSet.contains(object.getClass())) {
63 // For unnormal class
64 return fallbackSerializer.serialize(object);
65 }
66
67 /**
68 * In the following cases: 1. This class occurs for the first time. 2. This class have occurred and can be resolved
69 * by default kryo serializer
70 */
71 try (Output output = new Output(200, -1)) {
72 kryos.get().writeClassAndObject(output, object);
73 return output.toBytes();
74 } catch (Exception e) {
75 // For unnormal class occurred for the first time, exception will be thrown
76 unnormalClassSet.add(object.getClass());
77 return fallbackSerializer.serialize(object);// use fallback Serializer to resolve
78 }
79 }
80
81 @Override
82 public Object unserialize(byte[] bytes) {
83 if (bytes == null) {
84 return null;
85 }
86 int hashCode = Arrays.hashCode(bytes);
87 if (unnormalBytesHashCodeSet.contains(hashCode)) {
88 // For unnormal bytes
89 return fallbackSerializer.unserialize(bytes);
90 }
91
92 /**
93 * In the following cases: 1. This bytes occurs for the first time. 2. This bytes have occurred and can be resolved
94 * by default kryo serializer
95 */
96 try (Input input = new Input()) {
97 input.setBuffer(bytes);
98 return kryos.get().readClassAndObject(input);
99 } catch (Exception e) {
100 // For unnormal bytes occurred for the first time, exception will be thrown
101 unnormalBytesHashCodeSet.add(hashCode);
102 return fallbackSerializer.unserialize(bytes);// use fallback Serializer to resolve
103 }
104 }
105
106 }