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中,远程服务被代理,所以它们能够像其他Spring bean一样被装配到客户端代码中

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

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

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

使用远程导出器将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<Spittle> getRecentSpittles(int count);
    void saveSpittle(Spittle spittle);
    void saveSpitter(Spitter spitter);
    Spitter getSpitter(long id);
    void startFollowing(Spitter follower, Spitter followee);
    List<Spittle> getSpittlesForSpitter(Spitter spitter);
    List<Spittle> getSpittlesForSpitter(String username);
    Spitter getSpitter(String username);
    Spittle getSpittleById(long id);
    void deleteSpittle(long id);
    List<Spitter> getAllSpitters();
}

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

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

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配置

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="server" />

    <bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="SpitterServer" />
        <property name="service" ref="spitterServerImpl" />
        <property name="serviceInterface"
                  value="server.SpitterServer" />
    </bean>
</beans>

需要注意的是,由于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

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="client" />
    <bean id="clientInvoke" class="client.ClientInvoke" />
    <bean class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://192.168.68.115:1099/SpitterServer"></property>
        <property name="serviceInterface" value="client.SpitterServer"></property>
    </bean>
</beans>

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

RmiProxyFactoryBean生成一个代理对象,该对象代表客户端来负责与远程的RMI服务进行通信。客户端通过服务的接口与代理进行交互,就如同远程服务就是一个本地的POJO

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

@Autowired
SpitterService spitterService;

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

public List<Spittle> 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服务是不同的。

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:

<servlet-mapping>
    <servlet-name>spitter</servlet-name>
    <url-patten>*.service</url-patten>
</servlet-mapping>

如果在 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配置,如下:

<bean class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl" value="http://localhost:8080/Spitter/spitter.service"></property>
    <property name="serviceInterface" value="client.SpitterServer"></property>
</bean>

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

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的方法调用。

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填充了相同的位置。

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服务交互的代理。所创建的代理实现了服务接口,如下图。

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来确定。如下所示:

<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
xmlns:wsp="http://www.w3.org/ns/ws-policy" 
xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" 
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" 
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
xmlns:tns="http://web.spittr/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns="http://schemas.xmlsoap.org/wsdl/" 
targetNamespace="http://web.spittr/" name="spitterService">
....

    <service name="spitterService">
        <port name="SpitterServiceEndpointPort" 
               binding="tns:SpitterServiceEndpointPortBinding">
            <soap:address location="http://localhost:8092/services/spitterService"/>
        </port>
    </service>
</definitions>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容