2010년 4월 15일 목요일

JAX-WS client 작성


  • Dispatch를 이용한 클라이언트 작성

  • jaxws:client를 이용한 클라이언트 작성

  • JaxWsProxyFactoryBean를 이용한 클라이언트 작성


CXF를 이용하여 웹서비스를 개발할 때 JAX-WS 클라이언트 만드는 방법을 이야기하고자 한다.
CXF가 Spring기반의 프레임웍이기 때문에 빈을 등록하여 사용하는 방법과 SOAP 메시지를 만들어서 사용하는 Java 표준 API(?) 방식을 비교해 보기 바란다.

이해를 돕기 위해 요청메시지와 응답메시지를 먼저 살펴보도록 한다.

요청 메시지
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.demo.amf.archnal.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <ws:getUser>
         <userNo>1</userNo>
      </ws:getUser>
   </soapenv:Body>
</soapenv:Envelope>


응답 메시지
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ns2:getUserResponse xmlns:ns2="http://ws.demo.amf.archnal.com/">
         <return>
            <email>email1@company.com</email>
            <loginId>loginid1</loginId>
            <loginPassword>passwd1</loginPassword>
            <name>name1</name>
            <userNo>1</userNo>
         </return>
      </ns2:getUserResponse>
   </soap:Body>
</soap:Envelope>


JAVA API를 이용하여 클라이언트 작성


아래는 WSDL 정보가 있다면 개발자가 아래와 같이 클라이언트를 작성할 수 있다.

표준 JAVA API 클라이언트
  public void testInvoke() throws Exception {
    String endpointAddress = "http://localhost:8080/demo/amfesb/sampleUserWs";
    
    QName serviceName = new QName("http://ws.demo.amf.archnal.com/", "SampleUserWsImplService");
    QName portName = new QName("http://ws.demo.amf.archnal.com/", "SampleUserWsImplPort");
    
    Service service = Service.create(serviceName);
    
    service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
    
    Dispatch dispatch = service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);
    BindingProvider bp = (BindingProvider) dispatch;
    MessageFactory factory = ((SOAPBinding) bp.getBinding()).getMessageFactory();
    
    SOAPMessage request = factory.createMessage();
    SOAPBody body = request.getSOAPBody();
    QName payloadName = new QName("http://ws.demo.amf.archnal.com/", "getUser", "ws");
    SOAPBodyElement payload = body.addBodyElement(payloadName);
    
    
    QName userNoName = new QName("userNo");
    SOAPElement userNo = payload.addChildElement(userNoName);
    userNo.addTextNode("1");

    SOAPMessage reply = null;
    
    try {
      reply = dispatch.invoke(request);
    } catch(WebServiceException ex) {
      ex.printStackTrace();
    }
    
    body = reply.getSOAPBody();
    QName responseName = new QName("http://ws.demo.amf.archnal.com/", "getUserResponse");
    SOAPElement bodyElement = (SOAPElement) body.getChildElements(responseName).next();
    String responseMessageText = bodyElement.getTextContent();
    
  }


CXF 클라이언트 작성


CXF로 jaxws 클라이언트를 작성하기 위해서 SEI(Service Endpoint Interface)가 필요하다. SEI 클래스를 가지고 있지 않다고 하더라고 문제될 건 없다. http://credemol.blogspot.com/2010/04/cxf-codegen-plugin-maven.html 글에서 설명했던 것처럼 WSDL만 있으면 관련된 소스 코드를 자동으로 생성해 낼 수 있기 때문이다.

spring 빈 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans
     xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:jaxws="http://cxf.apache.org/jaxws"
     xmlns:cxf="http://camel.apache.org/schema/cxf"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://cxf.apache.org/jaxws 
    http://cxf.apache.org/schemas/jaxws.xsd
    http://camel.apache.org/schema/cxf 
    http://camel.apache.org/schema/cxf/camel-cxf.xsd
    ">
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxws.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-http.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-addr.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />

  <jaxws:client id="sampleUserWsBiz" 
    serviceClass="com.archnal.amf.demo.ws.SampleUserWs"
    address="http://localhost:8080/demo/amfesb/sampleUserWs">
  </jaxws:client>
  
 
</beans>


