保姆级教程:手把手教你将若依前后端分离版从Java 8平滑升级到Java 17(含Spring Security 6.2.3适配)
最近在技术社区看到不少开发者讨论Java 17的新特性,尤其是对LTS版本长期支持的期待。作为一个长期使用若依框架的开发者,我决定将手头的项目从Java 8升级到Java 17。整个过程花了三天时间,踩了不少坑,也积累了一些经验。今天就把这个完整的升级过程记录下来,希望能帮助到有同样需求的同行。
升级Java版本看似简单,但实际上涉及到整个技术栈的适配,特别是像若依这样集成了Spring Boot、MyBatis Plus、Spring Security等主流框架的系统。我们需要考虑的不仅仅是JDK本身的变更,还有依赖库的兼容性、配置语法的变化,以及Jakarta EE的迁移问题。
1. 环境准备与依赖分析
在开始升级之前,我们需要做好充分的准备工作。首先确保你的开发环境已经安装了Java 17 JDK,我推荐使用Amazon Corretto 17或者OpenJDK 17。可以通过以下命令验证:
java -version你应该看到类似这样的输出:
openjdk version "17.0.10" 2024-01-16 OpenJDK Runtime Environment (build 17.0.10+7) OpenJDK 64-Bit Server VM (build 17.0.10+7, mixed mode)接下来,我们需要分析当前项目的依赖树,找出可能需要升级的组件。在项目根目录下运行:
mvn dependency:tree > dependency.txt这个命令会把项目的完整依赖树输出到dependency.txt文件中。仔细检查这个文件,特别关注以下几个关键依赖:
- Spring Boot版本(需要升级到3.x系列)
- Spring Security版本
- MyBatis Plus版本
- 数据库驱动(如MySQL Connector/J)
- Servlet API相关依赖
常见需要升级的依赖清单:
- Spring Boot: 2.x → 3.2.5
- Spring Security: 5.x → 6.2.3
- MyBatis Plus: 3.4.x → 3.5.5
- MySQL Connector/J: 5.x → 8.1.0
- Logback: 1.2.x → 1.4.14
提示:建议在升级前创建一个新的Git分支,这样如果升级过程中出现问题可以随时回退。
2. 分模块修改POM文件
若依前后端分离版通常包含多个模块:ruoyi-admin、ruoyi-common、ruoyi-framework等。我们需要逐个模块进行升级。
2.1 父POM的修改
首先修改父POM中的属性定义,这是整个升级过程中最关键的一步。我们需要更新Java版本和主要依赖的版本号:
<properties> <java.version>17</java.version> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <spring-boot.version>3.2.5</spring-boot.version> <mybatis.plus.version>3.5.5</mybatis.plus.version> <tomcat.version>10.1.24</tomcat.version> <logback.version>1.4.14</logback.version> <spring-security.version>6.2.3</spring-security.version> <spring-framework.version>6.2.6</spring-framework.version> <mysql-connector-j.version>8.1.0</mysql-connector-j.version> <jakarta.servlet-api.version>6.0.0</jakarta.servlet-api.version> <mybatis-spring.version>3.0.3</mybatis-spring.version> </properties>然后更新dependencyManagement部分,确保所有子模块使用统一的依赖版本:
<dependencyManagement> <dependencies> <!-- 阿里数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>${druid.version}</version> </dependency> <!-- servlet --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>${jakarta.servlet-api.version}</version> </dependency> <!-- mysql --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>${mysql-connector-j.version}</version> </dependency> <!-- MyBatis相关 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring.version}</version> </dependency> </dependencies> </dependencyManagement>2.2 子模块POM的修改
ruoyi-admin模块主要需要更新MySQL驱动:
<dependencies> <!-- Mysql驱动包 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> </dependencies>ruoyi-common模块需要更新Servlet API:
<dependencies> <!-- servlet包 --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> </dependency> </dependencies>ruoyi-framework模块需要特别注意Druid连接池的版本:
<dependencies> <!-- 阿里数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> </dependency> <!-- 验证码 --> <dependency> <groupId>pro.fessional</groupId> <artifactId>kaptcha</artifactId> <exclusions> <exclusion> <artifactId>servlet-api</artifactId> <groupId>jakarta.servlet</groupId> </exclusion> </exclusions> </dependency> </dependencies>注意:从Spring Boot 3开始,很多依赖的groupId或artifactId发生了变化,特别是Jakarta EE相关的包名从javax.改为了jakarta.。
3. Spring Security 6.2.3适配
Spring Security 6.x相对于5.x有较大的API变化,这是升级过程中最具挑战性的部分之一。我们需要重点关注安全配置类的修改。
3.1 SecurityConfig的修改
最明显的变化是.antMatchers()方法被.requestMatchers()取代。下面是新旧代码对比:
旧版配置:
.authorizeRequests() .antMatchers("/login", "/register", "/captchaImage").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() .anyRequest().authenticated();新版配置:
.authorizeHttpRequests((requests) -> { requests.requestMatchers("/login", "/register", "/captchaImage").permitAll() .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js").permitAll() .anyRequest().authenticated(); })完整的SecurityConfig类修改示例:
@Bean protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity // CSRF禁用,因为不使用session .csrf(AbstractHttpConfigurer::disable) // 禁用HTTP响应标头 .headers(header -> header .cacheControl(HeadersConfigurer.CacheControlConfig::disable) .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) // 认证失败处理类 .exceptionHandling(exception -> exception .authenticationEntryPoint(unauthorizedHandler)) // 基于token,所以不需要session .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 注解标记允许匿名访问的url .authorizeHttpRequests((requests) -> { permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll()); requests.requestMatchers("/login", "/register", "/captchaImage").permitAll() .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll() .requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .anyRequest().authenticated(); }) // 添加Logout filter .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessHandler(logoutSuccessHandler)) // 添加JWT filter .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) // 添加CORS filter .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class) .addFilterBefore(corsFilter, LogoutFilter.class) .build(); }3.2 PermitAllUrlProperties的修改
Spring Security 6.x中获取请求映射的方式也发生了变化:
@Override public void afterPropertiesSet() { // 修改前 // RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); // 修改后 RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods(); map.keySet().forEach(info -> { HandlerMethod handlerMethod = map.get(info); // 获取方法上边的注解 Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues()) .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); // 获取类上边的注解 Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues()) .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); }); }4. Jakarta EE迁移与验证
从Java EE到Jakarta EE的包名变更是一个重大变化,我们需要检查所有javax.的导入并替换为jakarta.。
4.1 Servlet API变更
修改DruidConfig中的导入语句:
// 修改前 // import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; // import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; // 修改后 import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder; import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;4.2 验证升级结果
完成所有修改后,我们需要验证系统功能是否正常:
编译验证:
mvn clean compile确保没有编译错误。
单元测试:
mvn test运行所有单元测试,确保核心逻辑仍然正常工作。
功能验证:
- 启动应用:
mvn spring-boot:run - 测试登录功能
- 验证权限控制是否正常
- 检查数据库连接是否正常
- 测试文件上传下载等Servlet相关功能
- 启动应用:
常见问题排查:
- 如果遇到ClassNotFound异常,检查是否所有javax.的导入都已替换为jakarta.
- 如果Spring Security配置不生效,检查是否所有.antMatchers()都已改为.requestMatchers()
- 如果数据库连接失败,检查MySQL驱动版本和连接参数
提示:建议在升级完成后运行完整的回归测试,特别是权限相关的功能点。