背景:应部门要求微服务项目注册中心及配置中心需迁移至nacos,开发运维相分离,配置由运维组维护。
1 springBoot升级
1.1 版本变更
Nacos与SpringBoot、SpringCloud版本依赖关系(推荐使用),中间件部门提供的nacos版本为1.4 .2
版本依赖请参考:
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
原springBoot版本:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent>
升级后springBoot版本:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> </parent>
1.2 升级问题汇总
1)dom4j
问题描述:打包时提示找不到dom4j的jar包,之前没有写版本号。
问题原因:应该是被spring-boot-dependencies,2.6.3之前的某个版本移除了。
解决方案:增加版本号1.6.1
<dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency>
2)tomcat
问题描述:在项目启动时tomcat一直报错,提示无法定位。
问题原因:因为父pom的tomcat版本死了为8.5.40。
解决方案:删除此行代码即可(springBoot会指定默认版本)。
<tomcat.version>8.5.40</tomcat.version>
3)jackson-mapper-asl
问题原因:此依赖已由SpringBoot默认引入。
解决方案:移除。
<dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> </dependency>
4)jackson-annotation
问题原因:该依赖未被使用。
解决方案:移除。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotation</artifactId> </dependency>
5)配置文件环境激活
问题描述:springBoot 2.4版本之前,我们在yaml配置文件中,使用spring.profiles.active
来定义不同环境的标识,比如下面这样
spring: profiles: active:dev
而在本次2.4版本升级之后,SpringBoot为了提升对K8S的支持,我们需要将spring.profiles.active
配置用spring.config.activate.on-profile
替代(貌似不改也能生效,但是启动日志会有warn,所以最好改一下)。
spring: config: activate: on-profile: dev
6)Bean重复定义
问题描述:启动报错,微服务多个组件的openFeign都存在feign的重复定义问题,导致项目启动失败。
# 此注解就会生成一个Bean加载到IOC容器中,同名的Bean在springBoot2.1.0之前版本就会默认覆盖 @FeignClient(name = "xxxx")
问题原因:在springboot2.1.0之前,allowBeanDefinitionOverriding初始化为true,遇到重复的bean,会默认使用下一个bean的名字覆盖上一个(可以在wechatweb启动日志中查看)。springboot2.1.0以后的allowBeanDefinitionOverriding是没有初始化默认值的,所以是false,也就是不支持名称相同的bean被覆盖。
解决办法:在配置文件中将该值设置为true。
spring.main.allow-bean-definition-overriding = true 就是允许定义相同的bean对象去覆盖原有的
7)循环依赖
问题描述:多个组件启动时报错,控制台提示代码中有循环依赖。
@Service public class xxxService implements xxxService { // @Value可以正常获取配置文件中的值 @Value("${common.a}") private String a; @Value("${common.b}") private String b; @Value("${common.c}") private String c; @Value("${common.d}") private String d; // SpringBoot升级后该Bean的加载在@Value之前,所以获取到的值都是null @Bean private OssConfig ossConfig() { OssConfig ossConfig = new OssConfig(); ossConfig.setEndPoint(a); ossConfig.setAccessKeyId(b); ossConfig.setAccessKeySecret(c); ossConfig.setBucketName(d); return ossConfig; } @Autowired OssConfig ossConfig; }
下面为报错内容
************************* Description: The dependencies of some of the beans in the application context form a cycle: xxxController ┌─────┐ | xxxService (field com.xxx.common.model.OssConfig com.xxx.xxx.service.impl.xxx.xxxService.ossConfig) └─────┘ Action: Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true. 不鼓励使用循环引用,默认情况下禁止使用循环引用。更新您的应用程序以删除bean之间的依赖循环。作为最后的手段,可以通过设置spring.main.allow-circular-references允许循环引用为true。
问题原因:实例化该Service时需要先实例化该类中的Ossconfig,而Ossconfig又需要先加载这个Service,导致循环依赖。在2.6.0之前,spring会自动处理循环依赖(单例)的问题,2.6.0 以后的版本默认禁止 Bean 之间的循环引用,如果存在循环引用就会启动失败报错。
解决方案:
方案一
配置文件设置spring.main.allow-circular-references=true
采用方案一后启动不再报错,但是在测试中发现@Bean的方法无法获取到@Value中的值。经查,Springboot 中使用 @Configruation 和 @Bean 一起将 Bean 注册到 ioc 容器中,而 @Value 常用于将 yml 配置文件中的配置信息注入到类的成员变量中。当 @Configruation、@Bean 和 @Value 出现在同一个类中时,@Bean 会比 @Value 先执行,这会导致当 @Bean 注解的方法中用到 @Value 注解的成员变量时,无法注入(null)的情况。故采用方案二
方案二
以config形式单独抽取出来,同样可以实现IOC注入。
package com.xxx.xxx.config; import com.xxx.common.model.OssConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OssBeanConfig { @Value("${common.a}") private String a; @Value("${common.b}") private String b; @Value("${common.c}") private String c; @Value("${common.d}") private String d; @Bean public OssConfig ossConfig() { OssConfig ossConfig = new OssConfig(); ossConfig.setEndPoint(a); ossConfig.setAccessKeyId(b); ossConfig.setAccessKeySecret(c); ossConfig.setBucketName(d); return ossConfig; } }
8)引申HikariCP
HikariCP数据库连接池问题。
问题描述:因为要对数据库密码进行加密,所以重构了Hikari的配置,但是据库连接池使用的是HikariCP的默认值(池中最小最大都是10个连接数),而非配置文件中设定的值。
// 配置文件 spring: application: name: xxx datasource: url: ... driver-class-name: com.mysql.jdbc.Driver username: xxx password: xxx hikari: maximum-pool-size: 4 # 最大连接数 minimum-idle: 1 # 最小连接数 idle-timeout: 600000 max-lifetime: 1800000 connection-timeout: 500 login-timeout: 500 validation-timeout: 1000 initialization-fail-timeout: 1000
// 原代码 @Configuration public class DataSourceConfig { private static String KEY = "..."; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Bean public HikariDataSource dataSource(){ HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setJdbcUrl(url); dataSource.setUsername(username); dataSource.setPassword(String.valueOf(decode(password))); return dataSource; } }
问题原因:HikariCP是SpringBoot自带的数据源 ,如果我们重写数据源则会覆盖它本身的数据源,但是我们在重写数据源时没有将Hikari的值注入。
解决方案:
方案一
// 实现InitializingBean接口,在Bean初始化时修改数据库密码 @Configuration public class DataSourceConfig implements InitializingBean { @Value("${spring.datasource.password}") private String password; @Override public void afterPropertiesSet() throws Exception { HikariDataSource dataSource = new HikariDataSource(); dataSource.setPassword(String.valueOf(decode(password))); } }
方案二
@Configuration public class DataSourceConfig { private static String KEY = "jaas is the way"; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Bean // 通过注解@ConfigurationProperties("spring.datasource.hikari")获取配置文件中的值 @ConfigurationProperties("spring.datasource.hikari") public HikariDataSource dataSource(){ HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setJdbcUrl(url); dataSource.setUsername(username); log.info(String.valueOf(decode(password))); dataSource.setPassword(String.valueOf(decode(password))); return dataSource; } }
注:连接池并不会在启动后立刻生成,只有在第一次与数据库建立连接时才会在连接池中生成配置的最小连接数。
9)数据库驱动升级
问题描述:在日志中发现warn提示mysql驱动问题
[WARN] --- [Thread-19][com.zaxxer.hikari.util.DriverDataSource-70] [] [] []: Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
问题原因:springBoot默认指定的mysql-connector版本从5升到了8
//spring默认指定的mysql-connector版本 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency>
解决方案:
将spring.datasource.driver-class-name=com.mysql.jdbc.Driver 改成spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
10)javax.mail
javax.mail由springBoot统一管理,升级后改名为jakarta.mail
<dependency> <groupId>com.sun.mail</groupId> <artifactId>jakarta.mail</artifactId> </dependency>
2 springCloud升级
2.1 版本变更
原springCloud版本:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR2</version> <type>pom</type> <scope>import</scope> </dependency>
升级后springCloud版本:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2021.0.1</version> <type>pom</type> <scope>import</scope> </dependency>
2.2升级问题汇总
1)Netflix-Ribbon被移除
问题描述:Ribbon被高版本OpenFeign及Nacos移除,同时微服务设置的ribbon超时也失效。
// 原ribbon配置文件 ribbon: ConnectTimeout:60000 ReadTimeOut:60000
解决方案:引入最新的负载均衡组件spring-cloud-starter-loadbalancer,超时设置为openFeign超时,而不是负载均衡超时,可以达到同样的效果。
// loadbalancer依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
// openFeign超时设置 feign: client: config: default: connectTimeout: 60000 readTimeout: 60000
2)bootstrap.yml不读取
问题原因:从Spring Boot 2.4版本开始,配置文件加载方式进行了重构。另外也有配置的默认值变化,原来默认启用 true 现在变更为 false 如下:
version:2.4之前
package org.springframework.cloud.bootstrap; public class BootstrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered { public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) { } } }
version:2.4.2
package org.springframework.cloud.util; public abstract class PropertyUtils { public static boolean bootstrapEnabled(Environment environment) { return (Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, false) || MARKER_CLASS_EXISTS; } }
解决办法 官方说明:Spring Cloud Config
1、pom文件中引入如下配置
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
2、指定运行参数 spring.cloud.bootstrap.enabled 值为 true
1)在IDEA中:
spring.cloud.bootstrap.enabled=true
2)启动命令中:
java -jar -Dspring.cloud.bootstrap.enabled=true xxx.jar
我们选择了第一种方法,即添加dependecy方式,认为这种方法更加简单有效。
3.1 依赖变更
迁移原因:Spring Cloud 下的 Netflix Eureka 2.0组件项目宣布闭源,同时Spring也不再对Netflix下的部分组件进行维护。
解决方案:移除Eureka依赖以及Netflix的所有组件,引入alibaba相关依赖,修改配置文件。
// 在父pom引入依赖 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2021.0.1.0</version> <type>pom</type> <scope>import</scope> </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>
// 配置文件 spring: config: activate: on-profile: uat //激活环境 cloud: nacos: username: //用户名 password: //密码 discovery: //注册 server-addr: localhost:8847 //注册中心ip端口 namespace: //命名空间 group: config: //配置 server-addr: localhost:8847 //配置中心ip端口 file-extension: yaml //nacos配置文件后缀 namespace: //命名空间 group: shared-configs: //共享配置 - data-id: group: - data-id: group:
配置中心
注册中心
3.2 问题汇总
1)配置刷新
Nacos与Apollo不一样,它默认不支持配置的动态刷新,需要在使用@Value注解的方法或类上加上@RefreshScope注解。
2)配置类中@ConfigurationOnProperties与@RefreshScope冲突
如果在配置类中已经用了@ConfigurationOnProperties再用@RefreshScope会导致无法注册Bean,原因是@ConfigurationOnProperties已有动态刷新功能,不可与@RefreshScope一起用。
@Configuration //@RefreshScope public class DataSourceConfig { private static String KEY = "jaas is the way"; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Bean @ConfigurationProperties(prefix = "spring.datasource.hikari") public HikariDataSource dataSource() throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setJdbcUrl(url); dataSource.setUsername(username); log.info(String.valueOf(decode(password))); dataSource.setPassword(String.valueOf(decode(password))); return dataSource; } }
3)@EnableEurekaClient
启动类中的@EnableEurekaClient需要改成@EnableDiscoveryClient。
//@EnableEurekaClient @EnableDiscoveryClient @SpringBootApplication public class xxxApplication { public static void main(String[] args) { SpringApplication.run(xxxApplication.class, args); } }