马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
背景
随着公司业务的不断扩展,新技术的更新换代,企业内部免不了会对软硬件举行升级,淘汰老旧的组件和实现方案,更新一波技术栈。这不,最近我们公司就面临这么一个难题:旧版本的组件上发现毛病,为了修复体系毛病,不得不对它举行版本升级。而升级某个组件时,又会因为版本不兼容,配置更新等缘故原由,必须同步调整其他的组件或配置。因此,看似简朴的升级一个组件,却像是推倒了多米诺骨牌似的,越改越多,越理越乱。而受影响的项目又多,每个项目都得有技术人员花费大量时间行止理这种自制而繁琐的事情,事倍而功半。公司的CTO知道这事后,觉得长痛不如短痛,被动防守不如自动出击,咱干脆借着处置惩罚这件事,来一次架构升级,把SpringBoot改成SpringBoot3,JVM从1.8升到17,把公司该升的组件都升了。
而鄙人,就悲催荣幸的被委予此项重任。
框架升级
一、需求调研
框架是为了业务需求而服务的,一个好的框架能为整个研发团队降本增效,用更便利,更优雅的方式实现需求。 因此,在升级框架之前,必须得举行一轮需求调研。我接洽了公司几个产物线的Leader,通过收集当前产物线所用框架中包含的组件,以及公司的业务诉求,推导出了新框架的建设清单:
新框架技术栈 | 版本 | 种别 | 说明 | 当前现状 | JDK | 17 | 后端 | Java开发情况 | JDK 1.8 | Spring Boot | 3.1.9 | 后端 | 后端研发框架 | Spring Boot 2.1.6.RELEASE | Spring Cloud Alibaba | 2022.0.0.0 | 后端 | 微服务框架 | Spring Cloud 2.1.2.RELEASE | Nacos | 2.3.2 | 中间件 | 配置中央 & 注册中央 | Apollo 1.1.0+Eureka2.1.2.RELEASE | RocketMQ | 5.1.0 | 中间件 | 消息队列 | 不涉及 | Sentinel | 1.8.6 | 开源底子组件 | 服务保障/容错 | 不涉及 | XXL-JOB | 2.4.0 | 中间件 | 定时使命 | XXL-JOB 2.4.1-SNAPSHOT | Spring Cloud Gateway | 4.0.6 | 中间件 | 服务网关 | 3.0.6 | seata | 1.6.1 | 中间件 | 分布式事务 | 不涉及 | DM | V8 | 数据库 | 数据库服务器 | MYSQL | Druid | 1.2.22 | 底子组件 | JDBC 连接池、监控组件 | Druid 1.1.10 | Dynamic DataSource | 4.3.0 | 底子组件 | 动态数据源,可以实现数据源动态切换 | 不涉及 | Redis | 6.2.7 | 数据库 | key-value 数据库 | 6.2.7 | redisson | 3.24.3 | 工具 | Redis 客户端 | redisson 3.11.2 | hibernate-validator | 8.0.1 | 底子组件 | 参数校验组件 | hibernate-validator 6.0.17 Final | flowable | 7.0.0 | 底子组件 | 工作流引擎 | flowable 6.8.0 | knife4j | 4.4.0 | 底子组件 | Swagger 增强 UI 实现 | Swagger1.7.0 | Apache SkyWalking | 9.7.0 | 中间件 | 分布式应用追踪体系 | 不涉及 | Plumelog | 3.5 | 中间件 | 同一日志平台 | Logstash 5.3 | fastjson2 | 2.0.50 | 底子组件 | JSON 工具库 | fastjson2 1.2.13 | MapStruct | 1.5.5.Final | 底子组件 | Java Bean 转换 | MapStruct 1.5.2.Final | Project Lombok | 1.18.30 | 底子组件 | 消除冗长的 Java 代码 | Project Lombok 1.18.8 | JUnit | 4.13.2 | 底子组件 | Java 单元测试框架 | 4.12 | mockito | 5.7.0 | 底子组件 | Java Mock 框架 | 不涉及 | Mybatis-Plus | 3.5.5 | 底子组件 | 数据库利用组件 | Mybatis-Plus 3.4.6,tk.mybatis 2.1.5 | Nginx | 1.24 | 中间件 | 反向代理,负载均衡,请求转发 | Nginx 1.13.7 | poi | 5.2.5 | 底子组件 | 文档在线处置惩罚组件 | poi 4.1.0 | easyexcel | 3.3.4 | 底子组件 | excel在线处置惩罚组件 | easyexcel 2.1.6 | codec | 1.17.0 | 底子组件 | 数据编码解码组件 | 不涉及 | hutool | 5.8.27 | 底子组件 | 开源工具包 | hutool 5.3.0 | commons-net | 3.11.1 | 底子组件 | Ftp连接组件 | 不涉及 | beanutils | 1.9.4 | 底子组件 | Java底子类库 | beanutils 1.9.3 | aviator | 5.4.1 | 底子组件 | 表达式处置惩罚组件 | 不涉及 | jjwt | 0.12.5 | 底子组件 | 数字署名组件 | jwt 0.9.0 | OAuth2 | 6.3.1 | 中间件 | 认证服务 | OAuth2 2.3.3.RELEASE | minio | 8.5.10 | 底子组件 | 对象存储服务器 | 与新框架同等 | pagehelper | 2.1.0 | 底子组件 | 分页组件 | 与新框架同等 | 在规划这些组件/依赖的版本时,我直接根据名字去https://mvnrepository.com/中查询当前组件的最高版本,究竟JDK,Springboot都是用的比较新的版本,完全Hold住……于是后面就踩坑了,至于是什么坑,请看下章分解。
二、父工程搭建
有了上面的清单,就可以搭建父工程了,父工程实在很简朴,一个pom.xml足矣。而pom.xml里的内容,就是上面的清单的具象化。如下:
其着实搭建父工程时,我并不太明白这个父工程的意义,感觉有它没它区别不大。但是在撰写这篇博客的背景章节时,我幡然觉醒,父工程不就是整理了一套可以搭配起来利用的组件及版本清单吗?有了父工程做背书,子工程只需要根据自己的需求引入父工程指定的依赖即可,而不消担心引入的依赖会不会和其他依赖冲突,会不会因此导致服务无法启动,会不会引入毛病,这不就从根本上简化了利用吗?
三、子工程调试
创建好了父工程,并不代表它肯定是可用的。上文已经说过,在整理依赖组件时,所有的版本都是能取最新取最新,但有大概很多最新版的依赖相互之间并不兼容,以是还需要举行肯定的调试。于是我先对父工程举行mvn install,再引用父工程创建了一个子工程,如下:
主要是编写上图中的pom文件,留意要引用父工程,并把所有的依赖包都转过来。有条件的还可以再写个增编削查功能,如下:
- <!--CommonMapper.xml-->
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.leixi.leixihub.dao.CommonMapper">
- <select id="getDataBySql" resultType="java.util.Map">
- ${sql}
- </select>
- <update id="updateDataBySql">
- ${sql}
- </update>
- </mapper>
- //这里是 CommonMapper.java
- package com.leixi.leixihub.dao;
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import org.apache.ibatis.annotations.Mapper;
- import org.apache.ibatis.annotations.Param;
- import java.util.List;
- import java.util.Map;
- /**
- *
- * @author leixiyueqi
- * @since 2024/8/5 19:39
- */
- @Mapper
- public interface CommonMapper extends BaseMapper {
- List<Map<String, Object>> getDataBySql(@Param("sql") String sql);
-
- void updateDataBySql(@Param("sql") String sql);
- }
- // 这里是Controller方法
- @GetMapping("/getDataBySql")
- public Object getDataBySql(@RequestParam(value = "sql") String sql) {
- return commonMapper.getDataBySql(sql);
- }
复制代码
四、接口测试
有了这些代码和依赖,只要项目能成功启动,增编削查能顺利执行,就说明这个架子初步搭成了。
五、依赖包冲突解决和版本调整
虽说项目可以起来,但这并不代表这个父工程是安全可用的,主要缘故原由有两个:
1)引用的依赖包也不肯定是安全,大概存在毛病。
2)各依赖包之间大概存在依赖冲突的题目。
咱们基于以上两点,对这份依赖文件举行一次复查,逐个解决和排查题目。
1、对于存在毛病,大概依赖的资源有毛病的包,需要更换其版本,大概更换其依赖的子包的版本,大概更换另一个jar包。对于这些有题目的包,Idea的pom文件里都会有较显着的提示,一般标黄底的都是有些毛病的,如下图:
2、检查和处置惩罚依赖冲突,点击Idea2023右上角的图标,可以查看当前有冲突的依赖信息:
如下图,minio-8.5.5和jetcache-starter-redis-2.7.5都依赖checker-qual包,但是利用的版本不一样:
这种情况下,有两种解决方案,
1) 降低jetcache-starter-redis的版本号,大概提升minio的版本号,让它们依赖的checker-qual变得一样,可以在https://mvnrepository.com/中查询各包依赖的版本号,如下:
2)扫除minio中低版本的checker-qual依赖,这需要更改pom.xml里的配置,如下:
六、回写父工程
根据上面的方法把依赖信息调整之后,肯定要记得把相干变革都更新到父工程leixi-hub-parent里,克制每个子类都要举行类似的配置和调整。测试证实,只要父工程配好了<exclusions>,子工程里不消做这些配置,也不会显示冲突项。
SpringBoot2转3的方法
除了上文中说到了升级相干情况、依赖的版本,在对实际工程举行升级时,尤其是对从前的SpringBoot2.X升级到3.X时,还需要留意以下方面:
1. 升级 JDK 17
Spring Boot 3.0 需要 Java 17 作为最低版本。如果当前利用的是 Java 8 或 Java 11,则需要在 Spring Boot 迁移之前升级 JDK。
2. 升级到 Spring Boot 3
查看项目及其依赖项的状态后,要升级到 Spring Boot 3.0 的最新维护版本。对于不需要父工程的项目,可以这么写:
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>3.2.0</version>
- </parent>
复制代码
3. 配置属性迁移
在 Spring Boot 3.0 中,一些配置属性被重命名/删除,开发人员需要相应地更新其 application.properties/application.yml。为了快速实现这一点,Spring Boot 提供了一个 spring-boot-properties-migrator 模块。咱可以通过将以下内容添加到 Maven pom.xml 来添加迁移器:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-properties-migrator</artifactId>
- <scope>runtime</scope>
- </dependency>
复制代码
4. 升级到 Jakarta EE
由于 Java EE 已更改为 Jakarta EE,Spring Boot 3.x 的所有依赖项 API 也从 Java EE 升级为 Jakarta EE。代码中需要将所有 javax 的 imports 都更换为 jakarta。具体如下:
- javax.persistence.* -> jakarta.persistence.*
- javax.validation.* -> jakarta.validation.*
- javax.servlet.* -> jakarta.servlet.*
- javax.annotation.* -> jakarta.annotation.*
- javax.transaction.* -> jakarta.transaction.*
复制代码
5. 调整ConstructorBinding注解
@ConstructorBinding 在 @ConfigurationProperties 类的范例级别不再需要,应将其删除。
当一个类或记录有多个构造函数时,它仍然可以在构造函数上利用,以指示应利用哪一个构造函数举行属性绑定。
6. 尾部斜杠URL匹配更改
从 Spring Framework 6.0 开始,尾部斜杠匹配配置选项已为 deprecated,其默认值设置为 false。这意味着从前,以下控制器将匹配GET /health和GET /health/
- @RestController
- public class HealthController {
- @GetMapping("/health")
- public String health() {
- return "Application is Working";
- }
- }
复制代码
7. RestTemplate 调整
Spring Framework 6.0 中已删除对 Apache HttpClient 的支持,现在由 org.apache.httpcomponents.client5:httpclient5 代替。如果 HTTP 客户端活动存在题目,则 RestTemplate 大概会回退到 JDK 客户端。org.apache.httpcomponents:httpclient 可以由其他依赖项传递传递,因此在应用程序大概依赖此依赖项而不声明它。
下面是迁移后的RestTemplate示例:
- import org.apache.hc.client5.http.config.RequestConfig;
- import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
- import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
- import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
- import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
- import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
- import org.apache.hc.client5.http.impl.classic.HttpClients;
- import org.apache.hc.core5.util.Timeout;
- import org.springframework.cloud.client.loadbalancer.LoadBalanced;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
- import org.springframework.web.client.RestTemplate;
- /**
- *
- * @author leixiyueqi
- * @since 2024/8/5 21:39
- */
- @Configuration
- public class RestTemplateConfig {
- @LoadBalanced
- @Bean
- public RestTemplate restTemplate() {
- return buildTemplate(10000, 10000,3);
- }
- private RestTemplate buildTemplate(long requestTimeout, long connectTimeout, int retryTimes) {
- final SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactoryBuilder.create()
- .build();
- final PoolingHttpClientConnectionManager manager = PoolingHttpClientConnectionManagerBuilder.create()
- .setSSLSocketFactory(sslConnectionSocketFactory)
- .build();
- final CloseableHttpClient closeableHttpClient = HttpClients.custom()
- .setDefaultRequestConfig(RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(requestTimeout))
- .setConnectTimeout(Timeout.ofMilliseconds(connectTimeout)).build())
- //.setRetryStrategy(new DefaultHttpRequestRetryStrategy(retryTimes, NEG_ONE_SECOND))
- .setConnectionManager(manager)
- .build();
- final HttpComponentsClientHttpRequestFactory componentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
- componentsClientHttpRequestFactory.setHttpClient(closeableHttpClient);
- final RestTemplate restTemplate = new RestTemplate(componentsClientHttpRequestFactory);
- return restTemplate;
- }
- }
复制代码
8. 升级 Spring Security
Spring Boot 3.0 已升级到 Spring Security 6.0。因此,WebSecurityConfigurerAdapter 已被弃用。 Spring鼓励用户转向基于组件的安全配置。可利用 Spring Security lambda DSL 和方法 HttpSecurity#authorizeHttpRequests 来定义自己的授权规则。
下面是利用 WebSecurityConfigurerAdapter 的示例配置,它通过 HTTP Basic 掩护所有端点:
- @Configuration
- public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeHttpRequests((authz) -> authz
- .anyRequest().authenticated()
- )
- .httpBasic(withDefaults());
- }
- }
复制代码 猜测未来,推荐的方法是注册一个 SecurityFilterChain bean:
- @Configuration
- public class SecurityConfiguration {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http
- .authorizeHttpRequests((authz) -> authz
- .anyRequest().authenticated()
- )
- .httpBasic(withDefaults());
- return http.build();
- }
- }
复制代码
9. Spring Kafka 模板升级
KafkaTemplate 方法现在返回 CompleteableFuture 而不是 ListenableFuture,后者已被弃用。Spring Boot 2.x 中带有 ListenableFuture 的 Kafka 模板:
- private RoutingKafkaTemplate routingKafkaTemplate;
- public void send(){
- ListenableFuture<SendResult<Object,Object>> future = routingKafkaTemplate.send("Message","topic");
- future.addCallback(new ListenableFutureCallback<>() {
- @Override
- public void onFailure(Throwable ex) {
- log.error(ex);
- }
- @Override
- public void onSuccess(SendResult<Object, Object> result) {
- log.info("success");
- }
- });
- }
复制代码 Spring Boot 3.x 中带有 CompletableFuture 的 Kafka 模板:
- private RoutingKafkaTemplate routingKafkaTemplate;
- public void send() {
- CompletableFuture<SendResult<Object, Object>> future = routingKafkaTemplate.send("Message", "topic");
- future.thenAccept(log::info)
- .exceptionally(exception -> {
- log.error(exception);
- return null;
- });
- }
复制代码
10. Spring Doc OpenAPI 升级
springdoc-openapi用于为Spring Boot 项目自动天生 API 文档。 springdoc-openapi的工作原理是在运行时检查应用程序,以根据 spring 配置、类布局和各种解释推断 API 语义。对于 spring-boot 3 支持,请确保利用 springdoc-openapi v2。对于 WebMVC 项目,需要在 pom.xml. 文件中包含以下依赖项。
- <dependency>
- <groupId>org.springdoc</groupId>
- <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
- <version>2.3.0</version>
- </dependency>
复制代码 对于 WebFlux 项目,您需要在 pom.xml. 文件中包含以下依赖项。
- <dependency>
- <groupId>org.springdoc</groupId>
- <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
- <version>2.3.0</version>
- </dependency>
复制代码
参考资料
以下是相干参考资料,感谢大佬们的倾情整理。
参考0:jdk8升级JDK17避坑指南
参考1:重磅!Spring Boot 2.7 正式发布
参考2:hutool-盼望Hutool能支持下JDK8~JDK17的所有版本
参考3:一文详解|从JDK8飞升到JDK17,再到未来的JDK21
参考4:从 Java 8 升级到 Java 17 踩坑全过程,发起收藏!
参考5:老卫waylau-JDK
参考6:java - Spring Boot 2.x 到 3.2 的全面升级指南
后记
抛开过程中的困难和曲折不说,这是一次酣畅淋漓,难得且难忘的一次机会,一个中小型公司终其一生,能有几次这么升级框架的机会?又有多少程序员能有这样的经历?讲真,我是很感激老大能给我这个机会的。通过这次升级,我对于SpringBoot项目的团体架构,父子工程关系等都有了更深的熟悉,对依赖整理,毛病扫除也有了相干的积累。苦点累点没什么,获得的成就感却是满满的,将来,公司所有的项目,都将在整理的工程底子上举行建设,我不就是名副实在的奠定人了吗?(可把我给牛批坏了!)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |