因業務需求需對外提供 SOAP WebService,技術棧為 Spring Boot 2.x + Apache CXF 3.1.6。Maven 核心依賴:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.1.6</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.6</version>
</dependency>
public class User implements Serializable {
private String userId;
private String username;
private String age;
private Date updateTime;
// Getter/Setter 省略
}
@WebService
public interface UserService {
@WebMethod
String getName(@WebParam(name = "userId") String userId);
@WebMethod
User getUser(@WebParam(name = "userId") String userId);
}
@WebService(
targetNamespace = "https://service.demo.paybay.cn/",
endpointInterface = "cn.paybay.demo.service.UserService"
)
public class UserServiceImpl implements UserService {
private Map<String, User> userMap = new HashMap<>();
public UserServiceImpl() {
// 初始化測試數據
User u1 = new User(); u1.setUserId("411001"); u1.setUsername("zhansan"); u1.setAge("20"); u1.setUpdateTime(new Date());
User u2 = new User(); u2.setUserId("411002"); u2.setUsername("lisi"); u2.setAge("30"); u2.setUpdateTime(new Date());
User u3 = new User(); u3.setUserId("411003"); u3.setUsername("wangwu"); u3.setAge("40"); u3.setUpdateTime(new Date());
userMap.put(u1.getUserId(), u1);
userMap.put(u2.getUserId(), u2);
userMap.put(u3.getUserId(), u3);
}
@Override public String getName(String userId) { return "liyd-" + userId; }
@Override public User getUser(String userId) { return userMap.get(userId); }
}
核心坑點:CXF 發布的是實現類,預設命名空間為實現類包路徑(https://impl.service.demo.paybay.cn/),但 WSDL 對外暴露的是介面類,客戶端按介面類命名空間(https://service.demo.paybay.cn/)調用,導致命名空間不匹配報錯:
No operation was found with the name {https://impl.service.demo.paybay.cn/}getUser
在實現類上显式指定 targetNamespace 與介面一致:
@WebService(
targetNamespace = "https://service.demo.paybay.cn/", // 必須與介面命名空間一致
endpointInterface = "cn.paybay.demo.service.UserService"
)
發布後 WSDL 的 targetNamespace 即為介面命名空間,客戶端調用正常。
@Configuration
public class CxfConfig {
// 註冊 CXF Servlet,映射路徑 /test/*
@Bean
public ServletRegistrationBean<CXFServlet> dispatcherServlet() {
return new ServletRegistrationBean<>(new CXFServlet(), "/test/*");
}
// 提供 SpringBus
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
// 註冊服務實現 Bean
@Bean
public UserService userService() {
return new UserServiceImpl();
}
// 發布端點
@Bean
public Endpoint endpoint(SpringBus springBus, UserService userService) {
EndpointImpl endpoint = new EndpointImpl(springBus, userService);
endpoint.publish("/user"); // 相對路徑
return endpoint;
}
}
https://localhost:8080/{context-path}/test/user?wsdl
注意:必須包含專案上下文路徑(context-path),否則會 404。若 server.servlet.context-path=/webservice,則完整路徑為:
https://localhost:8080/webservice/test/user?wsdl
瀏覽器顯示 WSDL XML 文檔,包含 <wsdl:definitions>、<wsdl:service> 等標準結構。
public class Client {
public static void main(String[] args) throws Exception {
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
org.apache.cxf.endpoint.Client client = dcf.createClient(
"https://localhost:8080/webservice/test/user?wsdl"
);
// 呼叫 getUser 方法
Object[] result = client.invoke("getUser", "411001");
System.out.println("調用結果:" + result[0]);
}
}
src/main/java ├── cn.paybay.demo │ ├── entity.User │ ├── service.UserService │ ├── service.impl.UserServiceImpl │ └── config.CxfConfig └── Application.java
Serializable,建議加上 @XmlAccessorType(XmlAccessType.FIELD) 控制序列化@WebFault 自定義 SOAP FaultSpring Boot + CXF 發布 WebService 核心步驟:
cxf-rt-frontend-jaxws、cxf-rt-transports-http@WebService 介面)與實現類targetNamespace 與介面一致CXFServlet、SpringBus、EndpointImpl.publish()避開命名空間不一致這一最大坑點,即可快速構建穩定的 SOAP 服務。