V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wayn111
V2EX  ›  程序员

Spring Boot3.0 升级,踩坑之旅,附解决方案

  •  2
     
  •   wayn111 ·
    wayn111 · 57 天前 · 1041 次点击
    这是一个创建于 57 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本文基于 newbeemall 项目升级 Spring Boot3.0 踩坑总结而来,附带更新说明:

    Spring-Boot-3.0-发布说明

    Spring-Boot-3.0.0-M5-发布说明

    一. 编译报错,import javax.servlet.*; 不存在

    这个报错主要是 Spring Boot3.0 已经为所有依赖项从 Java EE 迁移到 Jakarta EE API,导致 servlet 包名的修改,Spring 团队这样做的原因,主要是避免 Oracle 的版权问题,解决办法很简单,两步走:

    1 添加 jakarta.servlet 依赖

    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
    </dependency>
    
    1. 修改项目内所有代码的导入依赖
    修改前:
    import javax.servlet.*
    修改后:
    import jakarta.servlet.*
    

    二. 附带的众多依赖包升级,导致的部分代码写法过期报警

    2.1 Thymeleaf 升级到 3.1.0.M2 ,日志打印的报警

    14:40:39.936 [http-nio-84-exec-15] WARN  o.t.s.p.StandardIncludeTagProcessor - [doProcess,67] - [THYMELEAF][http-nio-84-exec-15][admin/goods/goods] Deprecated attribute {th:include,data-th-include} found in template admin/goods/goods, line 4, col 15. Please use {th:insert,data-th-insert} instead, this deprecated attribute will be removed in future versions of Thymeleaf.
    14:40:39.936 [http-nio-84-exec-15] WARN  o.t.s.p.AbstractStandardFragmentInsertionTagProcessor - [computeFragment,385] - [THYMELEAF][http-nio-84-exec-15][admin/goods/goods] Deprecated unwrapped fragment expression "admin/header :: header-fragment" found in template admin/goods/goods, line 4, col 15. Please use the complete syntax of fragment expressions instead ("~{admin/header :: header-fragment}"). The old, unwrapped syntax for fragment expressions will be removed in future versions of Thymeleaf.
    

    可以看出作者很贴心,日志里已经给出了升级后的写法,修改如下:

    修改前:
    <th:block th:include="admin/header :: header-fragment"/>
    修改后:
    <th:block th:insert="~{admin/header :: header-fragment}"/>
    

    2.2 Thymeleaf 升级到 3.1.0.M2 ,后端使用 thymeleafViewResolver 手动渲染网页代码报错

    // 修改前 Spring Boot2.7:
    WebContext ctx = new (request, response,
            request.getServletContext(), request.getLocale(), model.asMap());
    html = thymeleafViewResolver.getTemplateEngine().process("mall/seckill-list", ctx);
    

    上述代码中针对 WebContext 对象的创建报错,这里直接给出新版写法

    // 修改后 Spring Boot3.0:
    JakartaServletWebApplication jakartaServletWebApplication = JakartaServletWebApplication.buildApplication(request.getServletContext());
    WebContext ctx = new WebContext(jakartaServletWebApplication.buildExchange(request, response), request.getLocale(), model.asMap());
    html = thymeleafViewResolver.getTemplateEngine().process("mall/seckill-list", ctx);
    

    三. 大量第三方库关于 Spring Bootstarter 依赖失效,导致项目启动报错

    博主升级到 3.0 后,发现启动时,Druid 数据源开始报错,找不到数据源配置,便怀疑跟 Spring boot 3.0 更新有关

    这里直接给出原因:Spring Boot 3.0 中自动配置注册的 spring.factories 写法已废弃,改为了 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 写法,导致大量第三方 starter 依赖失效

    在吐槽一下,这么重要的更改在 Spring 官方的 Spring-Boot-3.0-发布说明 中竟然没有,被放在了 Spring-Boot-3.0.0-M5-发布说明

    这里给出两个解决方案:

    1. 等待第三方库适配 Spring Boot 3.0
    2. 按照 Spring Boot 3.0 要求,在项目resources 下新建 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,手动将第三方库的 spring.factories 加到 imports 中,这样可以手动修复第三方库 spring boot starter 依赖失效问题

    四. Mybatis Plus 依赖问题

    Mybatis plus 最新版本还是 3.5.2 ,其依赖的 mybatis-spring 版本是 2.2.2 (mybatis-spring 已经发布了 3.0.0 版本适配 Spring Boot 3.0 ),这会导致项目中的 sql 查询直接报错,这里主要是因 Spring Boot 3.0 中删除 NestedIOException 这个类,在 Spring boot 2.7 中这个类还存在,给出类说明截图

    image.png 这个类在 2.7 中已经被标记为废弃,建议替换为 IOException, 而 Mybatis plus 3.5.2 中还在使用。这里给出问题截图 MybatisSqlSessionFactoryBean 这个类还在使用 NestedIOException

    image.png

    查看 Mybatis plus 官方 issue 也已经有人提到了这个问题,官方的说法是 mybatis-plus-spring-boot-starter 还在验证尚未推送 maven 官方仓库,这里我就不得不动用我的小聪明,给出解决方案:

    1. 手动将原有的 MybatisSqlSessionFactoryBean 类代码复制到一个我们自己代码目录下新建的 MybatisSqlSessionFactoryBean 类,去掉 NestedIOException 依赖
    2. 数据源自动配置代码修改
    @Slf4j
    @EnableConfigurationProperties(MybatisPlusProperties.class)
    @EnableTransactionManagement
    @EnableAspectJAutoProxy
    @Configuration
    @MapperScan(basePackages = "ltd.newbee.mall.core.dao", sqlSessionFactoryRef = "masterSqlSessionFactory")
    public class HikariCpConfig {
    
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return interceptor;
        }
    
    
        @Bean(name = "masterDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.master")
        public DataSource masterDataSource() {
            return new HikariDataSource();
        }
    
        /**
         * @param datasource 数据源
         * @return SqlSessionFactory
         * @Primary 默认 SqlSessionFactory
         */
        @Bean(name = "masterSqlSessionFactory")
        public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource datasource,
                                                         Interceptor interceptor,
                                                         MybatisPlusProperties properties) throws Exception {
            MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
            bean.setDataSource(datasource);
            // 兼容 mybatis plus 的自动配置写法
            bean.setMapperLocations(properties.resolveMapperLocations());
            if (properties.getConfigurationProperties() != null) {
                bean.setConfigurationProperties(properties.getConfigurationProperties());
            }
            if (StringUtils.hasLength(properties.getTypeAliasesPackage())) {
                bean.setTypeAliasesPackage(properties.getTypeAliasesPackage());
            }
            bean.setPlugins(interceptor);
            GlobalConfig globalConfig = properties.getGlobalConfig();
            bean.setGlobalConfig(globalConfig);
            log.info("------------------------------------------masterDataSource 配置成功");
            return bean.getObject();
        }
    
        @Bean("masterSessionTemplate")
        public SqlSessionTemplate masterSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    
    }
    

    到这里,项目就能够正常跑起来了

    总结

    Spring Boot 3.0 升级带来了很多破坏性更改,把众多依赖升级到了最新,算是解决了一部分历史问题,也为了云原型需求,逐步适配 graalvm ,不管怎么样作为技术开发者,希望有更多的开发者来尝试 Spring Boot 3.0 带来的新变化。

    sch1111878
        1
    sch1111878  
       57 天前
    感谢
    wayn111
        2
    wayn111  
    OP
       57 天前
    @sch1111878 分享交流,客气客气😂
    koela
        3
    koela  
       57 天前
    支持
    wayn111
        4
    wayn111  
    OP
       57 天前
    @koela 感谢
    SJH0402
        5
    SJH0402  
       57 天前 via iPhone
    这也算先驱了哈哈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   实用小工具   ·   1261 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 37ms · UTC 15:51 · PVG 23:51 · LAX 07:51 · JFK 10:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.