Poison

关于 Dubbo Hessian 反序列化的类不含无参构造函数的问题

今天帮同事看了个问题,该问题不复杂,只是表现出来没有头绪,在此简单记录。首先发现该问题是一个空指针异常,即调用方通过 Dubbo 调用消费端提供的方法时调用方的异常仅有一个空指针异常,没有其他有价值的信息。调用方的代码简化如下:

1
2
3
4
5
6
7
@Reference
private TestService testService;

@Test
public void testNPE() {
testService.queryByQuery(new TestQuery(22L));
}

第 6 行将抛出空指针异常,异常栈帧如下:

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
java.lang.NullPointerException
at me.tianshuang.TestServiceImpl.queryByQuery(TestServiceImpl.java:64)
at org.apache.dubbo.common.bytecode.Wrapper187.invokeMethod(Wrapper187.java)
at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:84)
at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:56)
at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
at me.tianshuang.dubbo.filter.DubboExceptionFilter.invoke(DubboExceptionFilter.java:22)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at me.tianshuang.dubbo.filter.DubboMethodFilter.invoke(DubboMethodFilter.java:38)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at me.tianshuang.dubbo.filter.DubboProviderFilter.invoke(DubboProviderFilter.java:56)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter.invoke(SentinelDubboProviderFilter.java:77)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:89)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:44)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:81)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:102)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:149)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81)
at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:150)
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:100)
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:175)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

提供者该方法实现的代码如下:

1
2
3
4
5
@Override
public List<TestDTO> queryByQuery(TestQuery query) {
query.setPageSize(Integer.MAX_VALUE); // 对应 TestServiceImpl.java:64
return queryPageByQuery(query);
}

在提供者方能看到的异常信息如下:

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
2021-12-23 22:23:33,289 ERROR   me.tianshuang.filter.DubboExceptionFilter:55 -  [DUBBO] Got unchecked and undeclared exception which called by 192.168.1.9. service: me.tianshuang.service.TestService, method: queryByQuery, exception: java.lang.NullPointerException: null, dubbo version: 2.7.5, current host: 192.168.1.9
java.lang.NullPointerException: null
at me.tianshuang.service.TestServiceImpl.queryByQuery(TestServiceImpl.java:64) ~[service-1.0-SNAPSHOT.jar:?]
at org.apache.dubbo.common.bytecode.Wrapper187.invokeMethod(Wrapper187.java) ~[dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47) ~[dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:84) ~[dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:56) ~[dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56) ~[dubbo-2.7.5.jar:2.7.5]
at me.tianshuang.dubbo.filter.DubboExceptionFilter.invoke(DubboExceptionFilter.java:22) ~[dubbo-tool-1.0-SNAPSHOT.jar:?]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at me.tianshuang.dubbo.filter.DubboMethodFilter.invoke(DubboMethodFilter.java:38) [dubbo-tool-1.0-SNAPSHOT.jar:?]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at me.tianshuang.dubbo.filter.DubboProviderFilter.invoke(DubboProviderFilter.java:56) [dubbo-tool-1.0-SNAPSHOT.jar:?]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter.invoke(SentinelDubboProviderFilter.java:77) [sentinel-apache-dubbo-adapter-1.8.0.jar:?]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:89) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:44) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:102) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:149) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:81) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:150) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:100) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:175) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57) [dubbo-2.7.5.jar:2.7.5]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_291]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_291]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_291]

为何 TestServiceImpl.java:64 会触发空指针呢?难道 query 对象为空?可是在调用端明显创建了 TestQuery 的实例啊,于是在提供方跟踪了源码,发现与 TestQuery 的构造函数有关,我们的 TestQuery 类的部分源码简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Getter
@Setter
public class TestQuery implements Serializable {

public TestQuery(Long userId) {
if (userId == null) {
throw new IllegalArgumentException("userId cannot be null");
}
this.userId = userId;
}

private Long id;
private Long userId;
private String userName;

}

在第 7 行将触发 IllegalArgumentException,而该查询类为何创建了这样的构造函数呢,因为我们将 TestQuery 对应的表进行了分库分表,且选用的拆分键为 userId,所以使用该工具类查询时,我们要求必须带有拆分键 userId。而通过跟踪源码,我们发现在 JavaDeserializer.java at master 会调用 instantiate() 实例化 TestQuery 类,源码如下:

1
2
3
4
5
6
7
8
9
10
11
protected Object instantiate()
throws Exception {
try {
if (_constructor != null)
return _constructor.newInstance(_constructorArgs);
else
return _type.newInstance();
} catch (Exception e) {
throw new HessianProtocolException("'" + _type.getName() + "' could not be instantiated", e);
}
}

可以看出采用的反射进行实例化,而构造器及构造器的参数在当前 JavaDeserializer 实例化时会确定,代码位于 JavaDeserializer.java at master,核心部分如下:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public JavaDeserializer(Class cl) {
_type = cl;
_fieldMap = getFieldMap(cl);

_readResolve = getReadResolve(cl);

if (_readResolve != null) {
_readResolve.setAccessible(true);
}

Constructor[] constructors = cl.getDeclaredConstructors();
long bestCost = Long.MAX_VALUE;

for (int i = 0; i < constructors.length; i++) {
Class[] param = constructors[i].getParameterTypes();
long cost = 0;

for (int j = 0; j < param.length; j++) {
cost = 4 * cost;

if (Object.class.equals(param[j]))
cost += 1;
else if (String.class.equals(param[j]))
cost += 2;
else if (int.class.equals(param[j]))
cost += 3;
else if (long.class.equals(param[j]))
cost += 4;
else if (param[j].isPrimitive())
cost += 5;
else
cost += 6;
}

if (cost < 0 || cost > (1 << 48))
cost = 1 << 48;

cost += (long) param.length << 48;

if (cost < bestCost) {
_constructor = constructors[i];
bestCost = cost;
}
}

if (_constructor != null) {
_constructor.setAccessible(true);
Class[] params = _constructor.getParameterTypes();
_constructorArgs = new Object[params.length];
for (int i = 0; i < params.length; i++) {
_constructorArgs[i] = getParamArg(params[i]);
}
}
}

