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.时间问题

所有项目的升级可能会导致时间的问题,比如返回的时间变成了时区格式

spring boot升级 springboot升级spring版本_spring boot升级


如果是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上获取路径的问题,感激不尽!