从 Restful 迁移到 SOAP 风格,首当其冲的是数据格式与接口定义差异带来的适配难题。Restful 以 JSON 格式为主,强调资源的轻量化交互,而 SOAP 采用复杂的 XML 协议,需严格遵循 WSDL 规范进行接口描述。
一、环境准备与依赖引入
若依框架本身基于 Spring Boot 构建,而在整合 CXF 时,由于使用的是 Spring 2.5.15 版本,为确保兼容性,只能选用 CXF 3 系列版本。在项目的pom.xml文件中添加以下依赖:
xml
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.x</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-features-logging</artifactId>
<version>3.x</version>
</dependency>
二、notice 接口改造实践
在若依框架中,notice模块的增删改查接口通常以 Restful 风格实现,通过@GetMapping、@PostMapping等注解映射 HTTP 请求。为减少迁移工作量,可直接复制原有的NoticeController类,通过添加 JAX-WS 相关注解,将其转换为 SOAP 风格的 WebService 接口,同时保留原接口定义和业务逻辑。
2.1 复制并改造NoticeController
将原有的NoticeController类复制一份,重命名为SysNoticeSoapService,删除原有的@RestController、@RequestMapping等 Restful 风格注解,并添加 JAX-WS 注解,同时保留若依框架原有的权限控制、日志记录等逻辑。改造后的代码如下:
java
package com.ruoyi.web.soap.system;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.WebServicePath;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.service.ISysNoticeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.jws.WebParam;
import javax.jws.WebService;
@WebService(targetNamespace = "http://localhost/notice")
@WebServicePath("notice")
@Component("noticeService")
public class SysNoticeSoapService extends BaseController {
@Autowired
private ISysNoticeService noticeService;
// 保留若依框架权限控制注解,确保SOAP接口调用权限与原Restful接口一致
@PreAuthorize("@ss.hasPermi('system:notice:list')")
public TableDataInfo selectNoticeList(@Validated @WebParam(name = "notice") SysNotice notice) {
return getDataTable(noticeService.selectNoticeList(notice));
}
@PreAuthorize("@ss.hasPermi('system:notice:query')")
public AjaxResult selectNoticeById(@WebParam(name = "noticeId") Long noticeId) {
return success(noticeService.selectNoticeById(noticeId));
}
/**
* 新增通知公告,保留日志记录注解,便于追溯操作
*/
@PreAuthorize("@ss.hasPermi('system:notice:add')")
@Log(title = "通知公告", businessType = BusinessType.INSERT)
public AjaxResult insertNotice(@Validated @WebParam(name = "notice") SysNotice notice) {
notice.setCreateBy(getUsername());
return toAjax(noticeService.insertNotice(notice));
}
/**
* 修改通知公告
*/
@PreAuthorize("@ss.hasPermi('system:notice:edit')")
@Log(title = "通知公告", businessType = BusinessType.UPDATE)
public AjaxResult updateNotice(@Validated @WebParam(name = "notice") SysNotice notice) {
notice.setUpdateBy(getUsername());
return toAjax(noticeService.updateNotice(notice));
}
/**
* 删除通知公告
*/
@PreAuthorize("@ss.hasPermi('system:notice:remove')")
@Log(title = "通知公告", businessType = BusinessType.DELETE)
public AjaxResult deleteNoticeById(@PathVariable @WebParam(name = "noticeId") long noticeId) {
return toAjax(noticeService.deleteNoticeById(noticeId));
}
}
在上述代码中,@WebService注解定义了服务的命名空间;@WebParam注解明确了方法参数在 SOAP 消息中的 XML 表示。同时,代码中保留了若依框架特有的@PreAuthorize权限控制注解和@Log日志记录注解,使得改造后的 SOAP 接口在安全管控和操作审计方面与原 Restful 接口保持一致。此外,@WebServicePath("notice")注解,确保 CXF 能够正确识别并发布服务。
2.2 批量发布配置实现
通过自定义@WebServicePath注解标记需要发布的服务类,并在配置类中扫描所有带有@WebService注解的 Bean,结合@WebServicePath指定的路径实现批量发布。具体代码如下:
ini
package com.ruoyi.web.core.config;
import com.ruoyi.common.annotation.WebServicePath;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import javax.jws.WebService;
import java.util.Map;
@Configuration
public class CxfConfig {
@Autowired
private SpringBus bus;
@Autowired
private ApplicationContext applicationContext;
@Bean
public void publishAllSoapServices() {
// 扫描所有带 @WebService 注解的 Bean
Map<String, Object> webServiceBeans = applicationContext.getBeansWithAnnotation(WebService.class);
for (Map.Entry<String, Object> entry : webServiceBeans.entrySet()) {
Object bean = entry.getValue();
Class<?> beanClass = bean.getClass();
// 只发布带 @WebServicePath 的 Bean
Class<?> targetClass = AopUtils.getTargetClass(bean);
WebServicePath soapPublish = targetClass.getAnnotation(WebServicePath.class);
if (soapPublish == null) {
continue;
}
String path = soapPublish.value();
EndpointImpl endpoint = new EndpointImpl(bus, bean);
endpoint.publish(path);
System.out.println("已发布SOAP服务: " + path + " -> " + beanClass.getName());
}
}
}
2.3 自定义注解WebServicePath的实现
上述批量发布逻辑的核心在于自定义注解WebServicePath,其作用是为 WebService 服务类指定发布路径,实现路径配置与代码的解耦。该注解的代码实现如下:
java
package com.ruoyi.common.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServicePath {
String value(); // SOAP服务发布路径,如 "/services/${notice}"
}
2.3 实现思路解析
- 注解标记:通过@WebService注解标识该类为 WebService 服务类,@WebServicePath注解指定服务发布的路径(如notice),实现服务路径与类的解耦。
- 包扫描:利用 Spring 的ApplicationContext获取所有带有@WebService注解的 Bean,无需手动注入每个服务类,减少配置冗余。
- 批量发布:遍历扫描到的 Bean,通过AopUtils.getTargetClass获取真实类,判断是否带有@WebServicePath注解,若有则通过EndpointImpl发布服务,发布路径为@WebServicePath注解的值。
三、为何不在服务层处理 WebService 改造
在若依框架的 WebService 改造过程中,选择新增业务层而非直接在服务层进行处理,主要受WSDL 规范约束 与前后端兼容性需求两方面因素影响。
WSDL作为 WebService 的接口描述标准,对接口类型有严格要求。在 Restful 风格的服务中,接口返回值类型灵活多样,如AjaxResult、TableDataInfo等封装了业务状态和数据的自定义类型。但在 SOAP 服务中,WSDL 要求接口返回值类型必须与定义完全对应,无法像 Restful 那样通过框架灵活转换。若在服务层直接改造,需对原有接口返回值类型进行大量调整,甚至可能需要修改业务逻辑以适配 WSDL 规范,这将大幅增加开发工作量和出错风险。
若依框架前端系统已深度适配 Restful 接口返回的数据格式,如通过AjaxResult解析业务状态码和数据内容。而 SOAP 服务的返回格式与 Restful 存在本质差异,若在服务层改变返回值类型,前端接收的数据格式将完全不同,这意味着前端代码需同步进行大规模重构,包括数据解析、页面渲染逻辑等,无疑会显著延长项目工期。
因此,通过新增SysNoticeSoapService这一业务层,保留原有的接口定义和业务逻辑,仅在该层添加 JAX-WS 注解进行协议转换,既能满足 WSDL 规范要求,又能最大限度减少对原有服务层和前端系统的影响,实现从 Restful 到 SOAP 风格的高效迁移。