Related posts : Simple Spring boot application
SpringWS use "contract first" approach for web services. That means: we have to start with "contract" : our web services have to be "explained" using XSD schemes for domain entities and operations. In this example we have only one entity class : Category.xsd and operation file CategoryOperations.xsd with 2 operations: save category and getCategoryById.
After that, based on XSD files we have to generate .java classes. Highlighted files on next picture were generated - we were not creating them manually :
Just regular java POJO class "transalted" to XSD language:
- messageDispatcherServlet - it will intercept our calls to url /ws/ and interpret them as web service calls
- Wsdl11Definition - it is responsible for showing WSDL - description of our web service. This WSDL file will be automatically generated based on CategoryOperations.xsd file. After all, it will be accessable by path ws/beanName.wsdl - so in our case : /ws/CategoryService.wsdl.
- Category schema - it will be accessable by path /ws/Category.xsd
- Category operation schema - it will be accessable by path /ws/CategoryOperations.xsd
Here everything is pretty simple - just running :
mvn clean compile
During compiling we will see in output list of generated files :
[INFO] com\demien\springws\domain\operation\CategoryGetByIdRequest.java
[INFO] com\demien\springws\domain\operation\CategoryGetByIdResponse.java
[INFO] com\demien\springws\domain\operation\CategorySaveRequest.java
[INFO] com\demien\springws\domain\operation\CategorySaveResponse.java
[INFO] com\demien\springws\domain\operation\ObjectFactory.java
[INFO] com\demien\springws\domain\operation\package-info.java
[INFO] com\demien\springws\domain\Category.java
[INFO] com\demien\springws\domain\ObjectFactory.java
[INFO] com\demien\springws\domain\package-info.java
Now we can run our application(main procedure in App.java class) and check our services.
First of all we can check our resources - by next 2 links we have to be able to open our schemas :
http://localhost:8080/ws/Category.xsd
http://localhost:8080/ws/CategoryOperations.xsd
Next - we can check generation of WSDL file(from CategoryOperations.xsd) by SpringWS:
http://localhost:8080/ws/CategoryService.wsdl
And if everything is OK - we can call web service from client, for example using SoapUI.
After import of WSDL link to SoapUI - it creates 2 operations : CategorySave and CategoryGetById.
Execution of CategorySave :
In request I filled only CategoryName field:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oper="http://com/demien/springws/domain/operation" xmlns:dom="http://com/demien/springws/domain">
<soapenv:Header/>
<soapenv:Body>
<oper:CategorySaveRequest>
<oper:category>
<dom:CategoryId>?</dom:CategoryId>
<dom:CategoryName>First Category</dom:CategoryName>
<dom:CategoryDescription>?</dom:CategoryDescription>
<dom:CategoryParentId>?</dom:CategoryParentId>
</oper:category>
</oper:CategorySaveRequest>
</soapenv:Body>
</soapenv:Envelope>
Returned response - we can see that ID was generated :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns3:CategorySaveResponse xmlns:ns2="http://com/demien/springws/domain" xmlns:ns3="http://com/demien/springws/domain/operation">
<ns3:category>
<ns2:CategoryId>1</ns2:CategoryId>
<ns2:CategoryName>First Category</ns2:CategoryName>
<ns2:CategoryDescription>?</ns2:CategoryDescription>
</ns3:category>
</ns3:CategorySaveResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Execution of CategoryGetById:
Request - I filled CategoryId :
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oper="http://com/demien/springws/domain/operation">
<soapenv:Header/>
<soapenv:Body>
<oper:CategoryGetByIdRequest>
<oper:categoryId>1</oper:categoryId>
</oper:CategoryGetByIdRequest>
</soapenv:Body>
</soapenv:Envelope>
Returned response - category which was created on previous step :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns3:CategoryGetByIdResponse xmlns:ns2="http://com/demien/springws/domain" xmlns:ns3="http://com/demien/springws/domain/operation">
<ns3:category>
<ns2:CategoryId>1</ns2:CategoryId>
<ns2:CategoryName>First Category</ns2:CategoryName>
<ns2:CategoryDescription>?</ns2:CategoryDescription>
</ns3:category>
</ns3:CategoryGetByIdResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
1. Intro
Usually, to make a web service we had to configure web server (like oracle weblogic, glassfish) and deploy there our web service. With SpringWS - everything is much more simple : our SpringWS-based web service can be deployed just to Tomcat - we don't need web servers. But with using of SpringBoot for running SpringWS-based web service - we even don't need a Tomcat : we can just run our application using main(String[] args) function and that's it!
2. Project structure
SpringWS use "contract first" approach for web services. That means: we have to start with "contract" : our web services have to be "explained" using XSD schemes for domain entities and operations. In this example we have only one entity class : Category.xsd and operation file CategoryOperations.xsd with 2 operations: save category and getCategoryById.After that, based on XSD files we have to generate .java classes. Highlighted files on next picture were generated - we were not creating them manually :
3. pom.xml
Most interesting thing here located in the bottom of file - it's a plugin for generating java src files based on our XSD schemes.<?xml version="1.0"?><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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>com.demien</artifactId> <groupId>springws</groupId> <version>1.0</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.7.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-ws</artifactId> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.ws.xmlschema</groupId> <artifactId>xmlschema-core</artifactId> <version>2.0.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jaxb2-maven-plugin</artifactId> <version>1.4</version> <executions> <execution> <goals> <goal>xjc</goal> </goals> <phase>generate-sources</phase> </execution> </executions> <configuration> <clearOutputDir>false</clearOutputDir> <outputDirectory>src/main/java</outputDirectory> <schemaDirectory>src/main/resources</schemaDirectory> <enableIntrospection>false</enableIntrospection> </configuration> </plugin> </plugins> </build> </project>
4. Entity schema (Category.xsd)
Just regular java POJO class "transalted" to XSD language:<?xml version="1.0" encoding="UTF-8"?><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://com/demien/springws/domain" targetNamespace="http://com/demien/springws/domain" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="Category" type="Category"/> <xs:complexType name="Category"> <xs:sequence> <xs:element name="CategoryId" type="xs:integer"/> <xs:element name="CategoryName" type="xs:string"/> <xs:element name="CategoryDescription" type="xs:string"/> <xs:element name="CategoryParentId" type="xs:integer"/> </xs:sequence> </xs:complexType> </xs:schema>
5. Operations schema (CategoryOperations.xsd)
Here we have 2 operations : save and getbyId. For both of them we have to define data types for request and response. So, in total we have 4 types :<?xml version="1.0" encoding="UTF-8"?><xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://com/demien/springws/domain/operation" xmlns:category="http://com/demien/springws/domain" targetNamespace="http://com/demien/springws/domain/operation" elementFormDefault="qualified"> <xsd:import namespace="http://com/demien/springws/domain" schemaLocation="Category.xsd"/> <xsd:element name="CategorySaveRequest"> <xsd:complexType> <xsd:sequence> <xsd:element name="category" type="category:Category"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="CategorySaveResponse"> <xsd:complexType> <xsd:sequence> <xsd:element name="category" type="category:Category"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="CategoryGetByIdRequest"> <xsd:complexType> <xsd:sequence> <xsd:element name="categoryId" type="xsd:integer"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="CategoryGetByIdResponse"> <xsd:complexType> <xsd:sequence> <xsd:element name="category" type="category:Category"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
6. Web service configuration(WSConfig.java)
Here we have 4 beans :- messageDispatcherServlet - it will intercept our calls to url /ws/ and interpret them as web service calls
- Wsdl11Definition - it is responsible for showing WSDL - description of our web service. This WSDL file will be automatically generated based on CategoryOperations.xsd file. After all, it will be accessable by path ws/beanName.wsdl - so in our case : /ws/CategoryService.wsdl.
- Category schema - it will be accessable by path /ws/Category.xsd
- Category operation schema - it will be accessable by path /ws/CategoryOperations.xsd
package com.demien.springws; import org.springframework.boot.context.embedded.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.transport.http.MessageDispatcherServlet; import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition; import org.springframework.xml.xsd.SimpleXsdSchema; import org.springframework.xml.xsd.XsdSchema; @Configuration@EnableWspublic class WSConfig { @Bean public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) { MessageDispatcherServlet servlet = new MessageDispatcherServlet(); servlet.setApplicationContext(applicationContext); servlet.setTransformWsdlLocations(true); return new ServletRegistrationBean(servlet, "/ws/*"); } @Bean(name = "CategoryService") public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema CategoryOperations) { DefaultWsdl11Definition result = new DefaultWsdl11Definition(); result.setPortTypeName("CategoryWS"); result.setLocationUri("/ws"); result.setTargetNamespace("http://com/demien/springws/domain/operation"); result.setSchema(CategoryOperations); return result; } @Bean public XsdSchema Category() { return new SimpleXsdSchema(new ClassPathResource("Category.xsd")); } @Bean public XsdSchema CategoryOperations() { return new SimpleXsdSchema(new ClassPathResource("CategoryOperations.xsd")); } }
7. App main file(App.java)
Here everything is pretty simple - just running :package com.demien.springws; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplicationpublic class App { public static void main(String[] args) { SpringApplication.run(new Class<?>[]{App.class}, args); } }
8. Service(business logic) class(CategoryService.java)
Here I made "emulation" of service implementation : my services is storing data in HashMap instead of saving in DB and generate ID by increment of static variable :package com.demien.springws.service; import com.demien.springws.domain.Category; import org.springframework.stereotype.Service; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; @Servicepublic class CategoryService { private Map<BigInteger,Category> storage=new HashMap<BigInteger,Category>(); private int id=1; public Category save(Category category) { category.setCategoryId(BigInteger.valueOf(id)); id++; storage.put(category.getCategoryId(), category); return category; } public Category getById(BigInteger categoryId) { Category result=storage.get(categoryId); return result; }}
9. Build app - for generating sources from schema files
Now development part is almost over and we need to generate classes from XDS schemas. Everything is defined in our pom.xml, so, all we need is to run :mvn clean compile
During compiling we will see in output list of generated files :
[INFO] com\demien\springws\domain\operation\CategoryGetByIdRequest.java
[INFO] com\demien\springws\domain\operation\CategoryGetByIdResponse.java
[INFO] com\demien\springws\domain\operation\CategorySaveRequest.java
[INFO] com\demien\springws\domain\operation\CategorySaveResponse.java
[INFO] com\demien\springws\domain\operation\ObjectFactory.java
[INFO] com\demien\springws\domain\operation\package-info.java
[INFO] com\demien\springws\domain\Category.java
[INFO] com\demien\springws\domain\ObjectFactory.java
[INFO] com\demien\springws\domain\package-info.java
10. Web service endpoint(CategoryServiceEndpoint.java)
All files were generated, now one step more to go: create a web service endpoint - "wrapper" for CategoryService with needed for endpoint annotations :package com.demien.springws.service; import com.demien.springws.domain.Category; import com.demien.springws.domain.operation.CategoryGetByIdRequest; import com.demien.springws.domain.operation.CategoryGetByIdResponse; import com.demien.springws.domain.operation.CategorySaveRequest; import com.demien.springws.domain.operation.CategorySaveResponse; 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; @Endpointpublic class CategoryServiceEndpoint { private static final String TARGET_NAMESPACE = "http://com/demien/springws/domain/operation"; @Autowired CategoryService categoryService; public CategoryServiceEndpoint() { } @PayloadRoot(localPart = "CategorySaveRequest", namespace = TARGET_NAMESPACE) public @ResponsePayload CategorySaveResponse save(@RequestPayload CategorySaveRequest request) { CategorySaveResponse response = new CategorySaveResponse(); Category category=categoryService.save(request.getCategory()); response.setCategory(category); return response; } @PayloadRoot(localPart = "CategoryGetByIdRequest", namespace = TARGET_NAMESPACE) public @ResponsePayload CategoryGetByIdResponse save(@RequestPayload CategoryGetByIdRequest request) { CategoryGetByIdResponse response = new CategoryGetByIdResponse(); Category category=categoryService.getById(request.getCategoryId()); response.setCategory(category); return response; } }
11. Running and testing of our web service using SoapUI
Now we can run our application(main procedure in App.java class) and check our services.First of all we can check our resources - by next 2 links we have to be able to open our schemas :
http://localhost:8080/ws/Category.xsd
http://localhost:8080/ws/CategoryOperations.xsd
Next - we can check generation of WSDL file(from CategoryOperations.xsd) by SpringWS:
http://localhost:8080/ws/CategoryService.wsdl
And if everything is OK - we can call web service from client, for example using SoapUI.
After import of WSDL link to SoapUI - it creates 2 operations : CategorySave and CategoryGetById.
Execution of CategorySave :
In request I filled only CategoryName field:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oper="http://com/demien/springws/domain/operation" xmlns:dom="http://com/demien/springws/domain">
<soapenv:Header/>
<soapenv:Body>
<oper:CategorySaveRequest>
<oper:category>
<dom:CategoryId>?</dom:CategoryId>
<dom:CategoryName>First Category</dom:CategoryName>
<dom:CategoryDescription>?</dom:CategoryDescription>
<dom:CategoryParentId>?</dom:CategoryParentId>
</oper:category>
</oper:CategorySaveRequest>
</soapenv:Body>
</soapenv:Envelope>
Returned response - we can see that ID was generated :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns3:CategorySaveResponse xmlns:ns2="http://com/demien/springws/domain" xmlns:ns3="http://com/demien/springws/domain/operation">
<ns3:category>
<ns2:CategoryId>1</ns2:CategoryId>
<ns2:CategoryName>First Category</ns2:CategoryName>
<ns2:CategoryDescription>?</ns2:CategoryDescription>
</ns3:category>
</ns3:CategorySaveResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Execution of CategoryGetById:
Request - I filled CategoryId :
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oper="http://com/demien/springws/domain/operation">
<soapenv:Header/>
<soapenv:Body>
<oper:CategoryGetByIdRequest>
<oper:categoryId>1</oper:categoryId>
</oper:CategoryGetByIdRequest>
</soapenv:Body>
</soapenv:Envelope>
Returned response - category which was created on previous step :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns3:CategoryGetByIdResponse xmlns:ns2="http://com/demien/springws/domain" xmlns:ns3="http://com/demien/springws/domain/operation">
<ns3:category>
<ns2:CategoryId>1</ns2:CategoryId>
<ns2:CategoryName>First Category</ns2:CategoryName>
<ns2:CategoryDescription>?</ns2:CategoryDescription>
</ns3:category>
</ns3:CategoryGetByIdResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>