修复 Sharding-JDBC 使用时的两次 NoClassDefFoundError

由于项目上有着某些需求,所以需要使用 Sharding-JDBC。根据官方文档,引入 shardingsphere-jdbc-core 依赖并使用当前最新版本 5.1.0,结果在测试的过程中出现了两次 NoClassDefFoundError,分别记录一下解决过程。

一、ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSource

配置完程序,兴冲冲地打开前端准备测试,可没想到刚打开就报了个错。老规矩,先看最后一个 Caused by,结果是个没头没脑的 ClassNotFoundException。

那就继续往上翻,看一下异常栈信息。

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
Caused by: java.lang.NoClassDefFoundError: org/apache/tomcat/dbcp/dbcp2/BasicDataSource
at org.apache.shardingsphere.infra.datasource.pool.metadata.type.dbcp.TomcatDBCPDataSourcePoolMetaData.getType(TomcatDBCPDataSourcePoolMetaData.java:67)
at org.apache.shardingsphere.spi.typed.TypedSPIRegistry.lambda$findRegisteredService$0(TypedSPIRegistry.java:44)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1361)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531)
at org.apache.shardingsphere.spi.typed.TypedSPIRegistry.findRegisteredService(TypedSPIRegistry.java:44)
at org.apache.shardingsphere.infra.datasource.pool.metadata.DataSourcePoolMetaDataFactory.newInstance(DataSourcePoolMetaDataFactory.java:46)
at org.apache.shardingsphere.infra.datasource.props.DataSourcePropertiesCreator.createProperties(DataSourcePropertiesCreator.java:81)
at org.apache.shardingsphere.infra.datasource.props.DataSourcePropertiesCreator.create(DataSourcePropertiesCreator.java:57)
at org.apache.shardingsphere.infra.metadata.resource.DataSourcesMetaData.<init>(DataSourcesMetaData.java:41)
at org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData.createResource(ShardingSphereMetaData.java:73)
at org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData.create(ShardingSphereMetaData.java:66)
at org.apache.shardingsphere.mode.metadata.MetaDataContextsBuilder.getMetaDataMap(MetaDataContextsBuilder.java:105)
at org.apache.shardingsphere.mode.metadata.MetaDataContextsBuilder.build(MetaDataContextsBuilder.java:96)
at org.apache.shardingsphere.mode.manager.memory.MemoryContextManagerBuilder.build(MemoryContextManagerBuilder.java:49)
at org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource.createContextManager(ShardingSphereDataSource.java:81)
at org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource.<init>(ShardingSphereDataSource.java:64)
at org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory.createDataSource(ShardingSphereDataSourceFactory.java:77)

首先点进去看一下第一行的 TomcatDBCPDataSourcePoolMetaData 这个类,IDE 很明显地提示了错误,所以根源在于 Sharding 使用了 BasicDataSource 类却没有引入其依赖。

既然问题的根源找到了,那么接下来看一眼为什么会触发到这里。

接着看第二行,TypedSPIRegistry 类的 44 行,并在这里下一个断点:

可以看到,ShardingSphereServiceLoader.newServiceInstances(typedSPIClass) 方法传入了 SPI 类 DataSourcePoolMetaData, 并获取到了其三个实现类,而最后一个 TomcatDBCPDataSourcePoolMetaData 类就是刚才抛出异常的类。

然后在后续的 filter 中,会循环每个对象,在调用 getType() 方法时便出现了异常。

所以看上去并不是自己的问题,在谷歌之后发现,近期已经有人提出了这个问题,具体可以参见:Add example that implements the Metadata SPI for third-party JDBC pools

通过 issue 中的描述我们可以得知,这个问题是在 5.1.0 中才出现的。看上去已经有人提了 PR 修复了这个问题,但是目前还没有发布新版本。所以我们可以降级到 5.0.0 使用旧版本来规避这个问题,或者直接引入缺失的依赖使代码不再报错。

1
2
3
4
5
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>10.0.16</version>
</dependency>

二、NoClassDefFoundError: org/antlr/v4/runtime/CodePointBuffer

修复好刚才的问题之后,重新运行程序,创建数据源,好,没有报错。再执行一下 SQL 查询,不好,又报错了…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Caused by: java.lang.NoClassDefFoundError: org/antlr/v4/runtime/CodePointBuffer
at org.apache.shardingsphere.sql.parser.core.SQLParserFactory.getSQLCharStream(SQLParserFactory.java:82)
at org.apache.shardingsphere.sql.parser.core.SQLParserFactory.createTokenStream(SQLParserFactory.java:76)
at org.apache.shardingsphere.sql.parser.core.SQLParserFactory.newInstance(SQLParserFactory.java:55)
at org.apache.shardingsphere.sql.parser.core.database.parser.SQLParserExecutor.twoPhaseParse(SQLParserExecutor.java:58)
at org.apache.shardingsphere.sql.parser.core.database.parser.SQLParserExecutor.parse(SQLParserExecutor.java:49)
at org.apache.shardingsphere.sql.parser.api.SQLParserEngine.parse(SQLParserEngine.java:47)
at org.apache.shardingsphere.infra.parser.sql.SQLStatementParserExecutor.parse(SQLStatementParserExecutor.java:48)
at org.apache.shardingsphere.infra.parser.sql.SQLStatementParserEngine.parse(SQLStatementParserEngine.java:47)
at org.apache.shardingsphere.infra.parser.ShardingSphereSQLParserEngine.parse0(ShardingSphereSQLParserEngine.java:70)
at org.apache.shardingsphere.infra.parser.ShardingSphereSQLParserEngine.parse(ShardingSphereSQLParserEngine.java:59)
at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement.createLogicSQL(ShardingSphereStatement.java:414)
at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement.executeQuery(ShardingSphereStatement.java:144)
at org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:452)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:381)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:465)
at org.springframework.jdbc.core.JdbcTemplate.queryForRowSet(JdbcTemplate.java:530)

从第一行点进去先看眼,又是找不到类,不过这次不太一样。看下上面的 import,发现这个包下的大部分的类都可以找到,只有这两个没有找到:

只能盲猜是版本问题,打开 Maven Helper 看一下依赖情况,果然是和公司自己的包发生了版本冲突。手动指定版本到 4.9.2,问题解决。