/**
* Creates a map of the classes fields.
*/
protected static Object getParamArg(Class cl) {
if (!cl.isPrimitive())
return null;
else if (boolean.class.equals(cl))
return Boolean.FALSE;
else if (byte.class.equals(cl))
return new Byte((byte) 0);
else if (short.class.equals(cl))
return new Short((short) 0);
else if (char.class.equals(cl))
return new Character((char) 0);
else if (int.class.equals(cl))
return Integer.valueOf(0);
else if (long.class.equals(cl))
return Long.valueOf(0);
else if (float.class.equals(cl))
return Float.valueOf(0);
else if (double.class.equals(cl))
return Double.valueOf(0);
else
throw new UnsupportedOperationException();
}

核心思想为选择一个 cost 最低的构造函数,然后调用 getParamArg 方法初始化每个构造函数参数的值,其中最为关键的为第 60 行,可以看出非原语类型会直接使用 NULL 作为参数的值。对应到以上这个例子,可以看出 userId 被初始化为空,然后反射调用构造函数,根据代码我们知道会触发构造函数中的 IllegalArgumentException,而为什么调用方收到的是空指针异常呢?我们仔细查看提供方的线程栈帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
instantiate:311, JavaDeserializer (com.alibaba.com.caucho.hessian.io)
readObject:201, JavaDeserializer (com.alibaba.com.caucho.hessian.io)
readObjectInstance:2818, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2145, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2074, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2118, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2074, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:92, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)
decode:139, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:79, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:57, DecodeHandler (org.apache.dubbo.remoting.transport)
received:44, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

会发现在 DecodeableRpcInvocation:139 行处理时对异常进行了 catch 操作,并且仅打印了日志,源码位于 DecodeableRpcInvocation.java at dubbo-2.7.5

1
2
3
4
5
6
7
8
9
10
args = new Object[pts.length];
for (int i = 0; i < args.length; i++) {
try {
args[i] = in.readObject(pts[i]);
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("Decode argument failed: " + e.getMessage(), e);
}
}
}

可以看出,参数解码异常时仅打印了日志,且日志等级为 WARN,并不是 ERROR,当我们的日志等级为 ERROR 时是看不到这个异常信息的,然后 query 参数的值为 NULL 进行提供方的调用,在实际的方法体中触发了空指针异常,这就是消费端收到空指针异常的原因。我把提供方的日志等级调整后看到了以上打印的异常:

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
2021-12-23 23:14:20,947 WARN    org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation:142 -  [DUBBO] Decode argument failed: 'me.tianshuang.query.TestQuery' could not be instantiated, dubbo version: 2.7.5, current host: 192.169.1.21
com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'me.tianshuang.query.TestQuery' could not be instantiated
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:316) ~[dubbo-2.7.5.jar:2.7.5]
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:201) ~[dubbo-2.7.5.jar:2.7.5]
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2818) ~[dubbo-2.7.5.jar:2.7.5]
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2145) ~[dubbo-2.7.5.jar:2.7.5]
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074) ~[dubbo-2.7.5.jar:2.7.5]
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2118) ~[dubbo-2.7.5.jar:2.7.5]
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074) ~[dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:92) ~[dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:139) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:79) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44) [dubbo-2.7.5.jar:2.7.5]
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57) [dubbo-2.7.5.jar:2.7.5]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_282]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_282]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_282]
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:1.8.0_282]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[?:1.8.0_282]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:1.8.0_282]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[?:1.8.0_282]
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:312) ~[dubbo-2.7.5.jar:2.7.5]
... 15 more
Caused by: java.lang.IllegalArgumentException: userId cannot be null
at me.tianshuang.query.TestQuery.<init>(TestQuery.java:23) ~[sdk-1.0-SNAPSHOT.jar:?]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:1.8.0_282]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[?:1.8.0_282]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:1.8.0_282]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[?:1.8.0_282]
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:312) ~[dubbo-2.7.5.jar:2.7.5]
... 15 more

知道原因后,我们将 TestQuery 类构造函数中的 Long userId 调整为了 long userId,即当 Dubbo 序列化时先默认使用 0 填充,然后再通过反射设置 userId 属性的值,以实现反序列化,这样调整的原因是依然保证了 TestQuery 使用时必须设置拆分键,而不是简单的新增一个无参的构造函数。

在排查过程中,我发现在 Duboo 源码仓库中无法搜索到 JavaDeserializer 类,而发行的 jar 文件中含有该类,猜测是通过将依赖进行 shade 处理后打入了 jar 文件,查询源码后发现果然如此,JavaDeserializer 类源码位于 dubbo-hessian-lite,在 Dubbo 的依赖配置文件 pom.xml at dubbo-2.7.5 中进行了如下配置:

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
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createSourcesJar>true</createSourcesJar>
<promoteTransitiveDependencies>false</promoteTransitiveDependencies>
<artifactSet>
<includes>
<include>com.alibaba:hessian-lite</include>
<!-- omitted -->
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Reference

SEC-779: GrantedAuthorityImpl constructor throws IllegalArgumentException during Hessian deserialization · Issue #1038 · spring-projects/spring-security · GitHub
dubbo2.7.0 com.alibaba.com.caucho.hessian.io.HessianProtocolException: ‘com.alibaba.dubbo.common.URL’ could not be instantiated · Issue #3342 · apache/dubbo · GitHub