Spring实战(十五)-使用远程服务

本文基于《Spring实战(第4版)》所写。

我们有多种可以使用的远程调用技术,包括:

  • 远程方法调用(Remote Method Invocation, RMI);
  • Caucho的Hessian和Burlap;
  • Spring基于HTTP的远程服务;
  • 使用JAX-RPC和JAX-WS的Web Service。

Spring远程调用概览

远程调用是客户端应用和服务端之间的会话。在客户端,它所需要的一些共功能并不在该应用的实现范围之内,所以应用要向能提供这些功能的其他系统寻求帮助。而远程应用通过远程服务暴露这些功能。

下表概述了每个一个Spring支持的RPC模型,并简要讨论了它们所适用的不同场景。

RPC模型 适用场景
远程方法调用(RMI) 不考虑网络限制时(例如防火墙),访问/发布基于Java的服务
Hessian或Burlap 考虑网络限制时,通过HTTP访问/发布基于Java的服务。Hessian是二进制协议,而Burlap是基于XML的(Spring 5.0不支持Burlap了)
HTTP invoker 考虑网络限制,并希望使用基于XML或专有的序列化机制实现Java序列化时,访问/发布基于Spring的服务
JAX-PRC和JAX-WS 访问/发布平台独立的、基于SOAP的Web服务

在所有的模型中,服务都作为Spring所管理的bean配置到我们的应用中。这是通过一个代理工厂bean实现的,这个bean能够把远程服务像本地对象一样装配到其他bean的属性中去。下图展示了它是如何工作的。

Spring实战(十五)-使用远程服务_第1张图片
在Spring中,远程服务被代理,所以它们能够像其他Spring bean一样被装配到客户端代码中

客户端向代理发起作用,就像代理提供了这些服务一样。代理代表客户端与远程服务进行通信,由它负责处理连接的细节并向远程服务发起调用。

更重要的是,如果调用远程服务时发生java.rmi.RemoteException异常,代理会处理此异常并重新抛出非检查型异常RemoteAccessException。远程异常通常预示着系统发生了无法优雅恢复的问题,如网络或配置问题。既然客户端通常无法从远程异常中恢复,那么重新抛出RemoteAccessException异常就能让客户端决定是否处理此异常。

在服务器端,我们可以使用上表所列出的任意一种模型将Spring管理的bean发布为远程服务。下图展示了远程导出器(remote exporter)如何将bean方法发布为远程服务。

Spring实战(十五)-使用远程服务_第2张图片
使用远程导出器将Spring管理的bean发布为远程服务

无论我们开发的是使用远程服务的代码,还是实现这些服务的代码,或者两者兼而有之,在Spring中,使用远程服务纯粹是一个配置问题。我们不需要编写任何Java代码就可以支持远程调用。我们的服务bean也不需要关心他们是否参与了一个RPC(当然,任何传递给远程调用的bean或从远程调用返回的bean可能需要实现java.io.Serializable接口)。

使用RMI

RMI涉及到好几个步骤,包括程序的和手工的。Spring简化了RMI模型,它提供了一个代理工厂bean,能让我们把RMI服务像本地JavaBean那样装配到我们的Spring应用中。Spring还提供了一个远程导出器,用来简化把Spring管理的bean转换为RMI服务的工作。

导出RMI服务

创建RMI服务,会涉及如下几个步骤:

  1. 编写一个服务实现类,类中的方法必须抛出java.rmi.RemoteException异常;
  2. 创建一个继承于java.rmi.Remote的服务接口;
  3. 运行RMI编译器(rmic),创建客户端stub类和服务端skeleton类;
  4. 启动一个RMI注册表,以便持有这些服务;
  5. 在RMI注册表中注册服务。

在Spring中配置RMI服务

Spring只需简单地编写实现服务功能的POJO就可以了,Spring会处理剩余的其他事项。

我们将要创建RMI服务需要发布SpitterService接口中的方法,如下的程序清单展现了该接口定义

package spittr.web;

import spittr.model.Spitter;
import spittr.model.Spittle;

import java.util.List;

public interface SpitterService {
    List getRecentSpittles(int count);
    void saveSpittle(Spittle spittle);
    void saveSpitter(Spitter spitter);
    Spitter getSpitter(long id);
    void startFollowing(Spitter follower, Spitter followee);
    List getSpittlesForSpitter(Spitter spitter);
    List getSpittlesForSpitter(String username);
    Spitter getSpitter(String username);
    Spittle getSpittleById(long id);
    void deleteSpittle(long id);
    List getAllSpitters();
}

