本指南将引导您完成使用Spring创建基于SOAP的Web服务的过程,这个服务将公开公开来自不同欧洲国家的数据,供你查询。
为了简化示例,您将通过硬编码编写英国、西班牙和波兰的数据。
我利用业余时间,翻译了Spring官网的例子,方便中文不好的同学,将陆续发到头条上,欢迎大家关注,也可以上我个人BLOG:itmanclub.com,上面有已经翻译过的。
程序结构
└── src └── main └── java └── hello
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework</groupId> <artifactId>gs-producting-web-service</artifactId> <version>0.1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <properties> <java.version>1.8</java.version> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Spring Boot将会你做如下的事:
- 将classpath里面所有用到的jar包构建成一个可执行的 JAR 文件,方便执行你的程序
- 搜索public static void main()方法并且将它当作可执行类
- 根据springboot版本,去查找相应的依赖类版本,当然你可以定义其它版本。
增加Spring-WS依赖
您创建的项目需要将spring-ws-core作为依赖项包含在构建文件和wsdl4j中。
对于maven:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web-services</artifactId> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> </dependency>
对于gradle:
sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile("org.springframework.boot:spring-boot-starter-web-services") testCompile("org.springframework.boot:spring-boot-starter-test") compile("wsdl4j:wsdl4j:1.6.1") jaxb("org.glassfish.jaxb:jaxb-xjc:2.2.11") compile(files(genJaxb.classesDir).builtBy(genJaxb)) }
创建XML schema以定义域
Web服务域是在XML schema文件(XSD)中定义的,Spring-WS将自动导出为WSDL。
创建一个带有操作的XSD文件,以返回一个国家的名称、人口、首都和货币:
src/main/resources/countries.xsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://spring.io/guides/gs-producing-web-service" targetNamespace="http://spring.io/guides/gs-producing-web-service" elementFormDefault="qualified"> <xs:element name="getCountryRequest"> <xs:complexType> <xs:sequence> <xs:element name="name" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="getCountryResponse"> <xs:complexType> <xs:sequence> <xs:element name="country" type="tns:country"/> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="country"> <xs:sequence> <xs:element name="name" type="xs:string"/> <xs:element name="population" type="xs:int"/> <xs:element name="capital" type="xs:string"/> <xs:element name="currency" type="tns:currency"/> </xs:sequence> </xs:complexType> <xs:simpleType name="currency"> <xs:restriction base="xs:string"> <xs:enumeration value="GBP"/> <xs:enumeration value="EUR"/> <xs:enumeration value="PLN"/> </xs:restriction> </xs:simpleType> </xs:schema>
基于XML schema生成域类
下一步是从XSD文件生成Java类。正确的方法是在构建期间使用Maven或Gradle插件自动执行此操作。
Maven的插件配置:
<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>
生成的类放在target/generated-sources/jaxb/目录中。
Gradle的插件配置
要对Gradle执行相同的操作,首先需要在构建文件中配置JAXB:
configurations { jaxb } bootJar { baseName = 'gs-producing-web-service' version = '0.1.0' from genJaxb.classesDir } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile("org.springframework.boot:spring-boot-starter-web-services") testCompile("org.springframework.boot:spring-boot-starter-test") compile("wsdl4j:wsdl4j:1.6.1") jaxb("org.glassfish.jaxb:jaxb-xjc:2.2.11") compile(files(genJaxb.classesDir).builtBy(genJaxb)) }
上面的构建文件有标记和结束注释,这是为了方便你了解。您自己的构建文件中不需要这些注释。
下一步是添加由gradle使用的任务genJaxb生成Java类:
task genJaxb { ext.sourcesDir = "${buildDir}/generated-sources/jaxb" ext.classesDir = "${buildDir}/classes/jaxb" ext.schema = "src/main/resources/countries.xsd" outputs.dir classesDir doLast() { project.ant { taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask", classpath: configurations.jaxb.asPath mkdir(dir: sourcesDir) mkdir(dir: classesDir) xjc(destdir: sourcesDir, schema: schema) { arg(value: "-wsdl") produces(dir: sourcesDir, includes: "**/*.java") } javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true, debugLevel: "lines,vars,source", classpath: configurations.jaxb.asPath) { src(path: sourcesDir) include(name: "**/*.java") include(name: "*.java") } copy(todir: classesDir) { fileset(dir: sourcesDir, erroronmissingdir: false) { exclude(name: "**/*.java") } } } } }
因为Gradle还没有JAXB插件,所以它涉及到一个Ant任务,这使得它比Maven要复杂一些。在这两种情况下,JAXB域对象生成过程都已连接到构建工具的生命周期中,因此没有额外的步骤可以运行。
创建国家/地区存储库
为了向Web服务提供数据,请创建一个国家/地区存储库。在本指南中,您将使用硬编码数据创建一个虚拟的国家/地区存储库实现。
package hello; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; import io.spring.guides.gs_producing_web_service.Country; import io.spring.guides.gs_producing_web_service.Currency; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @Component public class CountryRepository { private static final Map<String, Country> countries = new HashMap<>(); @PostConstruct public void initData() { Country spain = new Country(); spain.setName("Spain"); spain.setCapital("Madrid"); spain.setCurrency(Currency.EUR); spain.setPopulation(46704314); countries.put(spain.getName(), spain); Country poland = new Country(); poland.setName("Poland"); poland.setCapital("Warsaw"); poland.setCurrency(Currency.PLN); poland.setPopulation(38186860); countries.put(poland.getName(), poland); Country uk = new Country(); uk.setName("United Kingdom"); uk.setCapital("London"); uk.setCurrency(Currency.GBP); uk.setPopulation(63705000); countries.put(uk.getName(), uk); } public Country findCountry(String name) { Assert.notNull(name, "The country's name must not be null"); return countries.get(name); } }
创建国家/地区服务点
要创建一个服务端点,您只需要一个带有一些Spring WS 注释的POJO来处理传入的SOAP请求。
package hello; 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 io.spring.guides.gs_producing_web_service.GetCountryRequest; import io.spring.guides.gs_producing_web_service.GetCountryResponse; @Endpoint public class CountryEndpoint { private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service"; private CountryRepository countryRepository; @Autowired public CountryEndpoint(CountryRepository countryRepository) { this.countryRepository = countryRepository; } @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest") @ResponsePayload public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) { GetCountryResponse response = new GetCountryResponse(); response.setCountry(countryRepository.findCountry(request.getName())); return response; } }
@Endpoint使用Spring WS将类注册为处理传入SOAP消息的潜在候选对象。
然后,Spring WS使用@PayloadRoot 根据消息的命名空间和本地部分选择处理程序方法。
@RequestPayload指示传入消息将映射到方法的请求参数。
@ResponsePayload注释使SpringWS将返回的值映射到响应负载。
在所有这些代码块中,io.spring.guides类将在您的IDE中显示编译时错误,除非您已经运行了基于WSDL生成域类的任务。
配置一个Web服务bean
使用Spring WS Related Beans配置创建一个新类:
package hello; 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 WebServiceConfig extends WsConfigurerAdapter { @Bean public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) { MessageDispatcherServlet servlet = new MessageDispatcherServlet(); servlet.setApplicationContext(applicationContext); servlet.setTransformWsdlLocations(true); return new ServletRegistrationBean(servlet, "/ws/*"); } @Bean(name = "countries") public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) { DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition(); wsdl11Definition.setPortTypeName("CountriesPort"); wsdl11Definition.setLocationUri("/ws"); wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service"); wsdl11Definition.setSchema(countriesSchema); return wsdl11Definition; } @Bean public XsdSchema countriesSchema() { return new SimpleXsdSchema(new ClassPathResource("countries.xsd")); } }
Spring WS使用不同的servlet类型来处理SOAP消息:MessageDispatcherServlet。必须将MessageDispatcherServlet注入并设置到MessageDispatcherServlet。否则,Spring WS将无法自动检测到Spring Beans。
通过命名这个 messageDispatcherServlet bean,它不会替换Spring boot的默认DispatcherServlet bean。
DispatcherServlet配置注释驱动的SpringWS编程模型。这使得使用前面提到的@Endpoint 等各种注释成为可能。
DefaultWsdl11Definition 定义使用XsdSchema公开标准wsdl1.1
需要注意的是,您需要为MessageDispatcherServlet和DefaultWsdl11Definition指定bean名称。bean名称确定Web服务和生成的WSDL文件可用的URL。在这种情况下,WSDL将在http://<host>:<port>/ws/countries.wsdl下可用。
此配置还使用WSDL位置servlet转换servlet.setTransformWsdlLocations(true)。如果您访问http://localhost:8080/ws/countries.wsdl,soap:address将具有正确的地址。如果您改为从分配给您的机器的面向公共的IP地址访问WSDL,您将看到该地址。
创建Application
src/main/java/hello/Application.java
package hello; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
运行和测试程序
运行Application.java,然后创建SOAP请求文件request.xml:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:gs="http://spring.io/guides/gs-producing-web-service"> <soapenv:Header/> <soapenv:Body> <gs:getCountryRequest> <gs:name>Spain</gs:name> </gs:getCountryRequest> </soapenv:Body> </soapenv:Envelope>
在测试SOAP接口时有几个选项。如果您使用的是*nix/mac系统,那么您可以使用SoapUI 之类的工具,或者只使用命令行工具,如下所示:
$ curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws
因此,您应该看到此响应:
<?xml version="1.0"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service"> <ns2:country> <ns2:name>Spain</ns2:name> <ns2:population>46704314</ns2:population> <ns2:capital>Madrid</ns2:capital> <ns2:currency>EUR</ns2:currency> </ns2:country> </ns2:getCountryResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
很可能输出是一个紧凑的XML文档,而不是上面显示的格式良好的文档。如果您的系统上安装了xmllib2,那么您可以将curl -s <args above> | xmllint --format看格式较好的结果。