ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

datax-web从Spring Boot 2升级到Boot 3+JDK17:javax到jakarta、Security 6与反射限制全记录

datax-web从Spring Boot 2升级到Boot 3+JDK17:javax到jakarta、Security 6与反射限制全记录 去年年底做安全审计的时候手里那套datax-web图形化调度平台被连番点名Spring Boot 2.3.7已经停止社区维护JDK8在扫描报告里挂了一串高危前端和权限体系又急着接OAuth2。DataX本身是阿里开源的数据同步工具单独用要手写JSON、敲命令行团队协作时太容易出错所以才有datax-web这类图形化界面把Reader/Writer、同步任务、调度记录都管起来。datax-web在开源社区里算比较常用的DataX管理端我们内部也用了好几年。当时决定把它从Spring Boot 2.x JDK8整体升到Spring Boot 3.2 JDK17本以为是个体力活真正动起手来才发现框架升级本身就是一次小型的架构重构。DataX这个同步引擎没怎么变变的全是它外面的壳——任务管理、用户权限、调度日志、执行器调用每一层都要在Boot3和JDK17的规则里重新过一遍。这篇文章把整段升级过程的关键节点、依赖版本矩阵和排查思路原样写出来给同样维护datax-web二次开发版或者类似老管理端的同学做个参考。1. 升级前先摸清datax-web的家底版本矩阵与依赖边界1.1 老平台的技术栈底子datax-web是什么它是阿里开源同步工具DataX的图形化管理端使用者不需要手写DataX的作业JSON而是通过Web界面配置Reader和Writer由后端帮忙生成JSON并调度执行。项目本身是Spring Boot单体后台加Vue管理界面用户、菜单、数据源、任务管理都在一个库里。我们维护的分支基于Spring Boot 2.3.7.RELEASE、JDK8安全这块早期版本用的是Spring Security JWT数据库MySQL连接池DruidORM用的MyBatis。这套组合在当年是标准答案但站在现在往回看问题很刺眼Spring Boot 2.x的社区维护已经停止JDK8在容器环境和麒麟这类Linux环境上的安全配置越来越麻烦Security 5.x的配置方式跟新生态也严重脱节。升级前的第一件事不是改pom.xml而是先把这个技术栈的家底盘清楚哪个组件在哪个版本、哪个库还停在被废弃的写法上全都要有个清单。1.2 升级目标与边界划定这次升级的目标很明确JDK从8升到17 LTS选17.0.10以上的patch版本Spring Boot从2.3.7升到3.2.x优先选已经出了一堆patch的稳定小版本Spring Security跟着升到6.2把登录、鉴权、JWT链路重写数据访问层替换到兼容Boot3的MyBatis、PageHelper、Druid版本。还有一个关键决定是DataX核心引擎暂时不动仍然用已验证过的稳定版本。这个边界非常重要。datax-web这个工程里最不稳定、最需要谨慎的是调度执行链路如果顺手把DataX引擎也升级了出了问题就要在框架升级和引擎升级两层里排查难度翻倍。我的原则是一次只动一件事。框架层动了引擎层就锁版本。很多人在升级时喜欢把能升的全部升一遍最后连到底是哪一步炸的都不知道这种教训实在太多。1.3 先写验证清单再动手改依赖升级前先定死一条回归路径管理员和普通用户登录是否正常、创建数据源测试连通是否通过、配置同步任务生成JSON是否成功、手动触发任务执行器是否跑得通、调度记录和历史任务是否完整、数据源权限隔离是否还在。后面每改一步依赖都拿这条路径跑一遍。只跑一个“启动不报错”远远不够很多问题要等真正登录、真正跑一个迁移任务才会暴露出来。先有验证清单再动手改代码这是整个升级过程里性价比最高的一件事。2. javax到jakarta这一步决定后面所有麻烦的大小2.1 为什么改名会引发连锁反应Spring Boot 3最大的硬性变化是所有原本挂在javax.命名空间下的Java EE API整体迁到了jakarta.。这不是Spring自己改的是Java EE移交给Eclipse基金会后Jakarta EE 9起的包名规则。Tomcat 10只实现jakarta.servlet不再认javax.servlet。所以Boot3项目里如果还留着javax.servlet.http.HttpServletRequest这样的import编译能过因为你可能还带着老servlet-api依赖一旦运行类直接冲突或者找不到。我在升级datax-web时第一步动作就是全局搜索import javax.把所有命中点拉出来梳理。Controller层、过滤器、拦截器、工具类全都要改。这里有一个特别容易忽略的坑javax.annotation.PostConstruct、javax.annotation.Resource在JDK9之后被从Java SE里移除了所以哪怕你只升JDK不升Spring Boot也可能踩到。Boot3环境下统一换成jakarta.annotation.*。数据校验注解也一样javax.validation.要整体换成jakarta.validation.不然Controller层的Valid RequestBody直接失效。2.2 数据访问层的依赖换血datax-web的持久层是MyBatis配PageHelper分页连接池Druid这三个组件在Boot3下全部要换坐标版本。下面是我们最终锁定的版本对照表组件升级前升级后补充说明JDK1.817.0.10选LTS别用17.0.1这种早期版Spring Boot2.3.7.RELEASE3.2.x生产环境图稳不追最新大版本mybatis-spring-boot-starter2.2.03.0.3注意mybatis-spring 3.0的API变化pagehelper-spring-boot-starter1.4.62.1.0Boot3有专门的starter坐标druid-spring-boot-starter1.2.6druid-spring-boot-3-starter 1.2.20或者手动注入DruidDataSourceLombok1.18.221.18.30低版本编译期就挂spring-security5.x6.2.x配置类基本要重写servlet-apijavax.servlet-api 4.0.1jakarta.servlet-api 6.0依赖由Boot传递管理MyBatis这里有个细节mybatis-spring-boot-starter 3.0.x对应的mybatis-spring版本是3.0.x不再是2.x。如果你原来在代码里直接new SqlSessionFactoryBean或者依赖tk.mybatis这类老封装要确认它对Boot3的自动装配机制是否兼容。datax-web的DAO层主要用注解影响不大但有个别插件依赖老mapper-starter这些必须清掉。PageHelper换到2.1.0之后原来的PageHelperAutoConfiguration自动装配逻辑有变化分页参数和插件顺序要重新验证一下否则列表页可能分页失效。2.3 spring.factories文件消失自动装配改名Spring Boot 2.x时代自定义自动配置类写在META-INF/spring.factories里键是org.springframework.boot.autoconfigure.EnableAutoConfiguration。Boot3把这个机制改成了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里面的内容是一行一个自动配置类的全类名同时要求类上加AutoConfiguration注解。这个改动的坑在于如果不改Spring Boot不会报错只是你的自动配置静默失效。datax-web里如果有自定义数据源初始化类、任务执行器初始化类这种失效会在运行时变成奇奇怪怪的Bean找不到或任务仓库未初始化。我当时排查一个执行器初始化失败的问题花了半天最后才发现是自动配置文件路径不对。如果你是从IDEA新建Spring Boot 3工程用Spring Initializr生成的项目里已经自动带了正确的AutoConfiguration.imports文件直接改就行。2.4 创建新工程时别在IDEA里选错版本如果你打算用IDEA从零搭一个Boot3工程来对照Spring Initializr里选Spring Boot 3.2.xJava版本选17Maven工程。这里要注意生成出来的pom里Java版本已经是17但如果你本地没装JDK17IDEA会提示invalid source release。先把JDK17装好再在Project Structure里把Project SDK和Modules的Language Level都切到17。千万不要出现Project SDK是17、Modules Language Level还是1.8的割裂状态这种配置下编译不报错但运行起来全是奇怪的不兼容。3. Spring Security 6重写登录鉴权链路旧写法全废3.1 WebSecurityConfigurerAdapter被删了datax-web原本的Security配置类大概率是继承WebSecurityConfigurerAdapter重写configure(HttpSecurity http)和configure(AuthenticationManagerBuilder auth)这两组方法。这套写法在Spring Security 5.7开始标记废弃到6.x彻底删干净。升级Boot3后编译期就会报错不是运行期问题所以很好发现但正确的新写法需要重新理解。新写法是直接声明SecurityFilterChain的BeanConfiguration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(Customizer.withDefaults()) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/login, /captcha, /swagger-ui/**, /doc.html, /favicon.ico).permitAll() .anyRequest().authenticated() ) .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }这段代码有几个点值得展开说。首先csrf直接禁用因为datax-web是无状态JWT接口不需要CSRF防护如果保留默认开启登录后的POST请求会一直403。其次session管理设为STATELESS否则Security会在内存里维护Session跟JWT无状态设计冲突。最后authorizeHttpRequests里的requestMatchers就是原来antMatchers的替代品。3.2 antMatchers到requestMatchers不只是改个名很多人以为antMatchers换成requestMatchers就是关键字替换实际匹配器的底层实现从AntPathRequestMatcher切到了PathPatternRequestMatcher两者的通配规则略有差异。对大多数/xx/**这种写法没有影响但如果你原来用了一些比较刁钻的Ant表达式例如*只匹配一级路径这种特性需要重新验证。还要注意如果配了多个SecurityFilterChain每个filter chain的匹配顺序也要理清。认证这部分原来习惯直接Autowired一个AuthenticationManager进登录Controller在Security6里如果直接在配置类里把AuthenticationManager定义为Bean容易在依赖注入时踩循环依赖Security官方推荐从AuthenticationConfiguration获取Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); }然后在登录接口里正常调authenticationManager.authenticate(...)即可。这里有一个常见的坑如果自定义UserDetailsService和PasswordEncoder的Bean没配对会报无法找到用户服务或者密码校验永远失败。我用的是BCryptPasswordEncoder而旧库里如果存的是明文或者MD5老账号会全线登录失败需要做密码编码兼容。datax-web这种老平台很多时候用户表里的密码是历史原因留下的升级前先看看备份库里的密码哈希前缀是什么再决定PasswordEncoder的兼容策略。3.3 JWT过滤器与SecurityContextHolder的适配JWT校验的逻辑可以用一个OncePerRequestFilter实现除了放行的登录接口之外其他请求都走一遍Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token resolveToken(request); if (token ! null SecurityContextHolder.getContext().getAuthentication() null) { // 解析token从缓存加载用户信息组装UsernamePasswordAuthenticationToken SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } }这里有几个容易被忽略的点。一是过滤器里加载用户信息时不要每次都查库高并发下datax-web的任务查询接口很吃鉴权性能Redis或者本地缓存兜一层很必要。二是SecurityContextHolder默认策略是ThreadLocal如果异步任务里要用到用户信息需要显式设置策略为MODE_INHERITABLETHREADLOCAL。三是异常处理filter里token过期或非法时不要直接抛异常丢给Spring Security默认入口否则前端拿到的不是JSON错误信息而是默认的403/500页面这些要统一处理成接口返回结构。3.4 跨域配置和OAuth2扩展位Boot3里跨域推荐声明CorsConfigurationSource的Bean然后在Security的cors(Customizer.withDefaults())里生效。datax-web前端如果和后台不同源这个必须配好。原来Boot2时代很多项目是在WebMvcConfigurer的addCorsMappings里写的Boot3下Security6会先于CORS过滤器工作如果只配了MVC层授权之前跨域请求就直接被拦截了前端登录都发不出去。热词里反复出现springboot3 security6 oauth2这里多说一句。升级到Boot3 Security6最大的红利之一就是OAuth2客户端的接入变得很规整。引入spring-boot-starter-oauth2-client配置ClientRegistrationRepository就能对接第三方登录。datax-web这种管理平台后续如果要接企业微信、钉钉或者内部统一身份认证这个扩展位在升级后已经是通的。我们这次升级把oauth2-client依赖加进去了但暂时没启用只留了配置位避免一次动太多功能面。4. JDK17模块化反射限制运行期各种NoSuchFieldError的元凶4.1 JDK9之后反射不再是原来的反射JDK17带来的第一大冲击不是语法而是模块化之后反射访问被收紧。JDK8里写个反射直接访问java.lang包的私有字段虽然警告但能过JDK17下会直接抛InaccessibleObjectException本质是模块系统不允许非法访问java.base模块内部。Spring自身靠ASM和MethodHandles的Lookup机制做了大量适配但datax-web工程里还有DataX引擎这一层这层是独立进程不是Spring容器管理的。升级后我第一次跑同步任务DataX引擎启动阶段直接报java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(...) accessible: module java.base does not opens java.lang to unnamed module看到这个报错就明白了DataX的插件类加载器在执行defineClass时访问了java.lang包但JDK17默认不开这个口子。这个过程很像你住在一栋有门禁的大楼里老式门卡在旧楼里随便刷换了新楼必须先到物业登记才能开门。4.2 --add-opens清单和两处生效位置解决方案是给JVM加--add-opens参数。简要解释一下这个参数的作用它告诉JVM允许某个模块的包向另一个模块开放反射访问。对应用来说平时访问不到java.base里的内部类加了之后就能用反射访问了。实际用到datax-web里的完整清单--add-opens java.base/java.langALL-UNNAMED --add-opens java.base/java.lang.reflectALL-UNNAMED --add-opens java.base/java.utilALL-UNNAMED --add-opens java.base/java.util.concurrentALL-UNNAMED --add-opens java.base/java.util.concurrent.atomicALL-UNNAMED --add-opens java.base/java.netALL-UNNAMED --add-opens java.base/java.ioALL-UNNAMED --add-opens java.base/java.textALL-UNNAMED --add-opens java.base/java.timeALL-UNNAMED --add-opens java.sql/java.sqlALL-UNNAMED这串参数实际上要加在两个地方。datax-web主进程一份放在bin/server.sh的JAVA_OPTS里DataX执行器子进程一份放在DataX安装目录下bin/datax.py的JVM参数模板里。因为datax-web调用DataX任务时是通过ProcessBuilder拉起一个新的Java进程来跑datax.py脚本的这个子进程和主进程是两套JVM。如果你只在主进程加了--add-opens子进程里DataX引擎启动照样炸。我当时漏了第二处主进程启动很干净一触发任务就报错而且报错信息在日志文件末尾非常不好找。4.3 还有一类问题是代理类和Lombok引起的JDK17下CGLIB动态代理有个历史问题生成代理类时通过ClassLoader.defineClass反射访问如果缺上面那行java.lang的add-opensSpring AOP、MyBatis拦截器、PageHelper代理都可能挂。补上之后基本就顺了。另外Lombok也必须升到1.18.30否则编译期就报illegal reflective access。Maven的maven-compiler-plugin的release参数也要同步改成17别只改pom里的java.version编译插件不跟上打出来的字节码版本还是52JDK8跑在JDK17上虽然能兼容但你新代码用到的JDK17语法和API会直接编译失败。用IDEA的annotationProcessorPaths配置时把Lombok版本也钉死避免多个模块版本不一致。5. 执行器改造与调度链路验证从datax.py到DS联动5.1 一个同步任务在前后台是怎么跑起来的要理解升级后该查哪里得先把datax-web执行同步任务的链路梳理清楚。datax-web本身不执行数据抽取它是给DataX做编排和托管的。具体流程是用户配置Reader/Writer后台生成DataX作业JSONJSON落到本地临时目录datax-web记录任务实例后台通过ProcessBuilder执行python脚本通常是datax.py脚本参数是JSON路径datax.py内部检查JAVA_HOME用java命令启动DataX核心进程DataX核心加载Reader/Writer插件做同步实时输出日志datax-web按行读取子进程输出回传到前端。代码层大致是这样ProcessBuilder processBuilder new ProcessBuilder(python3, dataXPyPath, jobJsonPath); processBuilder.redirectErrorStream(true); processBuilder.directory(new File(dataXHome)); Process process processBuilder.start();升级Boot3/JDK17对前两步和后两步影响不大问题集中在中间几步的环境匹配上。最典型的就是子进程拿到的JAVA_HOME还是JDK8的路径datax.py就按JDK8去启动DataX了这不会立刻报错但会绕过前面配的所有add-opens等到插件反射直接崩。而且datax.py通过java -version判断版本时如果输出里有两套版本它会优先用PATH里找到的那一套这个排查起来非常迷惑。5.2 在麒麟V10环境上翻车的三个细节真实环境是麒麟V10 x86_64部署时遇到的问题都很有代表性。第一个是服务器上同时装了几套JDK。系统里既有JDK8又有JDK17直接在终端执行java -version显示的是17但datax-web的bin/server.sh里写死了老路径或者用旧的JAVA_HOME环境变量导致平台是17启动的子进程是8启动的。解决方式统一在bin/server.sh顶部显式export JAVA_HOME和PATH不要在脚本里依赖全局环境变量的运气。用source /etc/profile后执行which java确认指向新路径如果/usr/bin/java里还有老JDK软链优先级会覆盖PATH里的配置。第二个是临时目录权限。datax-web把作业JSON写到java.io.tmpdir如果这个目录没写权限任务会一直卡在等待执行。排查这类问题时先清理掉残留的python/java进程再手动跑一次datax.py看完整报错往往比看平台日志快得多。如果日志里出现“Permission denied”或“No such file or directory”基本就是临时目录或DataX安装目录的权限问题。第三个是python2和python3的差异。老的datax.py脚本有些版本依赖python2的语法而麒麟V10默认python是3。如果ProcessBuilder里写的是“python”可能跑到python3解释器上脚本跑不起来。建议在全局配置里明确python执行路径用python3或者python2的绝对路径不要裸写python。这个坑和JDK版本无关但升级后重新部署环境时最容易一起爆发。5.3 DolphinScheduler能不能调度DataX任务很多人会问dophin调度可以调datax任务吗可以而且和datax-web的升级完全不冲突。DolphinScheduler从3.x开始原生支持DataX任务类型在任务定义里选择DataX节点把DataX安装目录、作业JSON模板填进去即可。它的原理就是DS自己的worker去调用datax.py跟datax-web的ProcessBuilder没有本质区别。所以DataX引擎本身只要能跑DS侧不用做任何Boot3相关的适配。这两种用法可以并存。数据源配置、JSON生成、任务试跑放在datax-web里做生产环境的定时调度交给DolphinScheduler。升级datax-web只影响它自己的界面和管理功能不影响DS对DataX的调度。如果DS侧也跑在同一台机器上记得确认DS worker进程能读到JDK17的环境变量否则DS触发DataX任务时同样会按旧JDK路径去启动然后踩到反射问题。6. 在麒麟V10MySQL8上落地安装、启动脚本与验证6.1 Linux环境JDK17安装与JAVA_HOME切换先确定架构热词里提到的是x86_64所以在镜像站选择对应x86_64架构的OpenJDK17 tar.gz包。不要下载带aarch64后缀的包架构不匹配会报Exec format error。解压到统一目录后编辑/etc/profileexport JAVA_HOME/usr/local/java/jdk-17.0.10 export PATH$JAVA_HOME/bin:$PATH这里有个检查技巧source /etc/profile后执行which java看是不是指向了刚解压的路径。如果系统里还有老JDK的软链在/usr/bin/java优先级可能比PATH里的更高。最简单粗暴又可靠的办法是把/usr/bin/java直接替换成软链指向新JDK并且在datax-web和DataX的所有启动脚本里都显式带JAVA_HOME。Windows环境的JDK17安装更简单解压或点安装包都行但同样要注意把JAVA_HOME和Path系统变量配好确保cmd里java -version输出版本是17。6.2 MySQL8.0.36免安装部署和驱动参数测试环境用MySQL8.0.36的免安装包Windows下解压zip后用mysqld --initialize-insecure初始化数据目录再mysqld --install命令注册为服务。Linux下同样是解压tar包初始化后首次启动。这些操作网上教程很多不展开重点说连接池和驱动参数。datax-web的数据源URL建议写成jdbc:mysql://localhost:3306/datax_web?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruenullCatalogMeansCurrenttrue前面几个参数好理解时区不对数据会差8小时MySQL8的caching_sha2_password认证方式下不加allowPublicKeyRetrievaltrue会报Public Key Retrieval is not allowed。最后一个nullCatalogMeansCurrenttrue是关键中的关键Druid连接池在MySQL多库场景下元数据获取如果沿用老逻辑可能把别的库的表也当成当前库的表导致任务列表、数据源测试全乱。加上这个参数DataX元数据只认当前连接的catalog。MySQL8的驱动类用com.mysql.cj.jdbc.Driver不用再写老的com.mysql.jdbc.DriverBoot3自动配置能识别。6.3 回归验证的完整顺序最后把升级后的回归路径完整跑一遍。这一步建议严格按照从外层到内层的顺序启动datax-web主进程观察启动日志无异常端口正常监听打开登录页输入管理员账号验证验证码、登录接口、JWT签发创建MySQL数据源测试连通性确认Druid监控页有连接数据配置一个最简单的源表到目标表的同步任务手动触发观察日志输出任务从执行中到成功DataX统计信息出现在日志里查看运行历史、调度记录、用户权限菜单。这个顺序的意义在于分层定位问题。如果第3步过了但第5步挂问题范围就限定在执行器子进程环境JAVA_HOME、python路径、add-opens跟Security无关如果第2步就挂问题范围限定在Security6和JWT过滤器先不要动数据源和DataX。这种分层排查的思路比盯着一条报错瞎猜有效得多。尤其是升级这种牵一发动全身的任务惰性排查只会让你把时间耗在无关的地方。最后分享一点个人经验。升级这种老管理平台最怕的就是想一次性把所有库都换到最新、顺手重构一把。Spring Boot 3 JDK17不是简单的版本号跳动而是整个Java生态在命名空间和模块化上的一次分水岭老第三方库每一个都可能是雷。我这次能在一周多的时间内稳定跑完回归靠的就是先锁定验证路径再按“JDK → Spring Boot → 安全框架 → 数据访问层 → 执行器环境”的次序一层一层换每一层换完都跑一遍最小闭环。DataX引擎本身没变但整个管理平台的底座换了之后后面接企业微信、接统一身份认证这类新需求就顺手很多。如果你的datax-web也卡在Boot2和JDK8上照着这个思路走应该能省下不少排查时间。
返回列表