如果使用传统的RMI来发布服务,SpitterService和SpitterServiceImpl中的所有方法都需要抛出java.rmi.RemoteException。但是如果使用Spring的RmiServiceExporter把该类转变为RMI服务,那现有的实现不需要做任何改变。

RmiServiceExporter可以把任意Spring管理的bean发布为RMI服务。如下图所示,RmiServiceExporter把bean包装在一个适配器类中,然后适配器类被绑定到RMI注册表中,并且代理到服务类的请求—在本例中服务类也就是SpitterServiceImpl。

Spring实战(十五)-使用远程服务_第3张图片
RmiServiceExporter把POJO包装到服务适配器中,并将服务适配器绑定到RMI注册表中,从而将POJO转换为RMI服务

使用RmiServiceExporter将SpitterServiceImpl发布为RMI服务的最简单方式是在Spring中使用如下的@Bean方法进行配置:

    @Bean
    public RmiServiceExporter rmiServiceExporter(SpitterService spitterService){
        RmiServiceExporter rmiExporter = new RmiServiceExporter();
        rmiExporter.setService(spitterService);
        rmiExporter.setServiceName("SpitterService");
        rmiExporter.setServiceInterface(SpitterService.class);
//        rmiExporter.setRegistryHost("rmi.spitter.com");   // 可以不用,直接用本机地址
//        rmiExporter.setRegistryPort(1199);  // 可以不用,默认1099
        return rmiExporter;
    }

这就是使用Spring把某个bean转变为RMI服务所需要做的全部工作。现在Spitter服务已经导出为RMI服务,我们可以为Spittr应用创建其他的用户界面或邀请第三方使用此RMI服务创建新的客户端。如果使用Spring,客户端开发者访问Spitter的RMI服务会非常容易。

也可以使用XML配置




    

    
        
        
        
    

需要注意的是,由于rmi服务一般来讲使用jar包直接启动,所以我们还在工程中建立一个主函数

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) throws InterruptedException {
//        System.setProperty("java.rmi.server.hostname", "192.168.68.115");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
    }
}

然后打包为jar,并在Linux中执行,命令请看在linux下发布jar包

如果使用jar启服务时,提示没有“没有主清单属性”,请看maven生成jar,提示没有“没有主清单属性”

启动服务后,如果提示

[root@VM_0_17_centos ftpUser]# java -jar SpittrServer-1.0-SNAPSHOT.jar 
四月 19, 2018 10:52:45 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@22d8cfe0: startup date [Thu Apr 19 10:52:45 CST 2018]; root of context hierarchy
四月 19, 2018 10:52:45 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [app.xml]
四月 19, 2018 10:52:46 上午 org.springframework.remoting.rmi.RmiServiceExporter getRegistry
信息: Looking for RMI registry at port '1099'
四月 19, 2018 10:52:46 上午 org.springframework.remoting.rmi.RmiServiceExporter getRegistry
信息: Could not detect RMI registry - creating new one
四月 19, 2018 10:52:46 上午 org.springframework.remoting.rmi.RmiServiceExporter prepare
信息: Binding service 'SpitterServer' to RMI registry: RegistryImpl[UnicastServerRef [liveRef: [endpoint:[10.45.***.***:1099](local),objID:[0:0:0, 0]]]]

则表示服务请用成功。如果报Connection Refused,或者endpoint:后面的IP为127.0.0.1都表明服务启用不成功。

解决方法有两种

  1. 在代码中,启用服务前添加如下语句
System.setProperty("java.rmi.server.hostname", "192.168.68.115");

"192.168.68.115"表示当前IP,可以是局域网地址,也可以是外网地址

推荐本地调用时使用

  1. 如果是Linux系统,先 vim /etc/hosts ,文件内容如下:
127.0.0.1  localhost  localhost.localdomain  VM_0_17_centos
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

"VM_0_17_centos"是机器名

修改为

140.143.234.154 VM_0_17_centos localhost localhost4  localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

下面转换一下视角,来看看如何编写Spitter RMI服务的客户端。

装配RMI服务

传统上,RMI客户端必须使用RMI API的Naming类从RMI注册表中查找服务。例如,下面的代码片段演示了如何获取Spitter的RMI服务:

