首页 / 资讯中心 / 文章详情

SAP RFC 与 Spring Boot 整合:sapjco 环境搭建与避坑指南

SAP RFC 与 Spring Boot 整合:sapjco 环境搭建与避坑指南 ★ FEATURED ARTICLE
简介本资源是一套基于SpringBoot的Java后台管理系统源码面向需要打通SAP系统与外部业务数据的企业开发者和Java后端学习者。项目整合Spring、Spring MVC、MyBatis-Plus、Druid、Shiro、Thymeleaf、Quartz等技术栈核心亮点是通过sapjco3调用SAP的RFC函数实现数据获取与回传并以接口形式对外提供服务同时借助Quartz完成定时同步任务适合研究企业级SAP集成方案的读者参考。压缩包共336个文件约9.46MB以98个java源码、41个js脚本、28个html页面、25个css样式及13个xml配置为主另含sql脚本、yml与properties配置、字体图标和SAP JCO本地库文件前后端结构完整。目前已有432人学习下载。读者可从中获取SAP RFC交互的调用示例、Shiro权限控制与Quartz调度配置、MyBatis-Plus持久层写法以及一套可直接运行的后台管理项目骨架便于二次开发与排错参考。1. sapweb 环境搭建从零跑通 Spring Boot 与 SAP RFC 交互第一次接触 sapweb 这套组合是在一个内部系统对接项目里需求很明确用 Spring Boot 做服务端通过 sapjco 调用 SAP 的 RFC 函数前端用 Thymeleaf 渲染权限交给 Shiro定时任务用 Quartz 2 调度。听起来每个组件都熟但真正把它们塞进同一个 Maven 工程里跑起来光是 sapjco 的原生库加载就折腾了大半天。这套环境不算新但胜在稳定很多传统企业的内部系统至今还在用类似架构。装不好的典型症状是应用启动时报UnsatisfiedLinkError或者 RFC 调用返回空指针日志里只有一行sapjco not initialized排查起来像在黑匣子里摸路。这篇笔记面向的是需要落地这套组合的开发者不管你是第一次配 sapjco还是被 Shiro 和 Quartz 的版本冲突卡住过下面按实际安装顺序走一遍。2. 环境准备与依赖版本锁定JDK、Maven 和 sapjco 原生库2.1 版本对照表与选型理由这套技术栈的版本兼容性比想象中敏感尤其是 sapjco 和 JDK 的位数必须严格匹配。我一般会先锁定一张版本表避免中途换版本导致原生库重新编译。组件推荐版本说明JDK1.864 位sapjco 3.1 对 JDK 11 支持不稳定1.8 最稳Maven3.6.33.8 对 http 仓库拦截较严内网私服容易报错Spring Boot2.3.12.RELEASE2.7 与 Shiro 1.7 有 Filter 注册顺序问题MyBatis3.5.6与 Spring Boot 2.3 的 mybatis-spring-boot-starter 2.1.4 配套Thymeleaf3.0.12随 Spring Boot 2.3 自带无需单独指定Quartz2.3.2注意不是 Quartz 1.xAPI 差异大Shiro1.7.11.8 改了加密默认值老项目升级会翻车sapjco3.1.6需与 SAP 端内核版本对应32/64 位不能混选 JDK 1.8 不是守旧而是 sapjco 3.1 的libsapjco3.so和sapjco3.dll在 JDK 11 上加载时JVM 对 JNI 的校验更严格容易出现no sapjco3 in java.library.path的玄学问题。Maven 用 3.6.3 是因为很多企业内网私服还是 http 协议3.8 默认阻止 http 仓库改settings.xml也麻烦。2.2 安装 JDK 与配置 Maven 私服先确认系统里没有多个 JDK 混用。Linux 下用update-alternatives管理Windows 下检查JAVA_HOME是否指向 64 位 JDK。# 查看当前 JDK 位数必须输出 64 java -version 21 | grep -o 64-Bit # 设置 JAVA_HOMELinux 示例 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATHMaven 的settings.xml里如果公司有私服把 mirror 指向私服地址没有的话用阿里云仓库也行。关键是localRepository路径不要带空格和中文否则 sapjco 的本地安装脚本会解析失败。!-- settings.xml 片段 -- mirrors mirror idinternal-repo/id mirrorOfcentral/mirrorOf urlhttp://内网私服地址/repository/maven-public//url /mirror /mirrors2.3 安装 sapjco 到本地 Maven 仓库sapjco 不在中央仓库必须手动安装。从 SAP 官方渠道拿到sapjco3.jar和对应平台的libsapjco3.soLinux或sapjco3.dllWindows后用mvn install:install-file装进本地仓库。mvn install:install-file \ -Dfilesapjco3.jar \ -DgroupIdcom.sap.conn.jco \ -DartifactIdsapjco3 \ -Dversion3.1.6 \ -Dpackagingjar \ -DlocalRepositoryPath~/.m2/repository装完后在pom.xml里引用。注意systemPath方式虽然能编译但打包成 fat jar 后原生库不会自动带进去所以必须用install-file方式。dependency groupIdcom.sap.conn.jco/groupId artifactIdsapjco3/artifactId version3.1.6/version /dependency原生库文件要放到 JVM 能加载的路径。Linux 下我一般放在/usr/lib/sapjco/然后启动时加-Djava.library.path/usr/lib/sapjco。Windows 下把sapjco3.dll放到C:\Windows\System32或者项目根目录但更推荐显式指定路径避免和系统里其他版本的 dll 冲突。提示sapjco3.jar和原生库的版本号必须完全一致3.1.6 的 jar 配 3.1.5 的 so 文件调用时会在JCoDestinationManager.getDestination处直接崩溃日志里只有一行JCo initialization failed。3. 工程结构搭建Spring Boot 整合 MyBatis、Thymeleaf 与 Shiro3.1 创建 Maven 工程与目录规划用spring-boot-starter-parent做父工程目录结构按标准来但 sapjco 相关的配置类单独放一个包方便后续排查。src/main/java/com/example/sapweb/ ├── config/ # Shiro、Quartz、SAP 连接配置 ├── controller/ # Thymeleaf 页面路由 ├── service/ # RFC 调用封装 ├── mapper/ # MyBatis 接口 └── SapwebApplication.java src/main/resources/ ├── mapper/ # MyBatis XML ├── templates/ # Thymeleaf 模板 ├── application.yml └── sapjco3/ # 原生库放这里启动时动态加载把原生库放在resources/sapjco3/下的好处是打包后可以通过ClassPathResource提取到临时目录再加载避免依赖外部路径。但生产环境我还是倾向用-Djava.library.path显式指定更可控。3.2 配置 application.yml 与数据源MyBatis 和 Thymeleaf 的配置比较常规重点是 SAP 连接参数单独抽出来不要和数据库配置混在一起。spring: datasource: url: jdbc:mysql://127.0.0.1:3306/sapweb?useUnicodetruecharacterEncodingutf8 username: sapweb password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.sapweb.entity sap: jco: # SAP 连接参数实际值从环境变量注入 ashost: ${SAP_ASHOST} sysnr: ${SAP_SYSNR} client: ${SAP_CLIENT} user: ${SAP_USER} passwd: ${SAP_PASSWD} lang: ZH pool: max: 10 timeout: 30000ashost是 SAP 应用服务器地址sysnr是系统编号client是客户端编号。这几个参数写错任何一个JCoDestinationManager都会抛JCoException但错误信息很模糊只说connect failed。我一般会先用 SAP GUI 确认这几个值能正常登录再填到配置里。3.3 Shiro 权限过滤与 Quartz 定时任务注册Shiro 的配置类里ShiroFilterFactoryBean的filterChainDefinitionMap要用LinkedHashMap否则拦截顺序会乱。Quartz 的SchedulerFactoryBean要设置overwriteExistingJobs为 true否则改 cron 表达式后重启不生效。Configuration public class ShiroConfig { Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean factory new ShiroFilterFactoryBean(); factory.setSecurityManager(securityManager); MapString, String chain new LinkedHashMap(); chain.put(/login, anon); chain.put(/static/**, anon); chain.put(/**, authc); factory.setFilterChainDefinitionMap(chain); factory.setLoginUrl(/login); return factory; } }Quartz 这边如果任务类里需要注入 Spring Bean必须用SpringBeanJobFactory的子类否则Autowired会失效。这个坑我踩过两次任务执行时直接空指针日志里只看到NullPointerException没有其他线索。Bean public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { SchedulerFactoryBean factory new SchedulerFactoryBean(); factory.setDataSource(dataSource); factory.setOverwriteExistingJobs(true); factory.setJobFactory(new AutowiringSpringBeanJobFactory()); return factory; }4. SAP RFC 调用配置sapjco 连接池与 Destination 管理4.1 创建 Destination 与连接池参数sapjco 的连接池通过JCoDestinationManager管理但默认配置下连接数不可控。我一般会在DestinationDataProvider里自定义JCoDestination的属性设置最大连接数和超时。public class SapDestinationProvider implements DestinationDataProvider { Override public Properties getDestinationProperties(String name) { Properties props new Properties(); props.setProperty(DestinationDataProvider.JCO_ASHOST, ashost); props.setProperty(DestinationDataProvider.JCO_SYSNR, sysnr); props.setProperty(DestinationDataProvider.JCO_CLIENT, client); props.setProperty(DestinationDataProvider.JCO_USER, user); props.setProperty(DestinationDataProvider.JCO_PASSWD, passwd); props.setProperty(DestinationDataProvider.JCO_LANG, ZH); // 连接池关键参数 props.setProperty(DestinationDataProvider.JCO_POOL_MAX, 10); props.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, 20); return props; } }JCO_POOL_MAX是池中保持的最大空闲连接数JCO_PEAK_LIMIT是并发峰值上限。设太小会导致高并发时排队设太大又浪费 SAP 端资源。一般内部系统 10 到 20 够用。4.2 封装 RFC 调用模板与异常处理直接调JCoFunction的代码又臭又长我习惯封装一个SapRfcTemplate把获取 destination、执行 function、异常转换都包进去。public class SapRfcTemplate { public JCoFunction execute(String functionName, MapString, Object params) { try { JCoDestination destination JCoDestinationManager.getDestination(SAP_PRD); JCoFunction function destination.getRepository().getFunction(functionName); if (function null) { throw new RuntimeException(RFC 函数不存在: functionName); } params.forEach((k, v) - function.getImportParameterList().setValue(k, v)); function.execute(destination); return function; } catch (JCoException e) { // 区分连接异常和业务异常 if (e.getGroup() JCoException.JCO_ERROR_COMMUNICATION) { throw new RuntimeException(SAP 连接中断检查网络和连接池, e); } throw new RuntimeException(RFC 执行失败: e.getMessage(), e); } } }JCoException的getGroup()能区分是通信层问题还是 SAP 端返回的业务错误。通信层问题通常是连接池耗尽或网络抖动业务错误则是入参不对或权限不足。分开处理能省很多排查时间。4.3 原生库加载失败的兜底方案如果-Djava.library.path没生效可以在应用启动时用代码动态加载。把原生库放在resources/sapjco3/下启动时提取到临时目录再System.load。PostConstruct public void loadNativeLibrary() throws IOException { String os System.getProperty(os.name).toLowerCase(); String libName os.contains(win) ? sapjco3.dll : libsapjco3.so; InputStream in getClass().getResourceAsStream(/sapjco3/ libName); File temp File.createTempFile(sapjco3, libName.substring(libName.lastIndexOf(.))); Files.copy(in, temp.toPath(), StandardCopyOption.REPLACE_EXISTING); System.load(temp.getAbsolutePath()); }这种方式在容器化部署时特别有用因为镜像里不一定有权限写/usr/lib。但要注意临时文件在 JVM 退出后不会自动删除我一般会在PreDestroy里手动清理。5. 避坑与排查sapjco 加载失败、Shiro 拦截异常、Quartz 任务不执行5.1 现象启动报 UnsatisfiedLinkError提示 no sapjco3 in java.library.path原因通常是原生库位数不对或者路径没传对。64 位 JDK 配了 32 位的 so 文件加载时直接报这个错。解决方法是先用file libsapjco3.so确认位数再检查-Djava.library.path是否指向文件所在目录而不是文件本身。另外Linux 下如果 SELinux 开启即使路径对也可能被拦截临时用setenforce 0验证一下。5.2 现象Shiro 拦截了静态资源Thymeleaf 页面样式丢失原因是filterChainDefinitionMap里没有放行/static/**或/css/**。Shiro 的authc过滤器默认拦截所有未匹配的路径。解决方法是把静态资源路径加到anon列表里并且注意LinkedHashMap的顺序/**必须放在最后。5.3 现象Quartz 任务到了时间不执行日志无报错常见原因是任务类没有实现Job接口或者JobDetail的jobClass配错。另一个隐蔽原因是数据库里QRTZ_TRIGGERS表的TRIGGER_STATE卡在ACQUIRED导致调度器认为任务还在运行。解决方法是手动把状态改成WAITING或者重启应用时设置overwriteExistingJobs为 true。5.4 现象RFC 调用返回空值但 SAP 端确认有数据检查JCoFunction的getExportParameterList()是否取错了参数名。SAP 的 RFC 函数参数名大小写敏感IM_PARAM和im_param会被当成两个不同的参数。另外如果返回的是JCoTable需要先table.firstRow()再getValue直接遍历会漏掉第一行。5.5 现象应用运行一段时间后连接池耗尽报 JCoException默认连接池没有上限但 SAP 端有最大连接数限制。如果应用并发高连接数超过 SAP 端的rdisp/max_wprun_time限制就会被强制断开。解决方法是在DestinationDataProvider里设置JCO_PEAK_LIMIT并且确保每次 RFC 调用后连接能正确归还。用try-with-resources或者显式调用JCoContext.end()来释放。6. 验证跑通从登录页到 RFC 调用全链路检查装完不代表跑通我一般会按一条完整链路验证启动应用 → 访问登录页 → Shiro 认证 → 触发一个 Quartz 任务 → 任务里调用 RFC → 数据落库 → Thymeleaf 渲染结果。每一步都有明确的检查点。先看启动日志里有没有JCo initialization succeeded这一行。如果没有说明原生库没加载成功后面所有 RFC 调用都会失败。然后访问http://localhost:8080/login用 Shiro 配置的用户名密码登录如果跳转到首页说明认证通过。接着手动触发一个 Quartz 任务观察日志里是否出现RFC function executed: Z_GET_USER_INFO这样的输出。最后检查数据库里是否有任务执行后写入的记录以及页面上是否渲染出了 SAP 返回的数据。# 验证原生库加载 java -Djava.library.path/usr/lib/sapjco -cp sapjco3.jar com.sap.conn.jco.rt.JCoDestinationManager # 如果输出 JCo initialization succeeded说明库加载正常如果 RFC 调用返回数据但页面不显示检查 Thymeleaf 模板里的变量名是否和 Controller 里model.addAttribute的 key 一致。Thymeleaf 对大小写敏感userName和username是两个不同的变量。注意生产环境不要把sapjco3.jar和原生库放在应用目录下容易被误删。我一般会在部署脚本里加一行校验确认/usr/lib/sapjco/libsapjco3.so存在且位数正确再启动应用。从那以后我每次部署这套环境都会先跑一遍ldd libsapjco3.so确认依赖库完整再启动应用。这个习惯帮我省掉了至少三次半夜被叫起来排查连接失败的经历。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站