本文主要记录自己的负责的微服务集群从JDK8升级到JDK17过程中遇到过的一些问题。
SpringBoot3+的变化
由于原先的项目是采用的SpringBoot容器去进行运作的,随着JDK版本的提升,SpringBoot也要升级为了3+的版本,以下是我升级后的项目所使用的SpringBoot父依赖:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.4</version><relativePath/> <!-- lookup parent from repository --></parent><parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.4</version> <relativePath/> <!-- lookup parent from repository --> </parent><parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.4</version> <relativePath/> <!-- lookup parent from repository --> </parent>
包地址的变化
Spring Boot 3.0 最低要求 Java 17,并向上兼容支持 Java 19。
所以,如果你想升级 Spring Boot 3.0,请确保你的 JDK 版本是否符合要求,毕竟现在大部分人还是用的 Java 8,升级 JDK 版本不是一件小事,虽然现在 Java 17+ 是免费使用的,但不确定哪个时间点会收费,也可以转战 OpenJDK 或者其他开源的 JDK 版本。
另外,Spring Boot 3.0 已将所有底层依赖项从 Java EE 迁移到了 Jakarta EE API,基于 Jakarta EE 9 并尽可能地兼容 Jakarta EE 10。因为早在几年前 Java EE 已经正式更名为 Jakarta,所以,所有相关的名称都变了,包括包名,所以使用了 Java EE 的应用改动也不小。
spi文件的变化
SpringBoot3之后的spi文件变化为了以下文件名:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
但是文件内部的spi编写方式和早期版本的spring.factories编写方式基本相同
更多关于SpringBoot3之后的变化,可以参考以下文档:
github.com/spring-proj…
Dubbo的变化
由于JDK版本发生了变化,因为很多框架的版本也要做提升,这里Dubbo我所使用的版本如下:
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>3.2.0-beta.3</version></dependency><dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.2.0-beta.3</version> </dependency><dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.2.0-beta.3</version> </dependency>
SpirngCloudeAlibaba的版本升级
关于SpringCloudeAlibaba的版本升级变化如下:
<dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2022.0.0.0-RC1</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2022.0.0.0-RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement><dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2022.0.0.0-RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
基本上SpringCloudAlibaba引入的版本正常之后,其他相关的依赖如Nacos,就可以直接引入了:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency><dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
Nacos的升级
由于我们的SpringCloudeAlibaba做了调整,所以它对接使用的Nacos版本也最好做下同步升级,这里我推荐使用Nacos2.2.1版本,自己在使用它的时候还算正常。
ShardingJdbc版本设计
关于ShardingJDBC的版本升级,这个最为费劲,官网文档上也没有很明确的说明讲解,下边这个版本和配置文件是自己踩了很多坑之后才得到的一份结果。
首先ShardingJDBC需要兼容JDK17的话,需要引入以下内容,要注意,这里引入的依赖坐标和4.x版本的坐标名字是不一样的,这一点一定要注意。
<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core</artifactId><version>5.3.2</version></dependency><dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core</artifactId> <version>5.3.2</version> </dependency><dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core</artifactId> <version>5.3.2</version> </dependency>
另外一旦ShardingJDBC升级到了5.x版本之后,它的配置文件规则也会发生变化:
需要以一个类似驱动包的方式去指定sharding-jdbc的详细配置:例如下图
这段配置截图中的url属性里面有一个qiyu-live-db-sharding.yaml文件名,这份文件才是ShardingJDBC的详细内容,在这份详细配置中可以去编写关于数据库操作的一些内容:
这里我将自己的一份配置文件贴出来给大家参考,避免大家踩坑:
dataSources:user_master: ##新表,重建的分表dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://cloud.db:8808/qiyu_live_user?useUnicode=true&characterEncoding=utf8username: rootpassword: rootuser_slave0: ##新表,重建的分表dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://cloud.db:8809/qiyu_live_user?useUnicode=true&characterEncoding=utf8username: rootpassword: rootrules:- !READWRITE_SPLITTINGdataSources:user_ds:staticStrategy:writeDataSourceName: user_masterreadDataSourceNames:- user_slave0- !SINGLEdefaultDataSource: user_ds ## 不分表分分库的默认数据源- !SHARDINGtables:t_user:actualDataNodes: user_ds.t_user_${(0..99).collect(){it.toString().padLeft(2,'0')}}tableStrategy:standard:shardingColumn: user_idshardingAlgorithmName: t_user-inlinet_user_tag:actualDataNodes: user_ds.t_user_tag_${(0..99).collect(){it.toString().padLeft(2,'0')}}tableStrategy:standard:shardingColumn: user_idshardingAlgorithmName: t_user_tag-inlineshardingAlgorithms:t_user-inline:type: INLINEprops:algorithm-expression: t_user_${(user_id % 100).toString().padLeft(2,'0')}t_user_tag-inline:type: INLINEprops:algorithm-expression: t_user_tag_${(user_id % 100).toString().padLeft(2,'0')}props:sql-show: truemax-connections-size-per-query: 3dataSources: user_master: ##新表,重建的分表 dataSourceClassName: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://cloud.db:8808/qiyu_live_user?useUnicode=true&characterEncoding=utf8 username: root password: root user_slave0: ##新表,重建的分表 dataSourceClassName: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://cloud.db:8809/qiyu_live_user?useUnicode=true&characterEncoding=utf8 username: root password: root rules: - !READWRITE_SPLITTING dataSources: user_ds: staticStrategy: writeDataSourceName: user_master readDataSourceNames: - user_slave0 - !SINGLE defaultDataSource: user_ds ## 不分表分分库的默认数据源 - !SHARDING tables: t_user: actualDataNodes: user_ds.t_user_${(0..99).collect(){it.toString().padLeft(2,'0')}} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_user-inline t_user_tag: actualDataNodes: user_ds.t_user_tag_${(0..99).collect(){it.toString().padLeft(2,'0')}} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_user_tag-inline shardingAlgorithms: t_user-inline: type: INLINE props: algorithm-expression: t_user_${(user_id % 100).toString().padLeft(2,'0')} t_user_tag-inline: type: INLINE props: algorithm-expression: t_user_tag_${(user_id % 100).toString().padLeft(2,'0')} props: sql-show: true max-connections-size-per-query: 3dataSources: user_master: ##新表,重建的分表 dataSourceClassName: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://cloud.db:8808/qiyu_live_user?useUnicode=true&characterEncoding=utf8 username: root password: root user_slave0: ##新表,重建的分表 dataSourceClassName: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://cloud.db:8809/qiyu_live_user?useUnicode=true&characterEncoding=utf8 username: root password: root rules: - !READWRITE_SPLITTING dataSources: user_ds: staticStrategy: writeDataSourceName: user_master readDataSourceNames: - user_slave0 - !SINGLE defaultDataSource: user_ds ## 不分表分分库的默认数据源 - !SHARDING tables: t_user: actualDataNodes: user_ds.t_user_${(0..99).collect(){it.toString().padLeft(2,'0')}} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_user-inline t_user_tag: actualDataNodes: user_ds.t_user_tag_${(0..99).collect(){it.toString().padLeft(2,'0')}} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_user_tag-inline shardingAlgorithms: t_user-inline: type: INLINE props: algorithm-expression: t_user_${(user_id % 100).toString().padLeft(2,'0')} t_user_tag-inline: type: INLINE props: algorithm-expression: t_user_tag_${(user_id % 100).toString().padLeft(2,'0')} props: sql-show: true max-connections-size-per-query: 3
ShardingJdbc引入Nacos配置中心
在5.3.x的版本中,ShardingJdbc是没有开发接入Nacos实现配置化的功能的,这块需要进行二次开发才可以进行实现。这里给大家一个思路,大家可以去看下ShardingJDBC的以下接口:
org.apache.shardingsphere.driver.jdbc.core.driver.ShardingSphereDriverURLProvider
ShardingJdbc底层是基于这个接口去定义了配置的读取规则,我们可以基于这个接口做一个自定义的扩展类,然后通过ShardingJdbc的spi文件将它注入到我们的应用上下文中,从而实现Nacos读取ShardingJdbc配置的功能。
下边是一段我自己进行二次开发之后的读取类,供大家参考
import com.alibaba.nacos.api.NacosFactory;import com.alibaba.nacos.api.PropertyKeyConst;import com.alibaba.nacos.api.config.ConfigService;import com.alibaba.nacos.api.exception.NacosException;import com.alibaba.nacos.api.utils.StringUtils;import org.apache.shardingsphere.driver.jdbc.core.driver.ShardingSphereDriverURLProvider;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.Properties;/*** @Author idea* @Date: Created in 20:51 2023/6/4* @Description*/public class NacosDriverURLProvider implements ShardingSphereDriverURLProvider {private static Logger logger = LoggerFactory.getLogger(NacosDriverURLProvider.class);private static final String NACOS_TYPE = "nacos:";private static final String GROUP = "DEFAULT_GROUP";/*** @param url the driver url* (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test)* @return*/@Overridepublic boolean accept(String url) {return url != null && url.contains(NACOS_TYPE);}/*** 从url中获取到nacos的连接配置信息** @param url (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test)* @return*/@Overridepublic byte[] getContent(final String url) {if (StringUtils.isEmpty(url)) {return null;}//得到例如:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test 格式的urlString nacosUrl = url.substring(url.lastIndexOf(NACOS_TYPE) + NACOS_TYPE.length());/*** 得到三个字符串,分别是:* qiyu.nacos.com* 8848* qiyu-live-user-shardingjdbc.yaml*/String nacosStr[] = nacosUrl.split(":");String nacosFileStr = nacosStr[2];/*** 得到两个字符串* qiyu-live-user-shardingjdbc.yaml* username=qiyu&&password=qiyu&&namespace=qiyu-live-test*/String nacosFileProp[] = nacosFileStr.split("\?");String dataId = nacosFileProp[0];String acceptProp[] = nacosFileProp[1].split("&&");//这里获取到Properties properties = new Properties();properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosStr[0] + ":" + nacosStr[1]);for (String propertyName : acceptProp) {String[] propertyItem = propertyName.split("=");String key = propertyItem[0];String value = propertyItem[1];if ("username".equals(key)) {properties.setProperty(PropertyKeyConst.USERNAME, value);} else if ("password".equals(key)) {properties.setProperty(PropertyKeyConst.PASSWORD, value);} else if ("namespace".equals(key)) {properties.setProperty(PropertyKeyConst.NAMESPACE, value);}}ConfigService configService = null;try {configService = NacosFactory.createConfigService(properties);String content = configService.getConfig(dataId, GROUP, 6000);logger.info(content);return content.getBytes();} catch (NacosException e) {throw new RuntimeException(e);}}}import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.utils.StringUtils; import org.apache.shardingsphere.driver.jdbc.core.driver.ShardingSphereDriverURLProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Properties; /** * @Author idea * @Date: Created in 20:51 2023/6/4 * @Description */ public class NacosDriverURLProvider implements ShardingSphereDriverURLProvider { private static Logger logger = LoggerFactory.getLogger(NacosDriverURLProvider.class); private static final String NACOS_TYPE = "nacos:"; private static final String GROUP = "DEFAULT_GROUP"; /** * @param url the driver url * (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test) * @return */ @Override public boolean accept(String url) { return url != null && url.contains(NACOS_TYPE); } /** * 从url中获取到nacos的连接配置信息 * * @param url (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test) * @return */ @Override public byte[] getContent(final String url) { if (StringUtils.isEmpty(url)) { return null; } //得到例如:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test 格式的url String nacosUrl = url.substring(url.lastIndexOf(NACOS_TYPE) + NACOS_TYPE.length()); /** * 得到三个字符串,分别是: * qiyu.nacos.com * 8848 * qiyu-live-user-shardingjdbc.yaml */ String nacosStr[] = nacosUrl.split(":"); String nacosFileStr = nacosStr[2]; /** * 得到两个字符串 * qiyu-live-user-shardingjdbc.yaml * username=qiyu&&password=qiyu&&namespace=qiyu-live-test */ String nacosFileProp[] = nacosFileStr.split("\?"); String dataId = nacosFileProp[0]; String acceptProp[] = nacosFileProp[1].split("&&"); //这里获取到 Properties properties = new Properties(); properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosStr[0] + ":" + nacosStr[1]); for (String propertyName : acceptProp) { String[] propertyItem = propertyName.split("="); String key = propertyItem[0]; String value = propertyItem[1]; if ("username".equals(key)) { properties.setProperty(PropertyKeyConst.USERNAME, value); } else if ("password".equals(key)) { properties.setProperty(PropertyKeyConst.PASSWORD, value); } else if ("namespace".equals(key)) { properties.setProperty(PropertyKeyConst.NAMESPACE, value); } } ConfigService configService = null; try { configService = NacosFactory.createConfigService(properties); String content = configService.getConfig(dataId, GROUP, 6000); logger.info(content); return content.getBytes(); } catch (NacosException e) { throw new RuntimeException(e); } } }import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.utils.StringUtils; import org.apache.shardingsphere.driver.jdbc.core.driver.ShardingSphereDriverURLProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Properties; /** * @Author idea * @Date: Created in 20:51 2023/6/4 * @Description */ public class NacosDriverURLProvider implements ShardingSphereDriverURLProvider { private static Logger logger = LoggerFactory.getLogger(NacosDriverURLProvider.class); private static final String NACOS_TYPE = "nacos:"; private static final String GROUP = "DEFAULT_GROUP"; /** * @param url the driver url * (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test) * @return */ @Override public boolean accept(String url) { return url != null && url.contains(NACOS_TYPE); } /** * 从url中获取到nacos的连接配置信息 * * @param url (jdbc:shardingsphere:nacos:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test) * @return */ @Override public byte[] getContent(final String url) { if (StringUtils.isEmpty(url)) { return null; } //得到例如:qiyu.nacos.com:8848:qiyu-live-user-shardingjdbc.yaml?username=qiyu&&password=qiyu&&namespace=qiyu-live-test 格式的url String nacosUrl = url.substring(url.lastIndexOf(NACOS_TYPE) + NACOS_TYPE.length()); /** * 得到三个字符串,分别是: * qiyu.nacos.com * 8848 * qiyu-live-user-shardingjdbc.yaml */ String nacosStr[] = nacosUrl.split(":"); String nacosFileStr = nacosStr[2]; /** * 得到两个字符串 * qiyu-live-user-shardingjdbc.yaml * username=qiyu&&password=qiyu&&namespace=qiyu-live-test */ String nacosFileProp[] = nacosFileStr.split("\?"); String dataId = nacosFileProp[0]; String acceptProp[] = nacosFileProp[1].split("&&"); //这里获取到 Properties properties = new Properties(); properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosStr[0] + ":" + nacosStr[1]); for (String propertyName : acceptProp) { String[] propertyItem = propertyName.split("="); String key = propertyItem[0]; String value = propertyItem[1]; if ("username".equals(key)) { properties.setProperty(PropertyKeyConst.USERNAME, value); } else if ("password".equals(key)) { properties.setProperty(PropertyKeyConst.PASSWORD, value); } else if ("namespace".equals(key)) { properties.setProperty(PropertyKeyConst.NAMESPACE, value); } } ConfigService configService = null; try { configService = NacosFactory.createConfigService(properties); String content = configService.getConfig(dataId, GROUP, 6000); logger.info(content); return content.getBytes(); } catch (NacosException e) { throw new RuntimeException(e); } } }
最后记得引入满足ShardingJdbc的spi配置文件:
关于引入ShardingJdbc+HikariDataSource连接池后无法自启动初始化数据源问题
这个问题是出现在,我引入了ShardingJdbc+HikariDataSource配合使用的数据库连接时功能之后才发现的,发现当服务启动的时候,ShardingJdbc的连接池没有进行初始化,这样会导致如果请求量突然上来的话,连接建立过慢,从而导致性能跟不上。
这里我也在网上查了很多资料,后来在github上看到了相关的issue提示,说是一个官方的bug,不过可以通过以下方式进行补救:(issue地址:github.com/spring-proj…)
@Configurationpublic class ShardingJdbcDatasourceAutoInitConnectionConfig {private static final Logger LOGGER = LoggerFactory.getLogger(ShardingJdbcDatasourceAutoInitConnectionConfig.class);@Beanpublic ApplicationRunner runner(DataSource dataSource) {return args -> {LOGGER.info("dataSource: {}", dataSource);//手动触发下连接池的连接创建Connection connection = dataSource.getConnection();};}}@Configuration public class ShardingJdbcDatasourceAutoInitConnectionConfig { private static final Logger LOGGER = LoggerFactory.getLogger(ShardingJdbcDatasourceAutoInitConnectionConfig.class); @Bean public ApplicationRunner runner(DataSource dataSource) { return args -> { LOGGER.info("dataSource: {}", dataSource); //手动触发下连接池的连接创建 Connection connection = dataSource.getConnection(); }; } }@Configuration public class ShardingJdbcDatasourceAutoInitConnectionConfig { private static final Logger LOGGER = LoggerFactory.getLogger(ShardingJdbcDatasourceAutoInitConnectionConfig.class); @Bean public ApplicationRunner runner(DataSource dataSource) { return args -> { LOGGER.info("dataSource: {}", dataSource); //手动触发下连接池的连接创建 Connection connection = dataSource.getConnection(); }; } }
在服务启动的初始化阶段中,触发下getConnection的操作即可。
关于MyBatis-Plus版本的升级
由于升级大了 JDK17,所以连我们的ORM框架MyBatis-Plus也是需要进行调整的。具体只需要升级下Maven依赖即可:
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version></dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency>
Java反射机制的调整
相信刚入坑JDK17的朋友,大多数都会在服务运行的时候看到这么一条提示:
java.base does not “opens java.util“ to unnamed module
通过反射访问JDK模块内类的私有方法或属性,且当前模块并未开放指定类用于反射访问,就会出现以上告警。解决方式也必须使用模块化相关知识,可以使用遵循模块化之间的访问规则,也可以通过设置 –add-opens java.base/java.lang = ALL-UNNNAMED 破坏模块的封装性方式临时解决;