try{
    String serviceUrl = "rmi:/spitter/SpitterService";
    SpitterService spitterService = (SpitterService) Naming.lookup(serviceUrl);
    ...
}
catch (RemoteException e) { ... }
catch (NotBoundException e) { ... }
catch (MalformedURLException e) { ... }

虽然这段代码可以获取Spitter的RMI服务的引用,但是它存在两个问题:

  • 传统的RMI查找可能会导致3种检查型异常的任意一种(RemoteException、NotBoundException和MalformedURLException),这些异常必须被捕获或重新抛出;
  • 需要Spitter服务的任何代码都必须自己负责获取该服务。这属于样板代码,与客户端的功能并没有直接关系。

Spring的RmiProxyFactoryBean是一个工厂bean,该bean可以为RMI服务创建代理。使用RmiProxyFactoryBean引用SpitterService的RMI服务是非常简单的,只需要在客户端的Spring配置中增加如下的@Bean方法:

@Bean
public RmiProxyFactoryBean spitterService() {
    RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
    rmiProxy.setServiceUrl("rmi://192.168.68.115:1099/SpitterService");
    rmiProxy.setServiceInterface(SpitterService.class);
    return rmiProxy;
}

也可以使用XML



    
    
    
        
        
    

服务的URL是通过RmiProxyFactoryBean的serviceUrl属性来设置的,在这里,服务名被设置为SpitterService,并且声明服务是在本地机器上的;同时服务提供的接口由serviceInterface属性来指定。下图展示了客户端和RMI代理和交互。

Spring实战(十五)-使用远程服务_第4张图片
RmiProxyFactoryBean生成一个代理对象,该对象代表客户端来负责与远程的RMI服务进行通信。客户端通过服务的接口与代理进行交互,就如同远程服务就是一个本地的POJO

现在已经把RMI服务声明为Spring管理的bean,我们就可以把它作为依赖装配进另一个bean中,就像任意非远程的bean的那样。例如,假设客户端需要使用Spitter服务为指定的用户获取Spittle列表,我们可以使用@Autowired注解把服务代理装配进客户端中:

@Autowired
SpitterService spitterService;

我们还可以像本地bean一样调用它的方法:

public List getSpittle(String userName) {
    Spitter spitter = spitterService.getSpitter(userName);
    return spitterService.getSpittlesForSpitter(spitter);
}

此外,代理捕获了这个服务所有可能抛出的RemoteException异常,并把它包装为运行期异常重新抛出,这样我们就可以放心地忽略这些异常。

如果用于调试可创建一个主函数调用

import client.ClientInvoke;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
        ClientInvoke clientInvoke = (ClientInvoke) context.getBean("clientInvoke");
        clientInvoke.test();
        context.close();
    }
}

提醒一下,本例中调用了两次服务,都会受网络延迟的影响,进而可能会影响到客户端的性能。

RMI是一种实现远程服务交互的好办法,但是它存在某些限制。首先,RMI很难穿越防火墙,这是因为RMI使用任意端口来交互—这是防火墙通常所不允许的。

另外一件需要考虑的事情是RMI是基于Java的。这意味这客户端和服务端必须都是用Java开发的。因为RMI使用了Java的序列化机制,所以通过网络传输的对象类型必须要保证在调用两端的Java运行时中是完全相同的版本。

使用Hessian发布远程服务

Hessian和Burlap是Caucho Technology提供的两种基于HTTP的轻量级远程服务解决方案。

  • Hessian,基于二进制消息进行交互。可在Java、PHP、Python、C++和C#。由于二进制交互,带宽上更具优势。
  • Burlap,基于XML的远程调用技术。它的消息结构尽可能的简单,不需要额外的外部定义语句(例如WSDL或IDL),Spring 5.0不支持Burlap了。

使用Hessian导出bean的功能

像之前一样,把SpitterServiceImpl类的功能发布为远程服务—这次是一个Hessian服务。我们只需要编写一个继承com.caucho.hessian.server.HessianServlet的类,并确保所有的服务方法是public的(在Hessian里,所有public方法被视为服务方法)。

和Spring一起使用时,可利用Spring的AOP来为Hessian服务提供系统级服务,例如声明式事务。

导出Hessian服务

