一. 服务端
1. 技术栈
- JDK 1.8,Eclipse,Maven -- 开发环境
- SpringBoot -- 基础应用程序框架
- wsdl4j -- 为我们的服务发布 WSDL
- SOAP-UI -- 用于测试我们的服务
- JAXB maven 插件 -- 用于代码生成
2.创建 Spring Boot 项目
添加 Wsdl4j 依赖关系
编辑pom.xml
并将此依赖项添加到您的项目中。
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
3. 创建 SOAP 域模型并生成 Java 代码
当我们遵循合同优先的方法来开发服务时,我们需要首先为我们的服务创建域(方法和参数)。 为简单起见,我们将请求和响应都保留在相同的 XSD 中,但在实际的企业用例中,我们将有多个 XSD 相互导入以形成最终定义。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://www.howtodoinjava.com/xml/school"
targetNamespace="https://www.howtodoinjava.com/xml/school" elementFormDefault="qualified">
<xs:element name="StudentDetailsRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="StudentDetailsResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="Student" type="tns:Student"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Student">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="standard" type="xs:int"/>
<xs:element name="address" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
将以上文件放置在项目的resources
文件夹中。
4. 将 XSD 的 JAXB maven 插件添加到 Java 对象生成
我们将使用jaxb2-maven-plugin
有效地生成域类。 现在,我们需要将以下 Maven 插件添加到项目的pom.xml
文件的插件部分。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory>${project.basedir}/src/main/resources/</schemaDirectory>
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<clearOutputDir>false</clearOutputDir>
</configuration>
</plugin>
该插件使用 XJC 工具作为代码生成引擎。 XJC 将 XML 模式文件编译为完全注解的 Java 类。
现在执行上面的 maven 插件以从 XSD 生成 Java 代码。
5. 创建 SOAP Web 服务端点
StudentEndpoint
类将处理对服务的所有传入请求,并将调用委派给数据存储库的finder
方法。
package com.example.howtodoinjava.springbootsoapservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import com.howtodoinjava.xml.school.StudentDetailsRequest;
import com.howtodoinjava.xml.school.StudentDetailsResponse;
@Endpoint
public class StudentEndpoint
{
private static final String NAMESPACE_URI = "https://www.howtodoinjava.com/xml/school";
private StudentRepository StudentRepository;
@Autowired
public StudentEndpoint(StudentRepository StudentRepository) {
this.StudentRepository = StudentRepository;
}
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "StudentDetailsRequest")
@ResponsePayload
public StudentDetailsResponse getStudent(@RequestPayload StudentDetailsRequest request) {
StudentDetailsResponse response = new StudentDetailsResponse();
response.setStudent(StudentRepository.findStudent(request.getName()));
return response;
}
}
这里有一些关于注解的细节:
- @Endpoint向 Spring WS 注册该类,作为处理传入 SOAP 消息的潜在候选者。
- 然后,Spring WS 使用@PayloadRoot根据消息的名称空间和 localPart 选择处理器方法。 请注意此注解中提到的命名空间 URL 和请求载荷根请求。
- @RequestPayload表示传入的消息将被映射到方法的请求参数。
- @ResponsePayload注解使 Spring WS 将返回的值映射到响应载荷。
创建数据存储库
如前所述,我们将使用硬编码的数据作为此演示的后端,让我们添加一个名为StudentRepository.java
并带有 Spring @Repository
注解的类。 它只会将数据保存在HashMap
中,并且还会提供一种称为findStudent()
的查找器方法。
package com.example.howtodoinjava.springbootsoapservice;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import com.howtodoinjava.xml.school.Student;
@Component
public class StudentRepository {
private static final Map<String, Student> students = new HashMap<>();
@PostConstruct
public void initData() {
Student student = new Student();
student.setName("Sajal");
student.setStandard(5);
student.setAddress("Pune");
students.put(student.getName(), student);
student = new Student();
student.setName("Kajal");
student.setStandard(5);
student.setAddress("Chicago");
students.put(student.getName(), student);
student = new Student();
student.setName("Lokesh");
student.setStandard(6);
student.setAddress("Delhi");
students.put(student.getName(), student);
student = new Student();
student.setName("Sukesh");
student.setStandard(7);
student.setAddress("Noida");
students.put(student.getName(), student);
}
public Student findStudent(String name) {
Assert.notNull(name, "The Student's name must not be null");
return students.get(name);
}
}
6. 添加 SOAP Web 服务配置 Bean
创建带有@Configuration
注解的类以保存 bean 定义。
package com.example.howtodoinjava.springbootsoapservice;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
@EnableWs
@Configuration
public class Config extends WsConfigurerAdapter
{
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext)
{
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/service/*");
}
@Bean(name = "studentDetailsWsdl")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema)
{
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("StudentDetailsPort");
wsdl11Definition.setLocationUri("/service/student-details");
wsdl11Definition.setTargetNamespace("https://www.howtodoinjava.com/xml/school");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
@Bean
public XsdSchema countriesSchema()
{
return new SimpleXsdSchema(new ClassPathResource("school.xsd"));
}
}
-
Config
类扩展了WsConfigurerAdapter,它配置了注解驱动的 Spring-WS 编程模型。 -
MessageDispatcherServlet -- Spring-WS 使用它来处理 SOAP 请求。 我们需要向该 servlet 注入
ApplicationContext
,以便 Spring-WS 找到其他 bean。 它还声明了请求的 URL 映射。 -
DefaultWsdl11Definition
使用XsdSchema
公开了标准的 WSDL 1.1。 Bean 名称studentDetailsWsdl
将是将公开的 wsdl 名称。 它可以在 http:// localhost:8080 / service / studentDetailsWsdl.wsdl 下找到。 这是在 Spring 公开合约优先的 wsdl 的最简单方法。此配置还在内部使用 WSDL 位置 servlet 转换
servlet.setTransformWsdlLocations( true )
。 如果我们看到导出的 WSDL,则soap:address
将具有localhost
地址。 同样,如果我们改为从分配给已部署机器的面向公众的 IP 地址访问 WSDL,我们将看到该地址而不是localhost
。 因此,端点 URL 根据部署环境是动态的。
- Spring Boot SOAP Web 服务演示
使用mvn clean install
进行 maven 构建,然后使用java -jar target\spring-boot-soap-service-0.0.1-SNAPSHOT.jar
命令启动应用程序。 这将在默认端口8080
中启动一台 tomcat 服务器,并将在其中部署应用程序。
1)现在转到http://localhost:8080/service/studentDetailsWsdl.wsdl
,查看 WSDL 是否正常运行。
WSDL 已生成
2)一旦成功生成了 WSDL,就可以使用该 WSDL 在 SOAP ui 中创建一个项目并测试该应用程序。 样品请求和响应如下。
请求:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="https://www.howtodoinjava.com/xml/school">
<soapenv:Header/>
<soapenv:Body>
<sch:StudentDetailsRequest>
<sch:name>Sajal</sch:name>
</sch:StudentDetailsRequest>
</soapenv:Body>
</soapenv:Envelope>
响应:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:StudentDetailsResponse xmlns:ns2="https://www.howtodoinjava.com/xml/school">
<ns2:Student>
<ns2:name>Sajal</ns2:name>
<ns2:standard>5</ns2:standard>
<ns2:address>Pune</ns2:address>
</ns2:Student>
</ns2:StudentDetailsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
SOAP UI 示例
二. 客户端
在运行此示例之前,我们需要准备好一个 SOAP 服务,该服务将从该客户端代码中调用。
运行此 SOAP 服务器项目后,将从http://localhost:8080/service/studentDetailsWsdl.wsdl
获取 WSDL。 将 WSDL 下载为studentDetailsWsdl.wsdl
,稍后将其放置在客户端项目的resources/wsdl
文件夹中,该文件夹将在下一步创建以生成客户端代理代码。
1. Spring Boot Soap 客户端的技术栈
- JDK 1.8,Eclipse,Maven -- 开发环境
- SpringBoot -- 基础应用程序框架
maven-jaxb2-plugin
插件 -- 用于生成 JAXB 存根- SpringBoot
CommandLineRunner
-- 测试客户端代码
2. 使用WebServiceTemplate
创建 Spring 客户端
2.1 创建启动项目
仅从具有Web Services
依赖关系的SPRING 初始化器站点创建一个 spring boot 项目。 选择依赖项并提供适当的 Maven GAV 坐标后,以压缩格式下载项目。 解压缩,然后将 eclipse 中的项目导入为 maven 项目。
Spring boot 项目生成
2.2 生成 SOAP 域类
现在使用maven-jaxb2-plugin
maven 插件生成 JAXB 注解的存根类 。 为此,将此 maven 插件添加到项目的pom.xml
中。
pom.xml
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.2</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<generatePackage>com.example.howtodoinjava.schemas.school</generatePackage>
<generateDirectory>${project.basedir}/src/main/java</generateDirectory>
<schemaDirectory>${project.basedir}/src/main/resources/wsdl</schemaDirectory>
<schemaIncludes>
<include>*.wsdl</include>
</schemaIncludes>
</configuration>
</plugin>
此插件将在项目的src
目录的com.example.howtodoinjava.springbootsoapclient
包中生成类,并且此插件将检查类的生成时间戳,以便仅在WSDL
中发生任何更改时才生成这些类。
2.3 使用WebServiceTemplate
创建 SOAP 客户端
创建一个名为SOAPConnector.java
的类,该类将充当对 Web 服务的所有请求的通用 Web 服务客户端。
SOAPConnector.java
package com.example.howtodoinjava.springbootsoapclient;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
public class SOAPConnector extends WebServiceGatewaySupport {
public Object callWebService(String url, Object request){
return getWebServiceTemplate().marshalSendAndReceive(url, request);
}
}
SOAPConnector
类是对WebServiceGatewaySupport
的扩展,它基本上是通过getWebServiceTemplate()
方法提供的WebServiceTemplate
内部实现注入一个接口。- 我们将使用此
WebServiceTemplate
来调用 SOAP 服务。 - 该类还期望注入一个名为
Marshaller
和Unmarshaller
的 spring bean,它们将由配置类提供,我们将在下面看到。
2.4 Spring bean 配置
现在,我们需要创建一个用@Configuration
注解的配置类,该类将具有SOAPConnector
所需的必需的 bean 定义,以使其正常工作。
Config.java
package com.example.howtodoinjava.springbootsoapclient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
@Configuration
public class Config {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this is the package name specified in the <generatePackage> specified in
// pom.xml
marshaller.setContextPath("com.example.howtodoinjava.schemas.school");
return marshaller;
}
@Bean
public SOAPConnector soapConnector(Jaxb2Marshaller marshaller) {
SOAPConnector client = new SOAPConnector();
client.setDefaultUri("http://localhost:8080/service/student-details");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
WebServiceGatewaySupport
需要Marshaller
和Unmarshaller
,它们是Jaxb2Marshaller
类的实例。- 它使用
com.example.howtodoinjava.schemas.school
作为 JAXB 类的基本包。 它将使用此包创建 JAXB 上下文。 - 我们将使用此
Jaxb2Marshaller
bean 作为SOAPConnector
bean 的Marshaller/Unmarshaller
。
2.5 使用CommandLineRunner
测试
为简单起见,我们将创建一个Spring Boot 命令行运行程序,该加载程序将加载 spring 上下文并调用处理器方法,并将命令行参数传递给该方法。 实时地,我们需要用一些其他代码替换此命令行运行程序,这些代码将更适合企业。
我们需要在SpringBootApplication
类中添加此命令行运行器 bean,如下。
SpringBootSoapClientApplication.java
package com.example.howtodoinjava.springbootsoapclient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.example.howtodoinjava.schemas.school.StudentDetailsRequest;
import com.example.howtodoinjava.schemas.school.StudentDetailsResponse;
@SpringBootApplication
public class SpringBootSoapClientApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSoapClientApplication.class, args);
}
@Bean
CommandLineRunner lookup(SOAPConnector soapConnector) {
return args -> {
String name = "Sajal";//Default Name
if(args.length>0){
name = args[0];
}
StudentDetailsRequest request = new StudentDetailsRequest();
request.setName(name);
StudentDetailsResponse response =(StudentDetailsResponse) soapConnector.callWebService("http://localhost:8080/service/student-details", request);
System.out.println("Got Response As below ========= : ");
System.out.println("Name : "+response.getStudent().getName());
System.out.println("Standard : "+response.getStudent().getStandard());
System.out.println("Address : "+response.getStudent().getAddress());
};
}
}
在这里,我们从命令行获取搜索参数,并创建StudentDetailsRequest
对象,并使用SOAPConnector
调用 SOAP Web 服务。
2.6 一些可选配置
打开application.properties
并添加以下配置
application.properties
server.port = 9090
logging.level.org.springframework.ws=TRACE
在这里,我们用server.port = 9090
将默认端口覆盖为9090
,因为您已经注意到我们的示例 SOAP 服务在默认端口8080
中运行,因为两个 Java 进程不能在同一端口中运行。
另外,我们正在通过logging.level.org.springframework.ws=TRACE
为org.springframework.ws
软件包启用TRACE
日志记录。 这将在控制台中打印 SOAP 负载。
这就是我们使用 Spring Boot 消费 SOAP 服务所需要做的一切,现在是时候进行测试了。
3. 示例
现在使用 maven 命令mvn clean install
来构建应用程序。 我们可以从命令提示符下通过命令java -jar target\spring-boot-soap-client-0.0.1-SNAPSHOT.jar Lokesh
调用命令行运行程序。
请注意,我们在此处传递了一个命令行参数Lokesh
,该参数将在CommandLineRunner
bean 的查找方法中使用。 如果没有传递任何名称,我们将在该方法中传递一个默认名称。
调用命令行运行程序后,我们应该看到 SOAP 服务输出,并且响应已正确解组到 JAXB 对象StudentDetailsResponse
。 同样,我们可以在 TRACE 日志中看到完整的 SOAP 请求/响应,如下所示。
3.1 输出
2017-10-09 23:20:45.548 TRACE 9204 --- [ main] o.s.ws.client.MessageTracing.received : Received response [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:StudentDetailsResponse xmlns:ns2="https://www.howtodoinjava.com/xml/school"><ns2:Student><ns2:name>Sajal</ns2:name><ns2:standard>5</ns2:standard><ns2:address>Pune</ns2:address></ns2:Student></ns2:StudentDetailsResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>] for request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:StudentDetailsRequest xmlns:ns2="https://www.howtodoinjava.com/xml/school"><ns2:name>Sajal</ns2:name></ns2:StudentDetailsRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>]
Got Response As below ========= :
Name : Lokesh
Standard : 6
Address : Delhi