springboot版本升级遇到的问题和注意
由于近期接到需求把项目全部升级到springboot2.x版本,经过不断的尝试和排错,总结到一些经验,现分享给大家。避免大家采坑。(我是从springboot1.5.7版本升级到springboot2.1.9版本)
由于大版本的升级,许多组件和配置都不能正常使用。所以要适应版本的更改,就要做一些相应的处理。
注意springboot2.x版本的jdk是1.8+,maven版本3.6.1+
不多说直接来!
下面是pom依赖的升级和相关配置文件的更改
Springboot 服务项目
1.Springboot版本升级相对应的parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
相对应的项目访问路径content-path也有更改
server.context-path变成server.servlet.context-path
2.eureka
springboot1.5.7版本对应的pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
springboot2.1.9版本对应的pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka.instance.preferIpAddress=true
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
eureka.client.serviceUrl.defaultZone=http://部署的eureka ip:端口/eureka/
3.日志处理
springboot2.x版本有对日志进行集成,所以不需要引用太多的pom依赖对日志进行处理
<dependency> <!-- 引入log4j2依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency><!-- 异步日志支持 -->
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.6</version>
</dependency>
4.config
这里我连接的是配置中心。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=api-config-server
spring.cloud.config.profile=test
spring.cloud.config.enabled=true
5.actuator
如果想对您的项目进行监控可以加上actuator依赖,可以通过访问你设置的监控地址对您的项目进行监控
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.server.port=8081
management.endpoint.shutdown.enabled=true
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=*
**注意,此配置只能放在bootstrap.properties。放入其他的配置文件中项目在启动的时候会扫描不到,以至于将不能监控您的项目。 **
6.security
security是一个神奇的存在,大家想了解它的具体功能可以去查官方文档。(慎用!!!!!)
由于升级的项目中存在cas登录,所以加上security依赖之后会存在跳不过去的情况。因为加上security依赖之后,会先对security进行验证,而原来在springboot1.5.7版本中在配置文件中配置的security账号和密码会全部失效。由于时间比较紧,新的配置还没有研究,有相关经验的小伙伴或者有好的解决办法可以进行交流。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
失效配置:
#security.user.name=123
#security.user.password=123
7.redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
由于我采用的是jedis连接,所以把lettuce排除掉了,引入jedis依赖。
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.min-idle=100
还有代码级别的改动:
RedisCacheManager配置。下面给了两种方式:
public RedisCacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
//过期时间单位毫秒
redisCacheManager.setDefaultExpiration(72000);
redisCacheManager.setUsePrefix(true);
redisCacheManager.setCachePrefix(new DefaultRedisCachePrefix());
ArrayList<String> cacheNames = new ArrayList<>();
cacheNames.add("common-cache-redis-caching");
}
--------------------------------------------------------
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config.entryTtl(Duration.ofMillis(72000));
config.computePrefixWith(cacheName -> appName.concat(":").concat(cacheName).concat(":"));
Set<String> cacheNames = new HashSet<>();
cacheNames.add("common-cache-redis-caching");
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("common-cache-redis-caching", config);
RedisCacheManager cacheManager = RedisCacheManager.builder(redisTemplate.getConnectionFactory())
.initialCacheNames(cacheNames)
.withInitialCacheConfigurations(configMap)
.build();
return cacheManager;
}
8.mysql
由于版本的升级,sql的连接也有了相应的改动。
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.datasource.username=123456
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
连接驱动由原来的com.mysql.jdbc.Driver更改为com.mysql.cj.jdbc.Driver。在这里建议大家配置时区,这样会减少因为时区不一致,而导致的时间相差8小时serverTimezone=Asia/Shanghai
9.guava
由于项目升级到2.x版本,也就是说Spring升级到了5.+的版本.那么Spring4.+版本的guava cache已经失效,需要换成对应的5.+版本.Spingboot1.x 对应的guava的pom依赖和相应的代码都有相应的变动
Springboot1.x:
public GuavaCacheManager guavaCacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
//规定了缓存在1小时没有使用的情况下进行回收
//规定了缓存的最大容量
cacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).maximumSize(100000));
ArrayList<String> cacheNames = new ArrayList<>();
cacheNames.add("common-cache-guava-caching");
cacheManager.setCacheNames(cacheNames);
return cacheManager;
}
在使用springboot-caffeine前,需要导包,pom.xml依赖如下:
<!--原有的spring-boot-starter-cache依赖不变-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--新增caffeine依赖-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
使用springboot-caffeine的代码如下,配置与使用guava cache是等同的。有关于caffeine更多的配置或详细信息,请参考caffeine官方文档
public CaffeineCacheManager guavaCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
Caffeine caffeine = Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(100000)
.expireAfterWrite(1, TimeUnit.HOURS);
cacheManager.setCaffeine(caffeine);
ArrayList<String> cacheNames = new ArrayList<>();
cacheNames.add("common-cache-guava-caching");
cacheManager.setCacheNames(cacheNames);
return cacheManager;
}
!!!!上述这些配置服务升级基本全部搞定,但你会发现在启动的时候会报错。那么在bootstrap.propertits中加入一行配置,你的项目就会正常启动
spring.main.allow-bean-definition-overriding=true
、
10.时间问题
所有项目的升级可能会导致时间的问题,比如返回的时间变成了时区格式
如果是Web项目,那么请前端的同学修改一下返回格式,如果是后台服务返回不做展示的话想变成dateTime格式只需要编写如下代码。
public void timeHadle(List<Object> list, Map<String,Object> map) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
if (list == null || list.isEmpty()){
return;
}
for(Object obj : list) {
map = (Map<String, Object>) obj;
if(map.get("sendTime") != null){
String sendTime = map.get("sendTime").toString();
Date parseSendTime = simpleDateFormat1.parse(sendTime);
String formatSendTime = simpleDateFormat.format(parseSendTime);
map.put("sendTime", formatSendTime);
}
obj = map;
}
}
ok。java服务的升级已经全部完成,接下来就到了头疼的Web项目。
还存在的问题就是时间相差8小时,最开始认为是时区问题导致的,但通过排查发现,导致时差相差8小时的原因是因为springboot1.x 和springboot2.x的序列化不同
解决办法添加如下配置:
spring.jackson.time-zone=GMT+8
Springboot Web项目
如果你要升级的是一个web项目,那么恭喜你,上面的那些配置只是冰山一角。接下来的东西,会让你痛不欲生。。。
升级一个web项目存在N个坑,会遇到各种各样的问题。
在这里介绍的也是从Springboot1.5.7 升级到Springboot2.1.9 web项目。
1.jsp
Springboot2.x版本对jsp很不友好,需要引入外部依赖。
<!--引入springboot 内嵌tomcat对jsp的解析包-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--servlet依赖jar-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!--jsp依赖jar-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<!--Jstl标签依赖的jar包start-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
注意:所有的jsp文件都要放到webapp下,否则会加载不到相应的文件
ps:如何创建webapp文件夹,请自行搜索!
在Web项目升级的时候,启动类原来继承的SpringBootServletInitializer会导致406的问题。406是json序列化的问题,可百度自行搜索。所以在升级之后,我选择继承WebMvcConfigurationSupport。
继承WebMvcConfigurationSupport的时候web项目会404而无法跳转相应的jsp页面,但你会发现直接访问静态页面的时候是可以的。所以那一定是跳转存在问题。通过查看官方文档发现当启动类在继承WebMvcConfigurationSupport的时候,这个类存在的默认配置会自动帮你屏蔽掉你配置的加载的jsp页面,那么就需要重写
那么在继承WebMvcConfigurationSupport的时候需要重写json序列化。
我采用的是fastjson序列化,相关代码如下。
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//1、定义一个convert转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
//2、添加fastjson的配置信息
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
//3、在convert中添加配置信息
fastConverter.setFastJsonConfig(fastJsonConfig);
//4、将convert添加到converters中
converters.add(fastConverter);
//5、追加默认转换器
super.addDefaultHttpMessageConverters(converters);
}
最近遇到的新坑:
由于Springboot2.x继承WebMvcConfigurationSupport,json序列化发生了改变,返回值为null的时候是默认不展示的。以至于我的后台一直在报404,但看接口是undefined。
所以在重写json序列化的时候,要注意返回的形式.
还是以fastjson为例,需要在序列化的时候添加SerializerFeature.WriteNullStringAsEmpty就可以解决.
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteNullStringAsEmpty);
对自定义的json树组要是以String返回的,要改成Object,否则返回的报文都是以字符串的方式进行返回,无法解析。
还有一点升级版本后的Web项目,对返回的xml格式的报文,也有影响,通过后台发现返回的xml报文,都变成了字符串无法解析。
所以要变成Object返回,不要以String形式返回。并设置消息头。(这个配置只针对xml形式)
response.setHeader("Content-Type","text/xml;charset=UTF-8");
response.setContentType("text/xml;charset=UTF-8");
try (PrintWriter writer = response.getWriter();){
writer.println(mmdata.getMmdl());
}catch (Exception e){
e.printStackTrace();
}
return null;
//return getXmlSuccessResponse(mmdata.getMmdl());
//注意注释掉的代码,不要转换其他形式返回
2.HTML
上面介绍了jsp的升级方式,那么现在来说说html方式的升级。html方式的升级就比jsp要简单的多。html文件放在resource/static下即可.
结尾!
如果有引用cas登录,那么修改的时候,要注意cas的改变。由于我所升级的项目都部署了docker,一定要注意cas的跳转地址!!!要注意跨域的问题。
由于我的Web项目放在k8s下部署,所以访问规则是路由规则。导致我项目的访问路径会少命名空间路径,这里我采用的是把前端文件写死加上命名空间路径,大家有什么好的想法和建议能解决在k8s上获取路径的问题,感激不尽!