ARTICLE DETAIL

资讯详情

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

SAP集成实践:基于Spring Boot与JCo的RFC系统开发指南

SAP集成实践:基于Spring Boot与JCo的RFC系统开发指南 简介SAP接口集成场景下基于Spring Boot的Java后台管理系统往往承担数据互通与权限管理双重职责。sapweb项目完整演示了如何整合Spring Boot、MyBatis-Plus、Shiro、Thymeleaf、Quartz 2与SAPJCO3通过RFC函数调用实现SAP数据对外暴露和外部数据回写SAP并配合定时任务完成数据同步适合Java后端开发及SAP实施人员参考。压缩包共336个文件约9.46MB。98个Java源码是核心控制器、服务与RFC调用实现41个JS文件处理管理后台的交互与MVVM逻辑配合28个HTML、25个CSS组成Thymeleaf渲染的响应式页面13个XML承担MyBatis映射与Spring配置另有SQL脚本、YML/Properties环境配置及适配不同系统的JCo链接库dll/so/jnilib便于直接移植调试。资源已有428人浏览学习。目录按后台模块、前端资源、数据库脚本等分层组织包含Shiro权限、Druid数据源、Quartz任务调度等企业级配置示例可作为SAP接口开发或Spring Boot后台项目的可运行脚手架减少从零搭建与SAP联调中的常见踩坑。 做企业系统集成的朋友应该对SAP不陌生。这些年我经手了不少SAP外围系统最常用也最稳的一条路就是用Java技术栈搭一个web应用通过SAP官方提供的JCo连接器把RFC接口的能力暴露给内部用户或其他业务系统。今天聊的这个项目就是一套完整的参考实现后端用Springboot 2.x MyBatis做业务和数据持久化前端用Thymeleaf渲染页面任务调度交给Quartz权限控制用Shiro核心的SAP交互则通过SAP JCo 3.x完成。这套组合最大的好处是每一层技术选型都足够成熟社区资料多踩坑成本低非常适合那些需要快速交付又要求稳定运行的SAP周边系统。1. 整体架构与思路拆解1.1 为什么是这套技术栈先说Springboot。它在这个项目里的角色是“胶水层”把HTTP接口、依赖注入、数据库连接池、外部配置全部统一管理起来。相比传统的SSMSpring SpringMVC MyBatis手写一大堆XML配置Springboot能省掉至少30%的搭建时间尤其是处理SAP连接池这种需要管理生命周期的资源时Springboot的ConfigurationProperties配合Bean可以很干净地完成初始化。MyBatis在这里不是主角但非常重要。SAP的RFC接口返回的数据往往是多表结构比如物料主数据、BOM、库存列表这些数据在写入本地数据库或做二次加工时用一个灵活的ORM比JPA更顺手。MyBatis允许手写SQL对复杂查询的掌控力更强而且配合PageHelper分页插件做列表展示非常方便。Thymeleaf选它是因为Springboot对它支持最好页面模板和Java代码的交互很自然。有人喜欢前后端分离但在这个场景里内部管理系统的用户量通常不大服务端渲染维护成本更低也不需要考虑跨域和Token刷新的问题。Quartz 2负责两类任务一是定时从SAP拉取数据比如每天凌晨同步物料库存二是定时向SAP推送状态比如定时触发某个审批流程。用Quartz而不是Spring自带的Scheduled是因为Quartz支持持久化、集群部署、动态修改触发时间真实项目里这些能力早晚会用到。Shiro管登录认证和接口权限。SAP系统通常已经有了一套账号体系但外围系统不建议直接使用SAP账号最好在本地维护一份用户表通过Shiro把登录态管理起来再按角色划分菜单和按钮权限。1.2 SAP JCo在架构中的位置SAP JCoJava Connector是整个系统的心脏。它分两个部分SAP JCo Middleware中间件和SAP JCo APIJava工具库。前者负责与SAP服务器建立RFC连接后者是Java开发者直接调用的接口。JCo的连接管理有两种方式单连接模式和连接池模式。实际项目里必须用连接池一方面是因为SAP的license对并发连接数有严格限制另一方面是频繁建立和释放连接的开销非常大。JCo自带的JCoDestinationManager本身就支持连接池配置我们只需要在项目中给它提供一份配置文件即可通常是sap-config.properties。我把整个SAP交互层设计成三块sapjco-layer ├── config # JCo连接配置、自定义连接池封装 ├── client # RFC调用客户端封装JCoDestination获取、错误处理 └── dto # 请求/响应模型的映射对象这样的好处是业务代码里不直接出现JCo的API全部通过自定义的SapClient调用未来如果SAP侧接口变了只需要改对应DTO和翻译层即可。2. 环境准备与核心依赖配置2.1 SAP JCo的安装与部署JCo最大的坑在于它依赖本机原生库。JCo的jar包sapjco3.jar只是一个Java wrapper真正干活的是sapjco3.dllWindows或libsapjco3.soLinux。所以安装分三步到SAP官网下载对应操作系统的JCo压缩包解压后你会看到lib和docs两个目录。把sapjco3.jar放进项目的lib目录如果你的构建工具是Maven可以把它手动安装到本地仓库mvn install:install-file -Dfilelib/sapjco3.jar -DgroupIdcom.sap -DartifactIdsapjco3 -Dversion3.1.0 -Dpackagingjar把原生的.dll或.so文件放到JVM的运行目录下或者放到系统的PATH路径中。我在Linux服务器上习惯把libsapjco3.so放到/usr/lib64下然后重启Java进程避免每次部署还要手动指定-Djava.library.path。有个细节需要注意JCo区分32位和64位并且要求JDK的位数和原生库完全一致。java -version显示的是64-Bit就一定要用64位的JCo包不然启动时直接报UnsatisfiedLinkError。2.2 Springboot核心配置application.yml配置文件的重点有三个数据库连接、MyBatis映射、Quartz线程池。这是最影响运行稳定性的一层。spring: datasource: url: jdbc:mysql://localhost:3306/sapweb?useUnicodetruecharacterEncodingutf8 username: root password: 123456 hikari: maximum-pool-size: 20 minimum-idle: 5 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.sapweb.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl sap: jco: destination: name: RFC_DEST client: 800 user: SAP_USER passwd: *** lang: ZH host: 192.168.1.100 sysnr: 00 poolCapacity: 10 peakLimit: 20这里解释一下JCo配置里的几个关键参数poolCapacity连接池中最多保持的RFC连接数。如果业务高峰时并发调RFC这个值太大会造成SAP侧的压力太小则会出现JCoException: PeakLimit reached。peakLimit同一时刻最多可以创建的连接数包括借出中还没归还的。这个值必须 poolCapacity否则空闲连接还没复用完新请求就被拒绝。sysnrSAP系统编号通常为00或01要和SAP Basis同事确认不能凭猜。2.3 MyBatis多模块配置的坑如果项目是多模块比如api模块、rpc模块、admin模块分开MyBatis的Mapper扫描路径很容易出问题。要么在启动类上明确指定MapperScan(com.example.**.mapper)要么在MyBatis配置文件中用typeAliasesPackage指定实体包路径。我在这个项目里用的是多数据源方案本地库 SAP接口数据缓存库给每个数据源单独配了一个SqlSessionFactory避免事务管理器混淆。有个经验MyBatis的Mapper接口和XML文件的namespace必须完全一致中文注释里的分号、引号也不能复制错不然接口注入时报BindingException是最难排查的。3. 核心业务实现RFC交互的全流程3.1 RFC接口的分类与调用封装SAP的RFC接口可以粗略分成两类同步RFC和异步RFCaRFC / tRFC。这个项目里主要用到同步RFC比如BAPI_MATERIAL_GETLIST获取物料列表BAPI_MATERIAL_AVAILABILITY查库存可用量这个对应热词里的MD07/MD04常见场景BAPI_GOODSMVT_CREATE创建货物移动凭证BAPI_ACC_DOCUMENT_POST生成会计凭证BAPI_BOM_GETDETAIL查BOM明细封装调用的核心代码不复杂关键点是异常处理和返回结构解析。public RfcResult execute(RfcFunction function) { JCoDestination destination JCoDestinationManager.getDestination(RFC_DEST); JCoFunction jcoFunction destination.getRepository().getFunction(function.getName()); if (jcoFunction null) { throw new SapRfcException(RFC函数不存在: function.getName()); } // 填入请求参数 JCoParameterList importParams jcoFunction.getImportParameterList(); for (Map.EntryString, Object entry : function.getImportParams().entrySet()) { importParams.setValue(entry.getKey(), entry.getValue()); } // 表格类型参数比如条件行、多行数据 JCoParameterList tableParams jcoFunction.getTableParameterList(); JCoTable table tableParams.getTable(function.getTableParamName()); ... // 填充表格行 try { jcoFunction.execute(destination); } catch (AbapException e) { JCoFunction abortFunction e.getFunction(); // 解析BAPI的RETURN表获取具体错误消息 JCoTable returnTable abortFunction.getTableParameterList().getTable(RETURN); ... } return convertToRfcResult(jcoFunction); }这里有个很关键的细节调用BAPI时必须检查RETURN表里的消息类型。如果TYPE为E错误或A异常即使JCo的execute()没有抛异常业务上也算失败。我在封装层里统一做了这个判断并且把MESSAGE字段可能带**号占位符用参数列表里的实际值做了替换这样前端或者日志里看到的错误信息才可读。3.2 RFC返回数据的解析与本地化SAP返回的数据结构很“经典”有单值字段、有内表类似Java的List、还有嵌套结构和嵌套表。我第一次对接BOM展开接口时返回的嵌套深度高达四层直接用JCoStructure一层层get代码写得非常啰嗦。后来的做法是为每个RFC接口写一个独立的DTO转换器把JCo的结构体转成Java的POJO下层业务只面对POJO。public class MaterialStockDto { private String materialCode; private String plant; private String storageLocation; private BigDecimal stockQty; private String baseUnit; }再配合一个简单工具类用反射或者手动映射完成JCoRecord到MaterialStockDto的转换。这样做还有一个额外的好处SAP字段名通常是全大写带下划线比如WERKS而Java习惯是驼峰命名转换层正好把这个差异消化掉。3.3 动态调用与元数据缓存JCo的JCoRepository会对已调用的函数做缓存。但有个常见问题如果SAP侧修改了函数接口增加了新参数本地JCo的缓存不会自动失效。在开发联调阶段这会让明明SAP已经更新了的接口一直报参数不存在。解决方案有两种一是每次调用前不通过destination.getRepository().getFunction(name)而是用destination.getRepository().clear()强制清空缓存再获取二是在生产环境使用JCoDestinationManager.getDestination()并设置合理的缓存过期策略。在实际项目中我推荐在启动时主动预加载频繁使用的函数到内存这样还能提前暴露开发阶段没发现的接口不匹配问题。3.4 Quartz任务调度的集成要点这个项目的Quartz用得非常克制只开发了三个任务每日凌晨同步物料库存快照从SAP拉所有物料可用量写入本地表每小时同步销售订单状态每周同步BOM变更记录集成Quartz时有两个必须处理的点第一任务持久化。如果你直接用Springboot整合Quartz会发现默认的RAMJobStore不持久化服务一重启所有计划都丢失。生产系统必须用JDBCJobStore也就是把job和trigger信息存在数据库表里。Quartz官方提供了一套建表脚本tables_mysql_innodb.sql在MySQL里执行一遍即可。第二Job里拿不到Spring的Bean。Quartz的Job实例是它自己通过反射创建的不在Spring容器里。我写了一个SpringJobFactory重写newJob()方法从ApplicationContext里直接获取Job实例这样Job里就能正常注入Mapper和SapClient。public class SpringJobFactory extends SpringBeanJobFactory { Autowired private ApplicationContext applicationContext; Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Object jobInstance super.createJobInstance(bundle); applicationContext.getAutowireCapableBeanFactory().autowireBean(jobInstance); return jobInstance; } }配置完成后每次任务触发都会拿到一个Spring托管的全新实例依赖注入完全正常。4. Shiro权限设计与Session管理4.1 登录认证和密码策略Shiro在这套系统里负责兜底安全。我在ShiroConfig里配置了三层过滤链/login匿名访问、/static/**匿名访问、其余路径一律需要认证。密码存库时用的是BCryptPasswordEncoderSpring Security的这个类可以直接拿来用比MD5安全很多而且自带随机盐同一个密码每次加密结果都不同防止彩虹表攻击。Shiro默认的HashCredentialsMatcher不适用BCrypt所以自定义了一个Matcherpublic class BcryptMatcher implements CredentialsMatcher { private BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { return encoder.matches(new String((char[]) token.getCredentials()), (String) info.getCredentials()); } }4.2 权限粒度控制权限设计不用太复杂但按钮级权限值得做。我在数据库里维护了三张表sys_user、sys_role、sys_permission中间用两张关联表连接。Shiro的AuthorizationInfo里把role和permission都填上页面里用Thymeleaf的sec:authorize标签控制按钮显示隐藏。Thymeleaf和Shiro的整合有个现成的库thymeleaf-extras-shiro引入后在HTML里就能写button sec:authorizehasPermission(sap:stock:sync)立即同步库存/button这样就避免了“只隐藏、不校验”的尴尬。后端接口方法上再配一层RequiresPermissions两层保护。4.3 会话管理的坑Shiro的DefaultSecurityManager默认使用DefaultWebSessionManager会创建自己的一套Session和HttpSession是两码事。这在集群部署时需要特别注意如果多节点负载均衡登录态会丢因为另外一台服务器不认识你第一次登录创建的Session。我当时的处理方式是统一使用Redis共享Session同时用Shiro的AbstractSessionDAO把SessionId存到Redis。这样只要用户请求带着同一个Cookie无论落到哪个节点都能通过认证。代码改动不多但解决了未来的扩容问题。5. 常见问题排查与避坑指南5.1 JCo连接异常现象JCoException: (104) JCO_ERROR_RESOURCE_IN_USE或PeakLimit reached。原因连接池配置过小同时并发RFC的数量超过了peakLimit。解决思路首先看监控是瞬时峰值还是持续增长。如果是瞬时峰值调大peakLimit和poolCapacity如果是持续增长多半是代码里借了连接没释放。JCo的execute()方法执行完之后要保证JCoDestinationManager的释放逻辑被执行建议把调用包在try-finally里或者使用try-with-resources风格。经验补充JCo调用本身是线程安全的JCoDestination实例可以被多个线程共享。但JCoFunction不是线程安全的不能把Function对象缓存在静态变量里复用每次调用都要从Repository获取新实例这个坑我踩过并发一高就会出现参数串号。5.2 SAP返回数据乱码现象从SAP读到的物料描述或文本字段里的中文乱码。原因JCo连接配置里的lang参数没设置或者本地的JVM默认编码不是UTF-8。解决思路配置文件里指定langZH并且确保Springboot的server.servlet.encoding是强制UTF-8。如果数据还是乱码检查SAP的classic表字段是否是LANG依赖的比如MAKTX跟语言相关在不同语言环境下返回的内容本身就不同。5.3 MyBatis Mapper扫描不到现象启动报Invalid bound statement (not found): com.example.mapper.StockMapper.selectByMaterial。原因绝大多数情况是Mapper接口和XML文件没有正确绑定。解决思路按顺序检查三点XML文件是否在mapper-locations参数指定的路径下Mapper接口的包名和XML的namespace是否完全一致XML中的id和接口方法名是否一致经验补充多模块项目里如果非启动模块放了Mapper XML构建工具Maven默认不会把src/main/java下的XML文件打进jar包。需要在pom.xml里加一段resources配置单独把**/*.xml也当作资源文件打包。5.4 一个典型的联调时间线对接RFC接口时我习惯于这样安排进度第一个半天确认接口清单和SAP开发或Basis对参数定义第二个半天用JCo的JCoFunction.getImportParameterList()打印一下确认参数名和嵌套结构第三天写完转换器联调核心场景成功路径第四天处理异常场景尤其是SAP弹出来的E/RETURN消息怎么翻译成中文提示这个节奏在多个项目里都是稳稳的。6. 部署与调优笔记6.1 服务器层面的几个参数JCo是重I/O的组件我在部署时重点关注三个Linux参数ulimit -n默认1024太小RFC连接池加上数据库连接池很容易突破建议调大到65535。net.core.somaxconn如果RFC调用频繁经过网络层TCP的连接队列满了会导致连接超时调整为1024以上。JVM堆内存SAP返回大表数据比如一次拉几万行物料内存吃紧是常事。我把-Xms和-Xmx都设为物理内存的一半并且预留了逃逸分析的空间。GC策略上JCo调用通常会产生大量短命对象用G1比CMS已废弃更合适。6.2 数据库表设计的两个建议一是所有和SAP交互的日志表字段类型尽量用String而不是Text。MySQL的Text类型不能直接加索引排查问题时想用material_code查日志都难。二是同步过来的SAP数据建议加一个sync_batch_no字段每次同步都生成一个新的批次号。这样即使上游数据源有主数据变更也能通过批次号快速做全量对账而不是一条条人工核对。6.3 监控与预警在调度任务里加一个简单的看板表记录每次同步的开始时间、结束时间、成功/失败状态、同步行数。每天早上到办公室第一件事就是看这张表如果某天同步失败但没有告警影响会延迟被发现后续对账会非常麻烦。我在项目里用了springboot的Actuator暴露/health端点再配合阿里云监控报警对关键任务做了失败告警。不要为了省事跳过这一步。7. 这个项目的扩展方向如果后续要往更深的方向走有几个比较顺其自然的演进引入Flowable工作流引擎。SAP的审批流程如果涉及多级审批、超时自动提醒Quartz倒也能做但Flowable的管理能力明显更适合。Springboot下通过flowable-spring-boot-starter接入并不难网上可以找到完整的整合教程。做一个通用RFC网关模块。前端或外部系统如果也需要调SAP与其每个系统单独对接JCo不如由这个sapweb提供一组统一REST接口内部做RFC路由和限流这样SAP侧的改动只需改这一处。引入消息队列如Kafka或RabbitMQ。如果经常要批量拉取大数据或者做异步回写引入MQ做削峰填谷是个不错的选择。JCo调RFC是同步的但可以先把请求扔到队列里消费者按SAP可承受的速率去消费这样不会压垮SAP。我在做类似项目时通常会先画一张“哪些是稳定的、哪些是可替换的”设计图。SAP相关的接口协议、字段含义是相对稳定的而Java技术栈那一层只要拆得足够干净未来不管是换ORM还是换前端方案代价都很小。做SAP集成的核心思路其实就是把SAP当成一个“只有RFC接口的大黑盒”系统设计上保证外围功能不会因为SAP侧一次配置变更而崩掉。与其反复猜不如把契约定清楚把错误处理做到位。上面这些经验都是在项目线上运行时一点点沉淀下来的。如果你也正好在折腾SAP和Java集成的项目照着这套思路去做至少能在环境构建和踩坑排查上少花一半时间。本文还有配套的精品资源点击获取
返回列表