如何排查SpringVaadinServlet类中的排序异常问题?
问题
在Vaadin应用中对UI条目排序时触发如下异常:
java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeHi(TimSort.java:899) ~[?:1.8.0_275] at java.util.TimSort.mergeAt(TimSort.java:516) ~[?:1.8.0_275] at java.util.TimSort.mergeCollapse(TimSort.java:441) ~[?:1.8.0_275] at java.util.TimSort.sort(TimSort.java:245) ~[?:1.8.0_275] at java.util.Arrays.sort(Arrays.java:1512) ~[?:1.8.0_275] at java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:353) ~[?:1.8.0_275] at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:500) ~[?:1.8.0_275] at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[?:1.8.0_275] at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[?:1.8.0_275] at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[?:1.8.0_275] at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:1.8.0_275] at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566) ~[?:1.8.0_275] at com.vaadin.data.provider.DataCommunicator.fetchItemsWithRange(DataCommunicator.java:379) ~[vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.data.provider.DataCommunicator.sendDataToClient(DataCommunicator.java:341) ~[vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.data.provider.DataCommunicator.beforeClientResponse(DataCommunicator.java:316) ~[vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.server.communication.UidlWriter.write(UidlWriter.java:126) [vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:124) [vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:92) [vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40) [vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1601) [vaadin-server-8.5.2.jar:8.5.2] at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:445) [vaadin-server-8.5.2.jar:8.5.2] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.14.jar:9.0.14] at $javax.servlet.http.HttpServlet$$FastClassBySpringCGLIB$$5ddc88f8.invoke(<generated>) [tomcat-embed-core-9.0.14.jar:?] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) [spring-core-5.1.4.RELEASE.jar:5.1.4.RELEASE] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE] at com.abc.myapp.ui.MyServlet$$EnhancerBySpringCGLIB$$beeb36e1.service(<generated>) [classes/:?]
已在自定义MyServlet类的所有方法中添加调试点,但未捕获到错误入口,该类定义如下:
@Component("vaadinServlet") @WebServlet(name = "MyServlet", asyncSupported = true) @VaadinServletConfiguration(ui = DiscoveryUI.class, productionMode = false) public class MyServlet extends SpringVaadinServlet { ... }
推测问题可能出在其父类SpringVaadinServlet中,需排查该异常是由数据错误还是其他原因导致。
排查方案
1. 定位违规的Comparator实现
异常核心是Comparison method violates its general contract!,说明排序用的Comparator违反了三大约定(自反性、对称性、传递性)。从堆栈看,排序发生在DataCommunicator.fetchItemsWithRange,和Servlet本身无关,重点排查:
- 所有自定义
Comparator:包括Grid等UI组件绑定的排序器、DataProvider中定义的排序逻辑; - 检查Comparator中的常见问题:
- null值处理逻辑不一致(如a为null返回-1,b为null返回1,但两者都为null时返回非0)
- 比较逻辑依赖外部可变状态(如动态变化的配置值)
- 比较结果不符合传递性(如a > b,b > c,但a <= c)
2. 临时切换排序算法验证
Java 7+默认使用TimSort,对Comparator规范要求严格。添加JVM参数-Djava.util.Arrays.useLegacyMergeSort=true切换回旧的MergeSort:
- 若异常消失,确认是Comparator的规范问题;
- 若异常仍出现,再排查数据本身的问题。
3. 校验排序数据合法性
- 提取触发排序时的数据集,用相同Comparator单独排序测试,复现异常后逐步排查异常数据(如null、非法枚举值、字段值异常的对象);
- 检查数据对象的排序字段是否存在逻辑错误(如类型不匹配、计算字段的bug)。
4. 调试Vaadin数据处理流程
无需在Servlet中加断点,直接在以下核心方法加断点:
DataCommunicator.fetchItemsWithRange:查看传入的排序参数和数据集;- 追踪到具体Comparator执行的地方,观察每次比较的输入值和返回结果,找出违反约定的场景。
5. 排除Spring代理影响
临时禁用Spring对该Servlet的AOP代理,排除代理拦截逻辑对排序流程的干扰。
内容的提问来源于stack exchange,提问作者vic
相关产品推荐
相关产品推荐

