周报-第2期:序列化Kryo、Service Provider Interface、旁听川普和马斯克的twitter live talking

序列化

Java本身有提供序列化和反序列化实现,但是在实际开发中发现大家很少使用。对于一些有可读性要求,前后端交互的场景,使用JSON序列化已成默认。

我自己有维护一个任务组件,一开始也是采用JSON序列化,并且数据存到数据库以后还可以直接搜索和阅读。但后面随着需要支持的业务越来越复杂,JSON序列化没有办法支持类似BufferedImage这类对象的序列化和反序列化操作,后面就改用了Java序列化实现。

最近在考虑将这个任务组件重写并开源,所以考虑在原来的基础上增加一些更好的选型。

Kryo序列化

Kryo 是我关注到的一个序列化选型。Kryo使用的是BSD-3-Clause许可协议,还是比较宽松的,对于我这个开源组件来说没有任何问题。

性能和安全性方面因为有其他知名开源项目的使用,所以我也不担心这方面。相比之下,我更关心他所支持的序列化类型。

我在Kryo项目的readme中查看到了这个链接:https://github.com/EsotericSoftware/kryo/blob/master/src/com/esotericsoftware/kryo/Kryo.java#L179 从这个代码来看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
addDefaultSerializer(byte[].class, ByteArraySerializer.class);
addDefaultSerializer(char[].class, CharArraySerializer.class);
addDefaultSerializer(short[].class, ShortArraySerializer.class);
addDefaultSerializer(int[].class, IntArraySerializer.class);
addDefaultSerializer(long[].class, LongArraySerializer.class);
addDefaultSerializer(float[].class, FloatArraySerializer.class);
addDefaultSerializer(double[].class, DoubleArraySerializer.class);
addDefaultSerializer(boolean[].class, BooleanArraySerializer.class);
addDefaultSerializer(String[].class, StringArraySerializer.class);
addDefaultSerializer(Object[].class, ObjectArraySerializer.class);
addDefaultSerializer(BigInteger.class, BigIntegerSerializer.class);
addDefaultSerializer(BigDecimal.class, BigDecimalSerializer.class);
addDefaultSerializer(Class.class, ClassSerializer.class);
addDefaultSerializer(Date.class, DateSerializer.class);
addDefaultSerializer(Enum.class, EnumSerializer.class);
addDefaultSerializer(EnumSet.class, EnumSetSerializer.class);
addDefaultSerializer(Currency.class, CurrencySerializer.class);
addDefaultSerializer(StringBuffer.class, StringBufferSerializer.class);
addDefaultSerializer(StringBuilder.class, StringBuilderSerializer.class);
addDefaultSerializer(Collections.EMPTY_LIST.getClass(), CollectionsEmptyListSerializer.class);
addDefaultSerializer(Collections.EMPTY_MAP.getClass(), CollectionsEmptyMapSerializer.class);
addDefaultSerializer(Collections.EMPTY_SET.getClass(), CollectionsEmptySetSerializer.class);
addDefaultSerializer(Collections.singletonList(null).getClass(), CollectionsSingletonListSerializer.class);
addDefaultSerializer(Collections.singletonMap(null, null).getClass(), CollectionsSingletonMapSerializer.class);
addDefaultSerializer(Collections.singleton(null).getClass(), CollectionsSingletonSetSerializer.class);
addDefaultSerializer(TreeSet.class, TreeSetSerializer.class);
addDefaultSerializer(Collection.class, CollectionSerializer.class);
addDefaultSerializer(ConcurrentSkipListMap.class, ConcurrentSkipListMapSerializer.class);
addDefaultSerializer(TreeMap.class, TreeMapSerializer.class);
addDefaultSerializer(Map.class, MapSerializer.class);
addDefaultSerializer(TimeZone.class, TimeZoneSerializer.class);
addDefaultSerializer(Calendar.class, CalendarSerializer.class);
addDefaultSerializer(Locale.class, LocaleSerializer.class);
addDefaultSerializer(Charset.class, CharsetSerializer.class);
addDefaultSerializer(URL.class, URLSerializer.class);
addDefaultSerializer(Arrays.asList().getClass(), ArraysAsListSerializer.class);
addDefaultSerializer(void.class, new VoidSerializer());
addDefaultSerializer(PriorityQueue.class, new PriorityQueueSerializer());
addDefaultSerializer(BitSet.class, new BitSetSerializer());
addDefaultSerializer(KryoSerializable.class, KryoSerializableSerializer.class);

我感觉有一些些担忧呀,毕竟我的任务组件是没有限制对象类型的。不过Kryo支持自定义序列化实现方式,如果真的遇到了不支持的对象类型,也许我可以自己写一个然后合并给Kryo。

这块如果要采用Kryo代替Java的序列化的话,我可能还得加一个单元测试来确认支持的对象范围。

参考内容

SPI:Service Provider Interface

从Java 1.6开始,引入了java.util.ServiceLoader类,我简单看了一下源码,有几个有意思的点分享一下:

懒加载

内部有一个private修饰的LazyIterator

扫描路径

默认扫描路径前缀是META-INF/services/

类加载的时候,有获取ExtClassLoader实例

这个方法蛮有意思的,循环查找,直到prevExtClassLoader的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Creates a new service loader for the given service type, using the
* extension class loader.
*
* <p> This convenience method simply locates the extension class loader,
* call it <tt><i>extClassLoader</i></tt>, and then returns
*
* <blockquote><pre>
* ServiceLoader.load(<i>service</i>, <i>extClassLoader</i>)</pre></blockquote>
*
* <p> If the extension class loader cannot be found then the system class
* loader is used; if there is no system class loader then the bootstrap
* class loader is used.
*
* <p> This method is intended for use when only installed providers are
* desired. The resulting service will only find and load providers that
* have been installed into the current Java virtual machine; providers on
* the application's class path will be ignored.
*
* @param <S> the class of the service type
*
* @param service
* The interface or abstract class representing the service
*
* @return A new service loader
*/
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}

我就很好奇,为什么他非要拿到extClassLoader,目前还没有找到答案。

SPI:Spring中的应用

其实Spring框架中也有用到SPI,对应的路径是META-INF/services/spring.factories,如果你在引入别人的jar包时,你就会发现很多都有这个spring.factories文件,这个就是Spring用于管理自动加载的。

与Configuration注解的差别

@Configuration注解的类路径需要是@SpringBootApplication声明的扫描路径(不声明如果就是当前类及所有子路径)。

所以我们如果要做一个组件,最好就是加上META-INF/services/spring.factories以便其他人集成

文件内容模版

1
2
3
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baofeidyz.AbcdThreadPoolConfiguration,\
com.baofeidyz.AbcdServiceConfiguration

旁听Donald J.Trump和Elon Musk的live talking

你可以点这个链接回听。

这件事发生在川普被枪击之后,也是被Twitter封禁之后第一次回归twitter平台,直接和Elon Musk开启talking。我听了一部分,可能就china、nuclear、develop fast这些地方有点记忆点,其他的也就是听了个寂寞。不过这么轻松就能直接参与历史事件,还是很值得记录一下。