최근에 내가 개발했던 웹서비스(클라이언트던 서버던 간에) RESTful 웹서비스였다.
메세지의 포맷도 JSON으로 사실상의 표준(De facto)으로 자리잡은 듯하다고 느꼈다.
얼마전부터 멤버십 서비스 연동을 할 일이 있어서 타 사와의 업무를 진행하게 되었다.
하지만 연동해야 하는 방식이 이제는 더 이상 안쓸 것 같다고 생각했던, 소켓(socket) 통신을 실 환경에서도 사용되고 있었다.
좀 더 추상화된 레벨을 요구했더니 SOAN 기반의 웹서비스를 제공해주었다.
SOAP과 RESTful 웹서비스가 익숙하지 않은 사람은 ETRI(한국전자통신연구원)에서 2010년에 출간한 비교 자료를 참고하면 좋다.
SOAP with Spring Framework
일반적인 스프링 서적중에 SOAP 웹서비스를 다루고 있는 책은 스프링 5 레시피(개정4판)정도였다. 주변에 있는 스프링 책중에서는 말이다.
2권의 13장(스프링 자바 엔터프라이즈 서비스와 원격 기술)
였는데, 이 책도 오래된 내용을 다루고 있어(예를 들자면 XML 마샬러중에 Castor와 XMLBeans는 스프링 4.3.13, 4.2부터 deprecated되었는데 스프링5라는 책이 아직도 소개한다.)
따라서 책보다는 최신을 따라가려면 웹 리소스가 더 나아보였다.
스프링의 프로젝트중에 Spring Web Services라는 프로젝트가 있다.(줄여서 ws) 아직 프로젝트가 없어지지 않은 걸(not retire)보니 웹서비스의 통합은 중요하게 인식되는 것 같다.
스프링에서 SOAP 클라이언트
Learn 탭에 보면 레퍼런스 문서가 있는데, 스프링에서 SOAP 클라이언트를 구현하기 위해 가장 적합하다.
스프링에서는 클라이언트 쪽 API를 위해 스프링 템플릿 패턴과 유사한 방식을 제공한다.
WebServiceTemplate
https://docs.spring.io/spring-ws/docs/3.0.7.RELEASE/reference/#client
위의 링크에도 제일먼저 나오지만 SOAP 클라이언트로 가장 중요한 클래스는 WebServiceTemplate 이다.
이 클래스를 직접 주입받아 사용할 수도 있지만 나는 WebServiceGatewaySupport 라는 추상 클래스를 상속받아 구현하는 쪽으로 하였다.
상속을 받고 나면 getWebServiceTemplate() 메서드를 통해 WebServiceTemplate 를 얻을 수 있다.
다음 섹션에서 설명할 샘플을 참고하면 이해하기 편하다.
Spring Guide Sample
스프링에서는 예를 통해서 어떻게 사용하는지 보여준다.
Producing a SOAP web service 와 Consuming a SOAP web service 가 바로 그것이다.
앞의 예제가 서비스 하는 쪽 코드이고, 뒤의 예제가 소비, 즉 클라이언트의 코드이다. 후자를 돌리기 위해서는 서버도 같이 띄워야 한다.
- server: https://github.com/spring-guides/gs-producing-web-service
- client: https://github.com/spring-guides/gs-consuming-web-service
마샬링/언마샬링
SOAP은 메세지를 XML을 통해서 구성한다.
따라서 serialize 및 deserialize를 해야 하는데 이를 담당하는 클래스를 Marshaller와 Unmarshaller라고 부른다.
이에 대한 내용은 ws 프로젝트가 아닌 core쪽에서 OXM이라는 부분에서 소개를 한다.
이에 대해서는 레퍼런스 문서를 참고한다.
개발하면서 발생했던 에러 로그
@XmlRootElement 주석이 누락되어 "hello.wsdl.SelectCustomerInfo$RequestBody" 유형을 요소로 마셜링할 수 없습니다.
Jaxb2Marshaller를 이용해서 마샬링과 언마셜링을 했었는데, setContextPath 라는 메서드를 이용하여 마샬링 혹은 언마샬링을 할 경로를 지정해준다. 이때 ContextPath 상 클래스에 @XmlRootElement 어노테이션이 없는 경우에 발생한다.
JAXB 제너레이터(생성기)로 만든 클래스인데 해당 어노테이션이 없이 만들어져서 의아했다.
Caused by: org.springframework.ws.soap.client.SoapFaultClientException: Message part {http://customerinfo.example.com/}CustomerInfoRequest was not recognized. (Does it exist in service WSDL?)
서비스 URL과 WSDL제공 URL은 다르다.
위에서 언급한 gs-producing-web-service 예의 경우, wsdl 주소는 http://localhost:8080/ws/countries.wsdl 이지만, HTTP POST 요청을 보내는 서비스 주소는 http://localhost:8080/ws 이다.
위의 캡쳐는 Wizdler 이라는 크롬 확장 프로그램인데 SOAP 서비스에 대해 브라우저에서 테스트를 해볼 수 있어서 이번 개발에 유용했다.
(실제 요청도 할 수 있다.)
Caused by: org.springframework.ws.soap.SoapMessageCreationException: Could not create message from InputStream: Unable to create envelope from given source: ; nested exception is com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl: Unable to create envelope from given source:
at org.springframework.ws.soap.saaj.SaajSoapMessageFactory.createWebServiceMessage(SaajSoapMessageFactory.java:218) ~[spring-ws-core-3.0.6.RELEASE.jar:na]
솔직히 왜 이 메세지가 나왔는지는 아직 잘 모르겠다. 위의 메세지에 언급되는 WebServiceTemplate 클래스를 초기화 할 때 기본적으로 사용하는 팩토리 클래스이다.
SAAJ는 SOAP with Attachments API for Java™의 약자로 스프링 부트 애플리케이션이 뜰 때 아래와 같은 로그를 볼 수 있다.
2019-05-03 11:01:42.268 INFO 23282 --- [ restartedMain] o.s.ws.soap.saaj.SaajSoapMessageFactory [] : Creating SAAJ 1.3 MessageFactory with SOAP 1.1 Protocol
@Override
public void afterPropertiesSet() {
if (messageFactory == null) {
try {
if (SaajUtils.getSaajVersion() >= SaajUtils.SAAJ_13) {
if (!StringUtils.hasLength(messageFactoryProtocol)) {
messageFactoryProtocol = SOAPConstants.SOAP_1_1_PROTOCOL;
}
if (logger.isInfoEnabled()) {
logger.info("Creating SAAJ 1.3 MessageFactory with " + messageFactoryProtocol);
}
messageFactory = MessageFactory.newInstance(messageFactoryProtocol);
}
else if (SaajUtils.getSaajVersion() == SaajUtils.SAAJ_12) {
logger.info("Creating SAAJ 1.2 MessageFactory");
messageFactory = MessageFactory.newInstance();
}
else if (SaajUtils.getSaajVersion() == SaajUtils.SAAJ_11) {
logger.info("Creating SAAJ 1.1 MessageFactory");
messageFactory = MessageFactory.newInstance();
}
else {
throw new IllegalStateException(
"SaajSoapMessageFactory requires SAAJ 1.1, which was not found on the classpath");
}
}
catch (NoSuchMethodError ex) {
throw new SoapMessageCreationException(
"Could not create SAAJ MessageFactory. Is the version of the SAAJ specification interfaces [" +
SaajUtils.getSaajVersionString() +
"] the same as the version supported by the application server?", ex);
}
catch (SOAPException ex) {
throw new SoapMessageCreationException("Could not create SAAJ MessageFactory: " + ex.getMessage(), ex);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using MessageFactory class [" + messageFactory.getClass().getName() + "]");
}
}
SAAJ 1.3 버전을 쓰는데 정작 사용한 SOAP은 1.1 프로토콜이었다.
'Programing > Framework' 카테고리의 다른 글
[spring boot] Type-safe Configuration Properties 쓸 때 주의점 (0) | 2019.06.11 |
---|---|
[Spring] AOP로 로깅 코드 덜어내기 (0) | 2019.05.29 |
[Jackson vs Gson] 오버라이딩한 메서드의 JSON serialize 테스트 (0) | 2019.04.22 |
[Spring] core - Converter 인터페이스 (0) | 2019.03.29 |
[SpringBoot] HikariCP의 leakDetectionThreshold 기본값은? (0) | 2019.03.28 |