あまり活躍する機会はないと思いますが、Spring Boot で Spring Web Serviceを使ってサーバを構築したときにハマったことと、解決方法のメモを残します。
バージョン
- Spring Boot
1.4.1.RELEASE
- Spring Web Service
2.4.0.RELEASE
- Java
1.8
サンプルコード
大半は、Spring Web ServiceのGetting Started Guide を参考にしています。
package sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Logger logger(InjectionPoint point) {
return LoggerFactory.getLogger(point.getMember().getDeclaringClass());
}
}
package sample;
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 = "Hello")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schemas) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("HelloPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://ijufumi.jp/sample");
wsdl11Definition.setSchema(schemas);
wsdl11Definition.setCreateSoap11Binding(true);
wsdl11Definition.setCreateSoap12Binding(true);
return wsdl11Definition;
}
@Bean
public XsdSchema schemas() {
return new SimpleXsdSchema(new ClassPathResource("Hello.xsd"));
}
}
package sample;
import org.slf4j.Logger;
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 javax.annotation.PostConstruct;
@Endpoint
public class Endpoint {
@Autowired
Logger logger;
@PostConstruct
public void postConstruct() {
logger.info("Endpoint#postConstruct() called.");
}
@PayloadRoot(namespace = "http://ijufumi.jp/hello", localPart = "HelloRequest")
@ResponsePayload
public HelloResponseType hello(@RequestPayload HelloRequestType request) {
HelloResponseType response = new HelloResponseType();
response.setMessage("Hello World, " + request.getName());
return response;
}
}
package sample;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "HelloRequestType", namespace = "http://ijufumi.jp/hello", propOrder = {
"name"
})
public class HelloRequestType {
@XmlElement(name = "Name")
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package sample;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "HelloResponseType", namespace = "http://ijufumi.jp/hello", propOrder = {
"message"
})
public class HelloRequestType {
@XmlElement(name = "Message")
protected String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://ijufumi.jp/hello"
targetNamespace="http://ijufumi.jp/hello" elementFormDefault="qualified">
<xs:element name="HelloRequest" type="tns:HelloRequestType" />
<xs:element name="HelloResponse" type="tns:HelloResponseType" />
<xs:complexType name="HelloRequestType">
<xs:sequence>
<xs:element name="Name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="HelloResponseType">
<xs:sequence>
<xs:element name="Message" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:h="http://ijufumi.jp/hello">
<soapenv:Header/>
<soapenv:Body>
<h:HelloRequest>
<Name>Iju</Name>
</h:HelloRequest>
</soapenv:Body>
</soapenv:Envelope>
ハマったポイント
「No adapter for endpoint」
事象
SOAPのXMLをPOSTすると、
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">No adapter for endpoint [public sample.HelloResponseType sample.Endpoint.hello(sample.HelloRequestType) ]: Is your endpoint annotated with @Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
というのが返ってきました。
原因
StackOverFlowでも回答されている通り、JAXBで作成したPOJOに@XmlRootElement
がないのが原因みたいです。
対応内容
リクエスト、レスポンスの両方のPOJOに@XmlRootElement
を追加しました。
package sample;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "HelloRequestType", namespace = "http://ijufumi.jp/hello", propOrder = {
"name"
})
public class HelloRequestType {
@XmlElement(name = "Name")
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package sample;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "HelloResponseType", namespace = "http://ijufumi.jp/hello", propOrder = {
"message"
})
public class HelloRequestType {
@XmlElement(name = "Message")
protected String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
「unexpected element」
事象
@XmlRootElement
を追加後に再度POSTしたら、
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">unexpected element (uri:"http://ijufumi.jp/hello", local:"HelloRequest"). Expected elements are <・・・</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
というのが返ってきました。
原因
Endpointのメソッドの引数に、JAXBElement
を使う必要があるようです。
ただ、この対応内容を見つけたソースを見失ってしまったので、見つけ次第更新します。
対応内容
Endpointを修正。
package sample;
import org.slf4j.Logger;
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 javax.annotation.PostConstruct;
import javax.xml.bind.JAXBElement;
@Endpoint
public class Endpoint {
@Autowired
Logger logger;
@PostConstruct
public void postConstruct() {
logger.info("Endpoint#postConstruct() called.");
}
@PayloadRoot(namespace = "http://ijufumi.jp/hello", localPart = "HelloRequest")
@ResponsePayload
public HelloResponseType hello(@RequestPayload JAXBElement<HelloRequestType> request) {
HelloResponseType response = new HelloResponseType();
response.setMessage("Hello World, " + request.getName());
return response;
}
}
最後に
ハマったときに日本語の情報がなかったのでとりあえずメモを残しました。
ただ、行き当たりばったりで調べたものなので多少なり間違っている記述があると思うので、
適宜指摘いただけると助かります。