原文:Producing a SOAP web service

译者:feilangrenM

校对:zaixiandemiao

本指南将帮助你了解使用Spring构建SOAP Web Service 服务端程序的基本流程。

你将创建什么

你将创建一个基于WSDL的SOAP web service服务器,用它发布不同欧洲国家的数据信息。

为了简化示例,接下来将使用英国、西班牙和波兰三个国家的硬编码数据。

你需要准备什么

怎样使用本指南

与大多数Spring入门指南一样,你可以从头开始,完成每一步,也可以绕过已经熟悉的基本设置步骤。不管采用哪种方式,最终都可以写出能够运行的代码。

如果是从零开始,则可以从使用Gradle构建项目小节开始。

如果想跳过基本的设置步骤,可以按照以下步骤执行:

  • 下载 并解压本指南相关的源文件,或者直接通过Git命令克隆到本地:
git clone https://github.com/spring-guides/gs-soap-service.git

当完成所有的编码以后,可以将你的代码与gs-soap-service/complete目录下的示例代码进行对比,以检查结果是否正确。

使用Gradle构建项目

首先需要设置一个基本的构建脚本。在使用Spring构建应用程序时,你可以使用任何自己喜欢的构建系统,这里准备了在使用GradleMaven构建项目时需要的代码。如果你对Gradle和Maven都不熟悉,可以参照使用Gradle构建Java项目使用Maven构建Java项目

创建目录结构

选择需要创建项目的目录,参照照以下目录结构创建子目录;如果使用的是UNIX系统,可以使用mkdir -p src/main/java/hello命令创建:

└── src
    └── main
        └── java
            └── hello

创建Gradle 的构建文件

下面是一个原始的Gradle build文件

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.7.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'

jar {
    baseName = 'gs-producing-web-service'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
}

Spring Boot gradle 插件 提供了很多便捷的特性:

  • 该插件可以把类路径下所有的jar打包成一个可以运行的“über-jar”,给程序的执行和传输带来很大的方便。
  • 该插件会自动搜索程序中的public static void main() 方法,把它作为程序运行的入口。
  • 它还提供了一个内置的依赖解析器,可以自动调整版本号与 Spring Boot 的依赖相一致。你可以覆盖其中的任何一个版本,但是默认情况下它会使用Spring Boot自身版本集中的版本。

使用Maven构建项目

首先,设置基本的构建脚本。在使用Spring构建应用程序时,你可以使用任何自己喜欢的构建系统,在这里为你提供了使用Maven构建项目时需要的代码。如果你对Maven不熟悉,可以参照使用maven构建JAVA项目工程

创建目录结构

选择需要创建项目的目录,参照以下目录结构创建子目录;如果使用的是UNIX系统,可以使用mkdir -p src/main/java/hello 命令创建:

└── 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 http://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>1.5.7.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 maven 插件 提供了很多便捷的特性:

  • 该插件可以把类路径下所有的jar打包成一个可以运行的“über-jar”,给程序的执行和传输带来很大的方便。
  • 该插件会自动搜索程序中的public static void main() 方法,作为程序运行的入口。
  • 它还提供了一个内置的依赖解析器,可以自动调整版本号与 Spring Boot 的依赖相一致。你可以覆盖其中的任何一个版本,但是默认情况下它会使用Spring Boot自身版本集中的版本。

使用IDE构建项目

添加 Spring-WS 依赖

在创建项目时需要在构建文件中添加spring-ws-corewsdl4j 的相关依赖。

对于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 来定义domain

Web service 的domain定义在一个XML schema 文件(XSD)中,Spring-WS 会基于此文件自动生成一个WSDL文件。

下面来创建XSD文件,在文件中定义国家的名字(name)、人口(population)、首都(capital)和货币(currency)

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 生成java类文件

下一步是基于上述定义的XSD文件来生成JAVA类文件。正确的做法是使用maven或者gradle插件,在构建项目的时候同时自动生成JAVA类文件。

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实现相同的效果需要在build文件中配置JAXB:

configurations {
    jaxb
}

jar {
    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))
}

上面的构建文件中如果有tagend注释,在你自己的构建文件中是不需要添加这些注释的。 这里引入这些注释只是为了解释得更详细。

下一步,添加Gradle生成java类文件时需要的task genJaxb:

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的task,这使得它比在maven中配置要复杂一些。