위와 같이 spring 빈을 설정하였다면 client 소스코드는 아래와 같이 작성하면 된다.
getBean()은 스프링 빈 객체를 리턴하는 메서드다.

CXF 클라이언트
  public void testSampleUserWsBiz() {
    SampleUserWs ws = (SampleUserWs) getBean("sampleUserWsBiz");
    assertNotNull(ws);
    
    SampleUser user = ws.getUser(1L);
    assertNotNull(user);
  }


JaxWsProxyFactoryBean를 이용한 클라이언트 작성


jaxws:client 설정을 이용하여 클라이언트를 작성하면 여러가지로 편리한 점이 있지만, endpoint uri를 설정에서만 지정하기 때문에 uri를 동적으로 지정하고자 할 때 문제가 발생한다.
하지만 JaxWsProxyFactoryBean는 address가 프로퍼티이기 때문에 스프링 설정에서 지정할 수 있고 또한 호출시에 동적으로 설정할 수도 있다.

아래의 소스코드를 살펴보자.
호출할 때마다 address(endpoint uri)를 지정하여 서비스를 호출하는 예제다.

cxf configuration
   <bean id="userProxyFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
      <property name="serviceClass" value="com.sds.shp.webservice.server.IUserWebService"/>
      <!-- 
      <property name="address" value="http://localhost:9002/HelloWorld"/>
       -->
    </bean>


서비스 호출 코드
package com.sds.shp.webservice.client;

import javax.annotation.Resource;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.sds.shp.domain.AuthWebservice;
import com.sds.shp.webservice.message.PutAccountInfoRequest;
import com.sds.shp.webservice.message.PutAccountInfoResponse;
import com.sds.shp.webservice.server.IUserWebService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:/spring/context-*.xml" })

public class UserWebServiceClient {
  @Resource(name="userProxyFactory")
  private JaxWsProxyFactoryBean userProxyFactory;
  
  @Test
  public void testJaxWsProxyFactoryBean() throws Exception {
    userProxyFactory.setAddress("http://70.7.105.245:8080/store/ws/userWebService");
    IUserWebService userWebService = (IUserWebService) userProxyFactory.create();

    
    System.out.println("userWebService: " + userWebService);
    
    AuthWebservice authToken = new AuthWebservice();
    authToken.setTokenId("token");
    authToken.setTokenPasswd("pw");
    
    PutAccountInfoRequest request = new PutAccountInfoRequest();
    PutAccountInfoResponse response = userWebService.putAccountInfo(authToken, request);
    
    System.out.println("code: " + response.getResultStatus().getErrorCode());
    
  }
}


위의 소스 코드는 JaxWsProxyFactoryBean을 스프링 환경 설정 파일로 등록 한 후 테스트 코드에서 등록된 리소스를 Injection 받아서 사용하는 예제다. 호출할 때마다 address를 설정해서 사용하기 때문에 동적으로 endpoint uri를 지정하도록 되어 있지만 JaxWsProxyFactoryBean 빈을 여러 쓰레드에서 함께 사용하기 때문에 쓰레드에 안전하지 않게 작동될 가능성이 높다.
스프링에 등록하지 않고 실행될 때마다 JaxWsProxyFactoryBean 빈을 생성하는 아래의 소스코드를 참조하기 바란다. 매 요청시마다 새로운 JaxWsProxyFactoryBean이 생성되기 때문에 멀티쓰레드 환경에서도 안전하게 실행될 수 있다.
  @Test
  public void testJaxWsProxyFactoryBean2() throws Exception {
    JaxWsProxyFactoryBean proxyFactoryBean = new JaxWsProxyFactoryBean();
    proxyFactoryBean.setServiceClass(IUserWebService.class);
    proxyFactoryBean.setAddress("http://70.7.105.245:8080/store/ws/userWebService");
    IUserWebService userWebService = (IUserWebService) proxyFactoryBean.create();

    
    System.out.println("userWebService: " + userWebService);
    
    AuthWebservice authToken = new AuthWebservice();
    authToken.setTokenId("token");
    authToken.setTokenPasswd("pw");
    
    PutAccountInfoRequest request = new PutAccountInfoRequest();
    PutAccountInfoResponse response = userWebService.putAccountInfo(authToken, request);
    
    System.out.println("code: " + response.getResultStatus().getErrorCode());
    
  }

댓글 없음:

댓글 쓰기