Spring支持:
使用JAX-RPC暴露服务
访问Web服务
除了上面所说的支持方法,你还可以用XFire xfire.codehaus.org 来暴露你的服务。XFire是一个轻量级的SOAP库,目前在Codehaus开发。
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服务的。
Spring有两个工厂bean用来创建Web服务代理,LocalJaxRpcServiceFactoryBean
和 JaxRpcPortProxyFactoryBean
。前者只返回一个JAX-RPT服务类供我们使用。后者是一个全功能的版本,可以返回一个实现我们业务服务接口的代理。本例中,我们使用后者来为前面段落中暴露的AccountService端点创建一个代理。你将看到Spring对Web服务提供了极好的支持,只需要很少的代码 - 大多数都是通过类似下面的Spring配置文件:
<bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean"> <property name="serviceInterface" value="example.RemoteAccountService"/> <property name="wsdlDocumentUrl" value="http://localhost:8080/account/services/accountService?WSDL"/> <property name="namespaceUri" value="http://localhost:8080/account/services/accountService"/> <property name="serviceName" value="AccountService"/> <property name="portName" value="AccountPort"/> </bean>
serviceInterface
是客户端将要使用的远程业务接口。wsdlDocumentUrl
是WSDL文件的URL。Spring需要这些在启动时创建JAX-RPC服务。namespaceUri
对应到.wsdl文件中的targetNamespace。serviceName
对应到.wsdl文件中的service name。portName
对应到.wsdl文件中的端口号。
现在bean工厂将把Web服务暴露为 RemoteAccountService
接口,访问服务变得很容易。我们可以在Spring中这样组装起来:
<bean id="client" class="example.AccountClientImpl"> ... <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 ex) { // ouch ... } } }
由于Spring提供了自动转换成非受控异常的能力,我们可以不用考虑受控的RemoteException异常。这要求我们也提供一个非RMI接口,配置文件现在如下:
<bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean"> <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(...); } }
为了传递类似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)); } }
本节中,我们将注册自己的 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" class="example.AccountHandlerJaxRpcPortProxyFactoryBean"> ... </bean>
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" class="org.codehaus.xfire.spring.XFireExporter"> <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" class="org.codehaus.xfire.spring.EchoImpl"/> </beans>
XFire处理了其他的事情。它检查你的服务接口并产生一个WSDL文件。这里的部分文档来自XFire网站,要了解更多有关XFire Spring的集成请访问 docs.codehaus.org/display/XFIRE/Spring。