为了把Spitter服务bean发布为Hessian服务,我们需要配置另一个导出bean,只不过这次是HessianServiceExporter。

它把POJO的public方法发布成Hessian服务的方法。不过正如下图所示,其实现过程与RmiServiceExporter将POJO发布为RMI服务是不同的。

Spring实战(十五)-使用远程服务_第5张图片
HessianServiceExporter是一个Spring MVC控制器,它可以接收Hessian请求,并把这些请求转换成对被POJO的调用从而将POJO导出为一个Hessian服务

HessianServiceExporter是一个Spring MVC控制器,它接收Hessian请求,并把这些请求转换成对被POJO的调用从而将POJO导出为一个Hessian服务。在如下Spring的声明中,HessianServiceExporter会把spitterService bean导出为Hessian服务:

@Bean
public HessianServiceExporter hessianExportedSpitterService(SpitterService service) {
    HessianServiceExporter exporter = new HessianServiceExporter();
    exporter.setService(service);
    exporter.setServiceInterface(SpitterService.class);
    return exporter;
}

与RmiServiceExporter不同的是,我们不需要设置serviceName属性。在RMI中,serviceName属性用来在RMI注册表中注册一个服务。而Hessian没有注册表,因此也就没有必要为Hessian服务进行命名。

配置Hessian控制器

由于Hessian是基于HTTP的,所以HessianServiceExporter实现为一个Spring MVC控制器。这意味着为了使用导出的Hessian服务,我们需要执行两个额外的配置步骤:

  • 在web.xml中配置Spring的DispatcherServlet,并把我们的应用部署为Web应用;
  • 在Spring的配置文件中配置一个URL处理器,把Hessian服务的URL分发给对应的Hessian服务bean。

首先,我们需要一个DispatcherServlet。这个我们已经在Spittr应用的web.xml文件中配置了。但是为了处理Hessian服务,DispatcherServlet还需要配置一个Servlet映射来拦截后缀为“*.service”的URL:


    spitter
    *.service

如果在 Java中通过实现WebApplicationInitializer来配置DispatcherServlet的话,那么需要将URL模式作为映射添加到ServletRegistration.Dynamic中,在将DispatcherServlet添加到容器中的时候,我们能够得到ServletRegistration.Dynamic对象:

ServletRegistration.Dynamic dispatcher = container.addServlet("appServlet", new DispatcherServlet(dispatcherServletContext));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");
    dispatcher.addMapping("*.service");

或者,如果你通过扩展AbstractDispatcherServletInitializer或AbstractAnnotationConfigDispatcherServletInitializer的方式来配置DispatcherServlet,那么在重载getServletMappings() 的时候,需要包含该映射:

@Override
protected String[] getServletMappings() {
    return new String[] {"/" , "*.service"};
}

这样配置后,任何以“.service”结束的URL请求都将由DispatcherServlet处理,它会把请求传递给匹配这个URL的控制器。因此“/spitter.service”的请求最终将被hessianSpitterService bean所处理(它实际上仅仅是一个SpitterServiceImpl的代理)。

我们还需要配置一个URL映射来确保DispatcherServlet把请求转给hessianSpitterService。如下的SimpleUrlHandlerMapping bean可以做到这一点:

@Bean
public HandlerMapping hessianMapping() {
    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    Properties mappings = new Properties();
    mappings.setProperty("/spitter.service",
                         "hessianExportedSpitterService");
    mapping.setMappings(mappings);
    return mapping;
}

访问Hessian服务

与RMI的客户端的代码类似,客户端调用基于Hessian的Spitter服务可以用如下的配置声明:

@Bean
public HessianProxyFactoryBean spitterService() {
    HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
    proxy.setServiceUrl("http://localhost:8080/Spitter/spitter.service");
    proxy.setServiceInterface(SpitterService.class);
    return proxy;
}

如果想用xml配置,如下:


    
    

serviceInterface属性指定了这个服务实现的接口。并且,serviceUrl标识了这个服务的URL。既然Hessian是基于HTTP的,当然在这里要设置一个HTTP URL(URL是由我们先前定义的URL映射所决定的)。下图展示了客户端以及由HessianProxyFactoryBean所生成的代理之间是如何交互的。

Spring实战(十五)-使用远程服务_第6张图片
HessianProxyFactoryBean和BurlapProxyFactoryBean生成的代理对象负责通过HTTP(Hessian为二进制、Burlap为XML)与远程对象通信