在上述两种情况下,通过JAXB生成java类文件的过程已经与构建工具的运行周期同步,因此不需要再进行其他的操作就可以自动生成相关的Java文件。

创建国家的信息库

在项目构建过程中,需要一个国家信息库向web service服务提供各个国家的数据。 在本指南中,将通过硬编码的方式创建一个虚拟的国家信息库。

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消息。

@PayloadRoot :Spring WS 根据消息的namespacelocalPart选择需要调用的方法。

@RequestPayload : 表示将传入的消息映射到方法的request参数上。

@ResponsePayload :通知Spring WS 将返回的值映射到reponse的有效负载。

代码块中的io.spring.guides类可以报告IDE中的编译时错误,除非正在访问WSDL文件生成Java类。

配置 web service beans

使用Spring WS的Bean配置创建一个新的Java类:

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"));
    }
}

需要强调的是,MessageDispatcherServletDefaultWsdl11Definition必须指定bean名称。 这两个bean的名称决定了访问WSDL文件的URL。比如,上述代码生成的WSDL文件位于

http://<host>:<port> /ws/countries.wsdl

此外,上述配置中还开启了WSDL地址转换:servlet.setTransformWsdlLocations(true)。 如果通过

http:// localhost:8080 / ws /countries.wsdl访问WSDL文件,则WSDL文件中的soap:address标签显示的是正确的地址。 如果你的计算机IP地址是面向公共的IP地址,则访问WSDL文件时,soap:address 标签显示的是你计算机的地址,并不是WSDL文件的真实地址。

使应用程序可执行

虽然可以将此服务打包为传统WAR文件以部署到外部应用程序服务器,但下面将演示一种创建了独立应用程序的简单方法:将所有内容都打包在一个可执行的JAR文件中,通过传统的Java main()方法驱动。采用这种方式,可以使用Spring的支持将内嵌的Tomcat servlet容器作为HTTP的运行环境,而不是在Tomcat servlet容器中部署一个外部实例。

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);
    }
}

@SpringBootApplication 是一个方便的注解,它会自动添加以下所有内容:

  • @Configuration将该类标记为应用程序上下文中bean定义的源。
  • @EnableAutoConfiguration指示Spring Boot根据类路径设置,其他bean和各种属性设置开始添加bean。
  • 通常,Spring MVC 应用程序会添加@EnableWebMvc注解,但是当Spring Boot在类路径中发现spring-webmvc时会自动添加该注解。通过添加该注解将应用程序标记为Web应用程序,并进行一些关键操作,比如设置DispatcherServlet
  • @ComponentScan通知Spring在hello包中查找其他组件,配置和服务,允许Spring扫描到控制器。

main()方法使用Spring Boot的SpringApplication.run()方法启动应用程序。 你注意到我们没有写过一行XML代码吗? 而且也没有web.xml配置文件。 此Web应用程序是100%纯Java编写的,无需再配置其他基础设施。

构建可执行的JAR

程序创建好以后,可以使用Gradle或Maven从命令行运行。或者,也可以将所有必需的依赖项,类和资源打包成一个可执行的JAR文件,并运行该文件。这种方式使得在整个开发生命周期中,应用程序可以轻松地发布,更新版本和部署服务。

如果你使用的是Gradle,则可以使用./gradlew bootRun运行应用程序。或者使用./gradlew build来构建JAR文件。然后运行:

java -jar build/libs/gs-soap-service-0.1.0.jar

如果你使用的是Maven,可以使用./mvnw spring-boot:run运行应用程序,或者使用./mvnw clean package来构建JAR文件。然后运行JAR文件:

java -jar target/gs-soap-service-0.1.0.jar

上面的过程创建了一个可运行的JAR。也可以选择构建一个经典的WAR文件

上述操作完成后,将会看到有日志信息输出,服务程序将会在几秒内启动并运行。

测试应用程序

现在应用程序正在运行,可以进行测试。创建一个包含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接口时,可以使用SoapUI,或者如果是在Unix / Mac系统上,可以使用命令行工具,如下所示:

$ 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 – 生成好看的格式。

小结

恭喜!你已经使用Spring Web Services创建了一个基于SOAP的web service 服务。

本文由spring4all.com翻译小分队创作,采用知识共享-署名-非商业性使用-相同方式共享 4.0 国际 许可 协议进行许可。

评论 抢沙发

请登录后发表评论

    暂无评论内容