18.5.Web服务

优质
小牛编辑
118浏览
2023-12-01

18.5. Web服务

Spring支持:

  • 使用JAX-RPC暴露服务

  • 访问Web服务

除了上面所说的支持方法,你还可以用XFire xfire.codehaus.org 来暴露你的服务。XFire是一个轻量级的SOAP库,目前在Codehaus开发。

18.5.1. 使用JAXI-RPC暴露服务

Spring对JAX-RPC Servlet的端点实现有个方便的基类 - ServletEndpointSupport。为暴露我们的Account服务,我们继承了Spring的ServletEndpointSupport类来实现业务逻辑,这里通常把调用委托给业务层。

/**
 * JAX-RPC compliant RemoteAccountService implementation that simply delegates
 * to the AccountService implementation in the root web application context.
 *
 * This wrapper class is necessary because JAX-RPC requires working with
 * RMI interfaces. If an existing service needs to be exported, a wrapper that
 * extends ServletEndpointSupport for simple application context access is
 * the simplest JAX-RPC compliant way.
 *
 * This is the class registered with the server-side JAX-RPC implementation.
 * In the case of Axis, this happens in "server-config.wsdd" respectively via
 * deployment calls. The Web Service tool manages the life-cycle of instances
 * of this class: A Spring application context can just be accessed here.
 */
public class AccountServiceEndpoint extends ServletEndpointSupport implements RemoteAccountService {

    private AccountService biz;

    protected void onInit() {
  this.biz = (AccountService) getWebApplicationContext().getBean("accountService");
    }

    public void insertAccount(Account acc) throws RemoteException {
  biz.insertAccount(acc);
    }

    public Account[] getAccounts(String name) throws RemoteException {
  return biz.getAccounts(name);
    }

}

AccountServletEndpoint需要在Spring中同一个上下文的web应用里运行,以获得对Spring的访问能力。如果使用Axis,把Axis的定义复制到你的web.xml中,并且在"server-config.wsdd"中设置端点(或使用发布工具)。参看JPetStore这个例子中OrderService是如何用Axis发布成一个Web服务的。

18.5.2. 访问Web服务

Spring有两个工厂bean用来创建Web服务代理,LocalJaxRpcServiceFactoryBeanJaxRpcPortProxyFactoryBean。前者只返回一个JAX-RPT服务类供我们使用。后者是一个全功能的版本,可以返回一个实现我们业务服务接口的代理。本例中,我们使用后者来为前面段落中暴露的AccountService端点创建一个代理。你将看到Spring对Web服务提供了极好的支持,只需要很少的代码 - 大多数都是通过类似下面的Spring配置文件:

    <bean id="accountWebService">
  <property name="serviceInterface">
<value>example.RemoteAccountService</value>
  </property>
  <property name="wsdlDocumentUrl">
<value>http://localhost:8080/account/services/accountService?WSDL</value>
  </property>
  <property name="namespaceUri">
<value>http://localhost:8080/account/services/accountService</value>
  </property>
  <property name="serviceName">
<value>AccountService</value>
  </property>
  <property name="portName">
<value>AccountPort</value>
  </property>
    </bean>

serviceInterface 是客户端将要使用的远程业务接口。wsdlDocumentUrl 是WSDL文件的URL。Spring需要这些在启动时创建JAX-RPC服务。namespaceUri 对应到.wsdl文件中的targetNamespace。serviceName 对应到.wsdl文件中的service name。portName 对应到.wsdl文件中的端口号。

现在bean工厂将把Web服务暴露为 RemoteAccountService 接口,访问服务变得很容易。我们可以在Spring中这样组装起来:

    <bean id="client">
  ...
  <property name="service" ref="accountWebService"/>
    </bean>

在客户端我们可以使用类似于普通类的方式来访问Web服务,区别是它抛出RemoteException异常。

public class AccountClientImpl {

    private RemoteAccountService service;

    public void setService(RemoteAccountService service) {
  this.service = service;
    }

    public void foo() {
 try {
     service.insertAccount(...);
  } catch (RemoteException e) {
     // ouch
     ...
  }
     }

}

由于Spring提供了自动转换成非受控异常的能力,我们可以不用考虑受控的RemoteException异常。这要求我们也提供一个非RMI接口,配置文件现在如下:

    <bean id="accountWebService">
  <property name="serviceInterface">
<value>example.AccountService</value>
  </property>
  <property name="portInterface">
<value>example.RemoteAccountService</value>
  </property>
  ...
    </bean>

这里 serviceInterface 已经改成我们目前的非RMI接口。我们的RMI接口现在使用属性 portInterface 进行定义。现在客户端代码可以不用处理 java.rmi.RemoteException 异常:

public class AccountClientImpl {

    private AccountService service;