Hessian和Burlap
优点:基于HTTP的,解决了防火墙渗透问题。服务端和客户端基本上支持常用语言。传输速度快。
缺点:Hessian和Burlap采用了私有的序列化机制

RMI
优点:RMI使用的是Java本身的序列化机制
缺点:由于不是HTTP协议,会有防火墙渗透问题。而且服务端和客户端都必须用Java语言。传输速度慢。

让我们看以下Spring的HTTP invoker, 它基于HTTP提供了RPC(像Hessian/Burlap一样),同时又使用了Java的对象序列化机制(像RMI一样)。

使用Spring的HttpInvoker

Spring的HttpInvoker 是一个新的远程调用模型,作为Spring框架的一部分,能够执行基于HTTP的远程调用(让防火墙不为难),并使用Java的序列化机制。

将bean导出为HTTP服务

为了把Spitter服务导出为一个基于HTTP invoker的服务,我们需要像下面的配置一样声明一个HttpInvokerServiceExporter bean:

    @Bean
    public HttpInvokerServiceExporter httpExportedSpitterService (SpitterService service) {
        HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
        exporter.setService(service);
        exporter.setServiceInterface(SpitterService.class);
        return exporter;
    }

如下图所示,HttpInvokerServiceExporter的工作方式与HessianServiceExporter很相似,它也是一个Spring的MVC控制器,它通过DispatcherServlet接收来自于客户端的请求,并将这些请求转换成对实现服务的POJO的方法调用。

Spring实战(十五)-使用远程服务_第7张图片
HttpInvokerServiceExporter工作方式与Hessian和Burlap很相似,通过Spring MVC的DispatcherServlet接收请求,并将这些请求转换成对Spring bean的方法调用

因为HttpInvokerServiceExporter是一个Spring MVC控制器,我们需要建立一个URL处理器,映射HTTP URL到对应的服务上,就像Hessian导出器所做的一样:

    @Bean
    public HandlerMapping httpInvokerMapping() {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        Properties mappings = new Properties();
        mappings.setProperty("/spitter.service",
                "httpExportedSpitterService");
        mapping.setMappings(mappings);
        return mapping;
    }

同样,我们需要确保匹配了DispatcherServlet,这样才能处理对“*.service”扩展的请求。

通过HTTP访问服务

如下图所示,HttpInvokerProxyFactoryBean填充了相同的位置。

Spring实战(十五)-使用远程服务_第8张图片
HttpInvokerProxyFactoryBean是一个代理工厂bean,用于生成一个代理,该代理使用Spring特有的基于HTTP协议进行远程通信

为了把基于HTTP invoker的远程服务装配进我们的客户端Spring应用上下文中,我们必须将HttpInvokerProxyFactoryBean配置为一个bean来代理它,如下所示:

@Bean
public HttpInvokerProxyFactoryBean spitterService(){
    HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
    proxy.setServiceUrl("http://localhost:8080/Spitter/spitter.service");
    proxy.setServiceInterface(SpitterService.class);
    return proxy;
}

serviceInterface属性用来标识Spitter服务所实现的接口,而serviceUrl属性用来标识远程服务的位置。

要记住HTTP invoker有一个重大的限制:客户端和服务端必须都是Spring应用,并且都要基于java。另外,因为使用Java的序列化机制,客户端与服务端必须使用相同版本的类(与RMI类似)。

发布和使用Web服务

SOA(面向服务架构)的核心理念是,应用程序可以并且应该被设计成依赖于一组公共的核心服务,而不是为每个应用都重新实现相同的功能。

Spring为使用Java API for XML Web Service(JAX-WS)来发布和使用SOAP Web服务提供了大力支持。

创建基于Spring的JAX-WS端点

Spring提供了JAX-WS服务导出器,SimpleJaxWsServiceExporter。但它并不一定是所有场景下的最好选择。SimpleJaxWsServiceExporter要求JAX-WS运行时支持将端点发布到指定地址上。Sun JDK 1.6自带的JAX-WS可以符合要求,但是其他的JAX-WS实现,包括JAX-WS的参考实现,可能并不能满足此需求。

如果我们将要部署的JAX-WS运行时不支持将其发布到指定地址上,那我们就要以更为传统的方式来编写JAX-WS端点。这意味着端点的生命周期由JAX-WS运行时来进行管理,而不是Spring。但这并不意味着它们不能装配Spring上下文的bean。

