MessageDispatcher#

Spring-WS의 서버측은 XML메시지를 endpoint로 전달하는 클래스로 디자인되었다. 다음은 Spring WS에서의 request를 처리 절차이다.

http://static.springframework.org/spring-ws/site/reference/html/images/sequence.png

MessageDispatcherServlet#

기본적으로 web.xml 파일에 정의할때 사용된다.

WSDL 노출#

<bean id="orders" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
    <constructor-arg value="/WEB-INF/wsdl/Orders.wsdl"/>
</bean>

http://localhost:8080/spring-ws/orders.wsdl

XSD에서 WSDL을 동적으로 생성하기#

<bean id="holiday" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition">
  <property name="builder">
    <bean class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder">
      <property name="schema" value="/WEB-INF/xsd/Orders.xsd"/>
      <property name="portTypeName" value="Orders"/>
      <property name="locationUri" value="http://localhost:8080/ordersService/"/>
    </bean>
  </property>
</bean>

DynamicWsdl11Definition 는 WSDL을 생성하기 위해 Wsdl11DefinitionBuilder 를 사용한다. 위 예제에서는 XsdBasedSoap11Wsdl4jDefinitionBuilder를 사용했다. 동적으로 WSDL을 생성하기 위해서는 xsd에 request나 response로 끝나는 element가 있어야 한다. 이를테면 다음의 예를 보자.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema elementFormDefault="qualified"
           targetNamespace="http://openframework.or.kr/hr/schemas" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:hr="http://openframework.or.kr/hr/schemas">
    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:all>
                <xs:element name="Holiday" type="hr:HolidayType"/>
                <xs:element name="Employee" type="hr:EmployeeType"/>
            </xs:all>
        </xs:complexType>
    </xs:element>

    ....

</xs:schema>

위 소스의 경우 HolidayRequest라는 element가 있다. 이런 경우 자동으로 생성되는 WSDL에는 HolidayRequest 요소와 함께 Holiday라는 operation이 생성된다.

<?xml version="1.0" encoding="UTF-8"?><wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:schema="http://openframework.or.kr/hr/schemas" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://openframework.or.kr/hr/definitions" targetNamespace="http://openframework.or.kr/hr/definitions">
  <wsdl:types>
....
  </wsdl:types>
  <wsdl:message name="HolidayRequest">
....
  </wsdl:message>
  <wsdl:portType name="HumanResource">
    <wsdl:operation name="Holiday">
      <wsdl:input message="tns:HolidayRequest" name="HolidayRequest">
    </wsdl:input>
    </wsdl:operation>
  </wsdl:portType>
....
  </wsdl:binding>
</wsdl:definitions>

DispatcherServlet에 Spring-WS 묶기#

Endpoints#

public interface PayloadEndpoint {

    /**
     * Invokes an operation.
     */
    Source invoke(Source requestthrows Exception;
}

Endpoint는 Spring-WS의 서버측 지원에 대한 핵심이다.

AbstractDomPayloadEndpoint #

package samples;
            
public class SampleEndpoint extends AbstractDomPayloadEndpoint {

    private String responseText;

    public SampleEndpoint(String responseText) {
        this.responseText = responseText;
    }

