Spring集成Webservice(JAX·WS)
初步尝试
- 引入依赖
dependencies {
// 引入Spring Boot Web Starter,提供基础的Web应用支持,包括Tomcat服务器、Spring MVC等组件 implementation("org.springframework.boot:spring-boot-starter-web")
// 引入Spring Boot Web Services Starter,为构建基于Spring的Web服务提供支持,包括对SOAP、RESTful等协议的集成
implementation("org.springframework.boot:spring-boot-starter-web-services")
// 引入Apache CXF Spring Boot Starter for JAX-WS,用于快速配置和启用CXF作为JAX-WS(SOAP Web服务)实现
// 版本号为4.0.4,确保与项目其他组件兼容并获取最新稳定功能及修复
implementation("org.apache.cxf:cxf-spring-boot-starter-jaxws:4.0.4")
// 引入Spring Boot Test Starter,提供测试所需的各类依赖,如JUnit、Mockito、Spring Test等,用于编写和运行应用程序的单元测试和集成测试
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
maven
<!-- pom.xml -->
<project>
<!-- ... -->
<dependencies>
<!-- 引入Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入Spring Boot Web Services Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<!-- 引入Apache CXF Spring Boot Starter for JAX-WS -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>4.0.4</version>
</dependency>
<!-- 引入Spring Boot Test Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- ... -->
</project>
- 实现服务类的接口,以及实现
import jakarta.jws.WebMethod;
import jakarta.jws.WebService;
@WebService(
name = "MyWebService",
targetNamespace = "http://hacoj.com/mywebservice"
)
public interface MyWebService {
@WebMethod
String sayHello(String name);
}
import com.hacoj.springwebservice.service.MyWebService;
import jakarta.jws.WebService;
import org.springframework.stereotype.Service;
@Service
@WebService
public class MyWebServiceImpl implements MyWebService {
@Override
public String sayHello(String name) {
System.err.println("sayHello is called..."); // 只是为了更明显的输出,采用err
return "Hello, " + name + "!";
}
}
- 配置Spring Boot的Web服务
import jakarta.xml.ws.Endpoint;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* CXF配置类,负责初始化CXF相关组件、发布Webservice服务以及配置CXF Servlet。
*/
@Configuration
public class CxfConfig {
/**
* 自动注入Spring Bus实例,它是CXF的核心组件之一,用于管理和配置CXF运行时环境。
*/
@Autowired
private SpringBus bus;
/**
* 自动注入实现了MyWebService接口的服务实现类实例,该实例将被发布为Webservice供外部调用。
*/
@Autowired
private MyWebService myWebServiceImpl;
/**
* 创建并返回Webservice端点(Endpoint)实例,用于发布MyWebService服务。
* 将服务实现类与Spring Bus关联,并指定发布地址为"/1"。
*
* @return Webservice端点实例
*/
@Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(bus, myWebServiceImpl);
endpoint.publish("/1"); // 发布地址
return endpoint;
}
/**
* 创建并返回CXF Servlet的ServletRegistrationBean实例,用于注册CXF Servlet到Spring Boot的Servlet容器中。
* 设置CXF Servlet的映射路径为"/services/*",表示所有以"/services/"开头的HTTP请求都将由CXF Servlet处理。
*
* @return CXF Servlet的ServletRegistrationBean实例
*/
@Bean
public ServletRegistrationBean wsServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/services/*");
}
}
- 启动Spring Boot应用
测试
访问
结果如下
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://impl.service.springwebservice.hacoj.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns2="http://schemas.xmlsoap.org/soap/http" xmlns:ns1="http://hacoj.com/mywebservice" name="MyWebServiceImplService" targetNamespace="http://impl.service.springwebservice.hacoj.com/">
<wsdl:import location="http://localhost:8080/services/1?wsdl=MyWebService.wsdl" namespace="http://hacoj.com/mywebservice"> </wsdl:import>
<wsdl:binding name="MyWebServiceImplServiceSoapBinding" type="ns1:MyWebService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="sayHello">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="sayHello">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="sayHelloResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="MyWebServiceImplService">
<wsdl:port binding="tns:MyWebServiceImplServiceSoapBinding" name="MyWebServiceImplPort">
<soap:address location="http://localhost:8080/services/1"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
调用
实现一个客户端,调用Webservice服务
客户端的实现代码也比较简单,只需要调用服务端的接口,并处理返回结果即可。
- 接口定义类:定义客户端的接口,用于调用服务端的接口。
- 客户端启动类:启动客户端,并调用服务端的接口。
依赖
dependencies {
// 测试依赖配置
testImplementation(platform("org.junit:junit-bom:5.9.1")) // JUnit依赖
testImplementation("org.junit.jupiter:junit-jupiter") // JUnit Jupiter测试框架
// 主要依赖配置
implementation("com.sun.xml.bind:jaxb-impl:4.0.5") // JAXB实现库
implementation("javax.xml.bind:jaxb-api:2.3.1") // JAXB API库
implementation("jakarta.activation:jakarta.activation-api:2.1.3") // Jakarta Activation API库
implementation("jakarta.jws:jakarta.jws-api:3.0.0") // Jakarta JWS API库
implementation("jakarta.xml.ws:jakarta.xml.ws-api:4.0.1") // Jakarta XML Web Services API库
implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.1") // Jakarta XML Binding API库
// Apache CXF相关依赖
implementation("org.apache.cxf:cxf-rt-transports-http-jetty:4.0.4") // CXF Jetty HTTP传输实现
implementation("org.apache.cxf:cxf-rt-frontend-jaxws:4.0.4") // CXF JAX-WS前端支持
implementation("org.slf4j:slf4j-reload4j:2.1.0-alpha1") // 使用Reload4J作为SLF4J的后端日志实现
}
// 输出字符集为UTF-8
tasks.withType<JavaExec> {
jvmArgs = listOf(
"-Dfile.encoding=UTF-8",
"-Dsun.stdout.encoding=UTF-8",
"-Dsun.stderr.encoding=UTF-8"
)
}
// 编译时使用UTF-8字符集
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
maven形式
<!-- pom.xml -->
<project>
<!-- ... -->
<dependencies>
<!-- 测试依赖配置 -->
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.9.1</version>
<type>pom</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<!-- 主要依赖配置 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>jakarta.jws</groupId>
<artifactId>jakarta.jws-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- Apache CXF相关依赖 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
<version>2.1.0-alpha1</version>
</dependency>
</dependencies>
<!-- ... -->
</project>
import jakarta.jws.WebService;
@WebService // Webservice注解表明是一个Webservice的服务类
(targetNamespace = "http://hacoj.com/mywebservice") // 指定服务的命名空间
public interface HelloService {
// 接口名一样
String sayHello(String name); // 方法定义名一样
}
/**
* 客户端调用类,用于通过JAX-WS代理方式访问HelloService Web服务。
*/
public class Client {
/**
* 程序主入口方法。
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建JAX-WS代理工厂对象
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
// 设置要访问的服务地址
jaxWsProxyFactoryBean.setAddress("http://localhost:8899/ws/hello");
// 设置服务接口类,即HelloService
jaxWsProxyFactoryBean.setServiceClass(HelloService.class);
// 使用工厂对象创建HelloService接口的代理实例
HelloService helloService = jaxWsProxyFactoryBean.create(HelloService.class);
System.out.println(helloService.getClass());
// 调用代理实例的方法,向服务端发送请求,并打印返回结果
System.out.println(helloService.sayHello("hacoj"));
}
}
结果如下
class jdk.proxy2.$Proxy36
Hello, hacoj!
深入了解
返回自定义的数据类
直接定义,在Server,Client定义一样的类即可
WebService注解与WebMethod注解
@WebService 注解支持若干属性,用于定制服务的相关特性。主要属性及其作用:
- name
- 作用:指定Web服务的全局唯一标识(endpoint name)。这个名称通常用于客户端调用时构造服务URL的一部分。
- 类型:String
- 默认值:如果不显式设置,会根据类名和服务发布时的配置自动生成。
- targetNamespace
- 作用:定义Web服务的命名空间(namespace),用于在WSDL(Web Services Description Language)文档中标识服务及其元素。
- 类型:String
- 默认值:如果没有指定,可能会根据服务发布的上下文自动确定,或者使用默认值(如不带前缀的类名)。
- serviceName
- 作用:指定Web服务在WSDL文档中的服务名。这对于当WSDL由多个端点组成的服务(例如,具有多个接口实现的单个服务)尤其有用,可以明确区分各个端点的服务名。
- 类型:String
- 默认值:如果不指定,服务名通常根据类名生成。
- portName
- 作用:指定Web服务端口在WSDL文档中的名称。端口定义了服务的访问点,包括绑定协议和地址信息。
- 类型:String
- 默认值:如果不指定,端口名通常根据类名和方法名生成。
- endpointInterface
- 作用:
endpointInterface 属性接收一个字符串值,该值是服务接口的完全限定类名。这个接口定义了服务对外提供的操作(方法)签名和契约。当您在服务实现类上使用 @WebService 注解并指定了 endpointInterface 时,意味着:
服务实现类必须实现该接口:服务实现类(如 MyWebService)必须实现您在 endpointInterface 中指定的接口(如 com.hacoj.springwebservice.service.MyWebService)。这意味着服务实现类需要提供接口中所有方法的具体实现。
接口方法作为Web服务操作:接口中声明的所有公有方法(通常带有 @WebMethod 注解)将作为Web服务对外提供的操作。客户端可以通过这些操作与服务进行交互。接口方法的签名(包括参数类型、返回类型和异常)决定了SOAP消息的结构和交互模式。 - 类型:Class<?>
- 默认值:如果不指定,JAX-WS会自动生成一个基于类中公共非静态方法的SEI。
- wsdlLocation
- 作用:提供一个指向WSDL文件的URL或路径。这个属性允许开发者指定一个预先存在的WSDL文件,用于替代JAX-WS自动生成的WSDL。这对于已经存在WSDL设计或者需要严格遵循特定WSDL规范的场景非常有用。
- 类型:String
- 默认值:如果不指定,JAX-WS会自动生成WSDL文件。
示例
@WebService(
name = "MyWebService",
targetNamespace = "http://hacoj.com/mywebservice",
serviceName = "MyWebServiceService",
portName = "MyWebServicePort",
endpointInterface = "com.hacoj.springwebservice.service.MyWebService"
)
public interface MyWebService {
@WebMethod
String sayHello(@WebParam(name = "id")String name);
@WebMethod(operationName = "getModel")
@WebResult(name = "result")
SimpleModel getSimpleModel(String name);
}
@WebService 注解通常与 @WebMethod、@WebResult、@WebParam 等其他JAX-WS注解一起使用,以精细控制服务操作、参数和结果的映射。
此时如果你还在使用原来的Client,会报错
Caused by: org.apache.cxf.binding.soap.SoapFault: Unmarshalling Error: 意外的元素 (uri:"", local:"arg0")。所需元素为<{}id>
这时修改为一下内容就会正常进行
@WebService
(targetNamespace = "http://hacoj.com/mywebservice")
public interface HelloService {
@WebMethod
String sayHello(@WebParam(name = "id") String name); // 通过注解指定id
@WebMethod()
@WebResult(name = "result")
SimpleModel getModel(String name); // 直接改名字,使得它与服务端的@WebMethod(operationName = "getModel")对应
}
SpringBus起什么作用?
SpringBus通常与Spring Web Service结合使用更多
SpringBus 是Apache CXF框架中一个核心组件,用于管理和配置CXF运行时环境。在集成Spring框架的应用中,SpringBus扮演着至关重要的角色,它将CXF的基础设施与Spring的依赖注入(DI)、生命周期管理等功能紧密结合起来,使得CXF服务的配置、发布、管理更加方便且符合Spring编程模型。
SpringBus的主要特点和作用
CXF上下文管理: SpringBus充当CXF内部的上下文容器,类似于Spring的ApplicationContext。它存储和管理CXF服务相关的各种对象,如服务端点(Endpoints)、数据绑定器(DataBinders)、拦截器(Interceptors)、消息处理器(MessageHandlers)等。通过SpringBus,这些组件能够共享配置、协作处理请求,并遵循统一的生命周期规则。
与Spring集成: SpringBus无缝集成了Spring框架,允许CXF服务和其他组件以Spring Beans的形式存在。这意味着你可以利用Spring的依赖注入、AOP(面向切面编程)、事务管理、资源管理等特性来配置和管理CXF服务。例如,你可以使用@Autowired注解来注入CXF服务实例,或使用@Bean注解在Spring配置类中定义CXF相关的组件。
配置加载: SpringBus可以加载并解析CXF的配置信息,无论是从Spring的XML配置文件、Java配置类还是从特定的CXF配置文件(如cxf.xml)。这些配置可能包括服务端点的地址、绑定、安全设置、拦截器链等。通过SpringBus,这些配置可以与Spring的其他应用配置一起管理,形成统一的应用配置体系。
服务发布与注册: 在使用CXF发布Web服务时,通常会创建一个Endpoint对象,并将其与SpringBus关联。这样做可以使服务端点获得SpringBus提供的上下文支持。此外,SpringBus还可以帮助管理服务端点的生命周期,如发布、更新、撤销等操作。