在Spring中自动装配JAX-WS端点

JAX-WS编程模型使用注解将类和类的方法声明为Web服务的操作。使用@WebService注解所标注的类被认为Web服务的端点,而使用@WebMethod注解所标注的方法被认为是操作。

装配JAX-WD端点的秘密在于继承SpringBeanAutowiringSupport。通过继承SpringBeanAutowiringSupport,我们可以使用@Autowired注解标注端点的属性,依赖就会自动注入了。(此方法未验证

导出独立的JAX-WS

SpringSimpleJaxWsServiceExporter的工作方式很类似于其他的服务导出器。它把Spring管理的bean发布为JAX-WS运行时中的服务端点。与其他服务导出器不同的是,SpringSimpleJaxWsServiceExporter不需要为它指定一个被导出bean的引用,它会将使用JAX-WS注解所标注的所有bean发布为JAX-WS服务。

SpringSimpleJaxWsServiceExporter可以使用如下的@Bean方法来配置:

    @Bean
    public SimpleJaxWsServiceExporter jaxWsServiceExporter(){
        SimpleJaxWsServiceExporter exporter = new SimpleJaxWsServiceExporter();
        exporter.setBaseAddress("http://localhost:8092/services/");
        return exporter;
    }

当启动的时候,它会搜索Spring应用上下文来查找所有使用@WebService注解的bean。当找到符合的bean时,SimpleJaxWsServiceExporter使用“http://localhost:8092/services/”地址将bean发布为JAX-WS端点的基本地址(也可不设置,默认为“http://localhost:8080”)。SpitterServiceEndpoint就是其中一个被查找到的bean

package spittr.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import spittr.model.Spittle;


import javax.jws.WebMethod;
import javax.jws.WebService;

@Service   // 必须标注,否则扫描不到
@WebService(serviceName = "spitterService")
public class SpitterServiceEndpoint { //启动自动配置
    @Autowired
    SpitterService spitterService; // 自动装配SpitterService

    @WebMethod
    public void addSpittle(Spittle spittle){
        spitterService.addSpittle(spittle);   // 委托给spitterService
    }

    @WebMethod
    public String deleteSpittle(long spittleId) {
       return spitterService.deleteSpittle(spittleId); // 委托给spitterService
    }

}

我们注意到SpitterServiceEndpoint完全就是一个Spring bean,因此它不需要继承任何特殊的支持类就可以实现自动装配。

需要注意的是,它只能用在支持将端点发布到指定地址的JAX-WS运行时中。这包含了sun 1.6 JDK自带的JAX-WS运行时。

在客户端代理JAX-WS服务

使用JaxWsProxyFactoryBean,我们可以在Spring中装配Spitter Web服务,与任意一个其他的bean一样。JaxWsProxyFactoryBean是Spring工厂bean,它能生成一个知道如何与SOAP Web服务交互的代理。所创建的代理实现了服务接口,如下图。

Spring实战(十五)-使用远程服务_第9张图片
JaxWsProxyFactoryBean生成可以与远程Web服务交互的代理。这些代理可以被装配到其他bean中,就像它们是本地POJO一样

我们可以像下面这样配置JaxWsPortProxyFactoryBean来引用Spitter服务:

    @Bean
    public JaxWsPortProxyFactoryBean spitterService() throws MalformedURLException {
        JaxWsPortProxyFactoryBean proxy = new JaxWsPortProxyFactoryBean();
        proxy.setWsdlDocumentUrl(new URL("http://localhost:8092/services/spitterService?wsdl"));
        proxy.setServiceName("spitterService");
        proxy.setPortName("SpitterServiceEndpointPort");
        proxy.setServiceInterface(SpitterService.class);
        proxy.setNamespaceUri("http://web.spittr/");
        return proxy;
    }

wdslDocumentUrl属性标识了远程Web服务定义文件的位置。JaxWsPortProxyFactoryBean将使用这个位置上可用的WSDL来为服务创建代理。由JaxWsPortProxyFactoryBean所生成的代理实现了serviceInterface属性所指定的SpitterService接口。

剩下的三个属性的值通常可以通过查看服务的WSDL来确定。如下所示:


....

    
        
            
        
    

你可能感兴趣的:(Spring实战(十五)-使用远程服务)