    public void setService(AccountService service) {
  this.service = service;
    }

    public void foo() {
  service.insertAccount(...);
     }

}

18.5.3. 注册bean映射

为了传递类似Account等复杂对象,我们必须在客户端注册bean映射。

注意

在服务器端通常在server-config.wsdd中使用Axis进行bean映射注册。

我们将使用Axis在客户端注册bean映射。为此,我们需要继承一个Spring Bean工厂并通过编程注册这个bean映射。

public class AxisPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {

protected void postProcessJaxRpcService(Service service) {
TypeMappingRegistry registry = service.getTypeMappingRegistry();
TypeMapping mapping = registry.createTypeMapping();
registerBeanMapping(mapping, Account.class, "Account");
registry.register("http://schemas.xmlsoap.org/soap/encoding/", mapping);
}

protected void registerBeanMapping(TypeMapping mapping, Class type, String name) {
QName qName = new QName("http://localhost:8080/account/services/accountService", name);
mapping.register(type, qName,
    new BeanSerializerFactory(type, qName),
    new BeanDeserializerFactory(type, qName));
}

}

18.5.4. 注册自己的处理方法

本节中,我们将注册自己的 javax.rpc.xml.handler.Handler 到Web服务代理,这样我们可以在SOAP消息被发送前执行定制的代码。javax.rpc.xml.handler.Handler 是一个回调接口。jarxpr.jar中有个方便的基类 - javax.rpc.xml.handler.GenericHandler 供我们继承使用:

public class AccountHandler extends GenericHandler {

    public QName[] getHeaders() {
  return null;
    }

    public boolean handleRequest(MessageContext context) {
  SOAPMessageContext smc = (SOAPMessageContext) context;
  SOAPMessage msg = smc.getMessage();

  try {
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
...

  } catch (SOAPException e) {
throw new JAXRPCException(e);
  }

  return true;
    }

}

我们现在要做的就是把AccountHandler注册到JAX-RPC服务,这样它可以在消息被发送前调用 handleRequest。Spring目前对注册处理方法还不提供声明式支持。所以我们必须使用编程方式。但是Spring中这很容易实现,我们只需继承相关的bean工厂类并覆盖专门为此设计的 postProcessJaxRpcService 方法:

public class AccountHandlerJaxRpcPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {

    protected void postProcessJaxRpcService(Service service) {
  QName port = new QName(this.getNamespaceUri(), this.getPortName());
  List list = service.getHandlerRegistry().getHandlerChain(port);
  list.add(new HandlerInfo(AccountHandler.class, null, null));

  logger.info("Registered JAX-RPC Handler [" + AccountHandler.class.getName() + "] on port " + port);
    }

}

最后,我们要记得更改Spring配置文件来使用我们的工厂bean。

    <bean id="accountWebService">
  ...
    </bean>

18.5.5. 使用XFire来暴露Web服务

XFire是一个Codehaus提供的轻量级SOAP库。在写作这个文档时(2005年3月)XFire还处于开发阶段。虽然Spring提供了稳定的支持,但是在未来应该会加入更多特性。暴露XFire是通过XFire自身带的context,这个context将和RemoteExporter风格的bean相结合,后者需要被加入到在你的WebApplicationContext中。

在所有这些允许你暴露服务的方法中,你都必须使用一个相关的WebApplicationContext来创建一个DispatcherServlet,这个WebApplicationContext包含将暴露的服务:

<servlet>
  <servlet-name>xfire</servlet-name>
  <servlet-class>
    org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
</servlet>
    

你还必须链接XFire配置。这是通过增加一个context文件到ContextLoaderListener(或者是Servlet)指定的 contextConfigLocations 参数中。这个配置文件在XFire jar中,当然这个jar文件应该放在你应用的classpath中。

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    classpath:org/codehaus/xfire/spring/xfire.xml
  </param-value>
</context-param>

<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>
    

在你加入一个Servlet映射后(映射 /* 到上面定义的XFire Servlet),你只需要增加一个额外的bean来暴露使用XFire的服务。例如,在 xfire-servlet.xml 中如下:

<beans>
  <bean name="/Echo">
    <property name="service" ref="echo">
    <property name="serviceInterface" value="org.codehaus.xfire.spring.Echo"/>
    <property name="serviceBuilder" ref="xfire.serviceBuilder"/>
    <!-- the XFire bean is wired up in the xfire.xml file you've linked in earlier -->
    <property name="xfire" ref="xfire"/>
  </bean>

  <bean id="echo"/>
</beans>

XFire处理了其他的事情。它检查你的服务接口并产生一个WSDL文件。这里的部分文档来自XFire网站,要了解更多有关XFire Spring的集成请访问 docs.codehaus.org/display/XFIRE/Spring。