    protected Element invokeInternal(
            Element requestElement,
            Document documentthrows Exception {
        String requestText = requestElement.getTextContent();
        System.out.println("Request text: " + requestText);

        Element responseElement = document.createElementNS("http://samples""response");
        responseElement.setTextContent(responseText);
        return responseElement;
    }
}

<bean id="sampleEndpoint" class="samples.SampleEndpoint">
    <constructor-arg value="Hello World!"/>
</bean>

위 소스를 사용했을때 요청 메시지는 다음과 같다.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <request xmlns="http://samples">
            Hello
        </request>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

다음은 응답 메시지이다.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <response xmlns="http://samples">
            Hello World!
        </response>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

AbstractMarshallingPayloadEndpoint#

DOM을 사용하여 XML직접 다루는 것보다 XML메시지를 자바 객체로 변환하기 위해 마샬링을 사용할수 있다. 먀샬링 기법에 대해서는 마샬링 페이지를 참고한다.

package samples;

import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class MarshallingOrderEndpoint extends AbstractMarshallingPayloadEndpoint{

    private final OrderService orderService;

    public SampleMarshallingEndpoint(OrderService orderService, Marshaller marshaller) {
        super(marshaller);
        this.orderService = orderService;
    }

    protected Object invokeInternal(Object requestthrows Exception {
        OrderRequest orderRequest = (OrderRequestrequest;
        Order order = orderService.getOrder(orderRequest.getId());
        return order;
    }
}

<beans>
    <bean id="orderEndpoint" class="samples.MarshallingOrderEndpoint">
        <constructor-arg ref="orderService"/>
        <constructor-arg ref="marshaller"/>
    </bean>

    <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>samples.OrderRequest</value>
                <value>samples.Order</value>
            </list>
        </property>
    </bean>

    <bean id="orderService" class="samples.DefaultOrderService"/>

    <!-- Other beans, such as the endpoint mapping -->
</beans>

요청 메시지가 다음과 같다면

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <orderRequest xmlns="http://samples" id="42"/>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

응답 메시지는 다음과 같을 것이다.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <order xmlns="http://samples" id="42">
            <item id="100">
                <quantity>1</quantity>
                <price>20.0</price>
            </item>
            <item id="101">
                <quantity>1</quantity>
                <price>10.0</price>
            </item>
        </order>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

@Endpoint#

앞서 본 방식은 상속을 이용한 Endpoint방식이다. 다음은 자바 5 이상에서 사용할수 있는 어노테이션을 이용한 방법이다. @PayloadRoot 어노테이션은 웹서비스 호출에 의해 처리되는 메소드를 지정한다고 보면 된다.

package samples;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;

@Endpoint
public class AnnotationOrderEndpoint {
    private final OrderService orderService;

    public AnnotationOrderEndpoint(OrderService orderService) {
        this.orderService = orderService;
    }

    @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
    public Order getOrder(OrderRequest orderRequest) {
        return orderService.getOrder(orderRequest.getId());
    }

    @PayloadRoot(localPart = "order", namespace = "http://samples")
    public void order(Order order) {
        orderService.createOrder(order);
    }

}

<beans>

    <bean id="orderEndpoint" class="samples.AnnotationOrderEndpoint">
        <constructor-arg ref="orderService"/>
    </bean>

    <bean id="orderService" class="samples.DefaultOrderService"/>

    <bean class="org.springframework.ws.server.endpoint.adapter.MarshallingMethodEndpointAdapter">
        <constructor-arg ref="marshaller"/>
    </bean>

    <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>samples.OrderRequest</value>
                <value>samples.Order</value>
            </list>
        </property>        
    </bean>

    <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>

</beans>

@XPathParam#

마샬링에 대한 대안으로 XML메시지에서 필요한 정보를 가져오기 위해 xpath를 사용할수 있다. @XPathParam 어노테이션을 통해 해당되는 경로의 xpath값을 사용한다.

package samples;

import javax.xml.transform.Source;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.XPathParam;

@Endpoint
public class AnnotationOrderEndpoint {

    private final OrderService orderService;

    public AnnotationOrderEndpoint(OrderService orderService) {
        this.orderService = orderService;
    }

    @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
    public Source getOrder(@XPathParam("/s:orderRequest/@id"double orderId) {
        Order order = orderService.getOrder((intorderId);
        // create Source from order and return it
    }

}

<beans>
    <bean id="orderEndpoint" class="samples.AnnotationOrderEndpoint">
        <constructor-arg ref="orderService"/>
    </bean>

    <bean id="orderService" class="samples.DefaultOrderService"/>

    <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>

    <bean class="org.springframework.ws.server.endpoint.adapter.XPathParamAnnotationMethodEndpointAdapter">
        <property name="namespaces">
            <props>
                <prop key="s">http://samples</prop>
            </props>
        </property>
    </bean>

</beans>

@XPathParam 어노테이션을 통해 지원되는 데이터 타입은 아래와 같다.

  • boolean or Boolean
  • double or Double
  • String
  • Node
  • NodeList

Endpoint 매핑#

던져진 메시지를 적절한 endpoint로 매핑하기 위해 사용한다. 예를 들어 PayloadRootQNameEndpointMapping 나 the SoapActionEndpointMapping가 있다. EndpointInvocationChain는 endpoint와 인터셉터의 목록을 가진다. 대부분의 endpoint 매핑은 AbstractEndpointMapping를 상속한다.

다음은 endpoint매핑에 사용되는 프로퍼티이다.

  • interceptors - 인터셉터의 목록
  • defaultEndpoint - 디폴트 endpoint

PayloadRootQNameEndpointMapping#

prop의 key값은 { + namespace URI + } + local 의 형태를 가진다.

<beans>

    <!-- no 'id' required, EndpointMapping beans are automatically detected by the MessageDispatcher -->
    <bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
        <property name="mappings">
            <props>
                <prop key="{http://samples}orderRequest">getOrderEndpoint</prop>
                <prop key="{http://samples}order">createOrderEndpoint</prop>
            </props>
        </property>
    </bean>

    <bean id="getOrderEndpoint" class="samples.GetOrderEndpoint">
        <constructor-arg ref="orderService"/>
    </bean>

    <bean id="createOrderEndpoint" class="samples.CreateOrderEndpoint">
        <constructor-arg ref="orderService"/>
    </bean>
<beans>

SoapActionEndpointMapping#

<beans>
    <bean id="endpointMapping" class="org.springframework.ws.soap.server.endpoint.mapping.SoapActionEndpointMapping">
        <property name="mappings">
            <props>
                <prop key="http://samples/RequestOrder">getOrderEndpoint</prop>
                <prop key="http://samples/CreateOrder">createOrderEndpoint</prop>
            </props>
        </property>
    </bean>

    <bean id="getOrderEndpoint" class="samples.GetOrderEndpoint">
        <constructor-arg ref="orderService"/>
    </bean>

    <bean id="createOrderEndpoint" class="samples.CreateOrderEndpoint">
        <constructor-arg ref="orderService"/>
    </bean>
</beans>

MethodEndpointMapping#

앞서 보았던 @Endpoint 스타일의 방법이다. PayloadRootAnnotationMethodEndpointMapping 과 SoapActionAnnotationMethodEndpointMapping 두가지가 있다. PayloadRootAnnotationMethodEndpointMapping 의 경우 @PayloadRoot 어노테이션을 사용하고 SoapActionAnnotationMethodEndpointMapping 의 경우 @SoapAction 어노테이션을 사용한다.

요청 가로채기(EndpointInterceptor 인터페이스)#

요청 가로채기는 보안및 로깅과 같은 어떤 추가적인 기능을 수행하고자 할때 적절하다고 볼수 있다. endpoint 매핑에 위치한 인터셉터는 org.springframework.ws.server.EndpointInterceptor 인터페이스를 구현해야만 한다. 이 인터페이스는 3개의 메소드는 가지는데 하나는 실제 endpoint가 실행되기 전에 요청 메시지를 처리하기 위해 사용되고, 또 하나는 일반적인 응답 메시지를 처리하기 위해 사용된다. 나머지 하나는 오류 메시지를 다루기 위해 사용된다. 인터셉터의 handleRequest(..) 메소드는 boolean값을 반환한다. true를 반환하면 이후 작업이 계속 진행되지만 false를 반환하면 처리는 거기서 멈춘다. handleResponse(..) 와 handleFault(..) 도 boolean값을 반환하고 false를 반환하면 클라이언트로 응답을 보내지 않는다. EndpointInterceptor 에는 다양한 종류가 존재한다. ! PayloadLoggingInterceptor 와 SoapEnvelopeLoggingInterceptor 로깅용 인터셉터이다.

 <beans>
    <bean id="endpointMapping"
        class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
        <property name="interceptors">
            <list>
                <ref bean="loggingInterceptor"/>
            </list>
        </property>
        <property name="mappings">
            <props>
                <prop key="{http://samples}orderRequest">getOrderEndpoint</prop>
                <prop key="{http://samples}order">createOrderEndpoint</prop>
            </props>
        </property>
    </bean>

    <bean id="loggingInterceptor"
    class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
</beans>

이 인터셉터들은 'logRequest' 와 'logResponse' 라는 두개의 프로퍼티를 가진다. 요청이냐 응답이냐에 따라 값을 false로 지정하면 로깅기능이 작동하지 않는다.

PayloadValidatingInterceptor#

contract-first 개발 스타일(WSDL을 기준으로 개발하는 방식)의 장점중 하나는 스키마를 기준으로 XML메시지의 유효성 체크가 가능하다는 것이다.

<bean id="validatingInterceptor"
        class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
    <property name="schema" value="/WEB-INF/orders.xsd"/>
    <property name="validateRequest" value="false"/>
    <property name="validateResponse" value="true"/>
</bean>

PayloadTransformingInterceptor#

XML 메시지를 다른 형태로 변형하기 위해 사용하는 인터셉터이다. 인자로 xslt를 주면 된다.

<bean id="transformingInterceptor"
        class="org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor">
    <property name="requestXslt" value="/WEB-INF/oldRequests.xslt"/>
    <property name="requestXslt" value="/WEB-INF/oldResponses.xslt"/>
</bean>

예외 다루기#

Spring-WS는 예외를 다루기 위해 EndpointExceptionResolvers 를 제공한다. MessageDispatcher는 자동으로 endpoint 예외 결정자(resolvers)를 자동으로 가져온다. 자동으로 가져오기 때문에 명시적인 설정은 필요하지 않다. resolveException(MessageContext, endpoint, Exception) 메소드를 구현해야 한다. 가장 간단한 구현체는 SimpleSoapExceptionResolver이다. SoapFaultMappingExceptionResolver 는 좀더 정교한 구현체이다.

<beans>
    <bean id="exceptionResolver"
        class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
    <property name="defaultFault" value="SERVER">
    </property>
    <property name="exceptionMappings">
        org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
    </property>
    </bean>
</beans>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Client</faultcode>
           <faultstring>Invalid request</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

package samples;

import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;

@SoapFault(faultCode = FaultCode.SERVER)
public class MyBusinessException extends Exception {

    public MyClientException(String message) {
        super(message);
    }
}

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
   <SOAP-ENV:Fault>
       <faultcode>SOAP-ENV:Server</faultcode>
       <faultstring>Oops!</faultstring>
   </SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-3) was last changed on 28-Oct-2007 22:01 by DongGukLee