zoukankan      html  css  js  c++  java
  • 通过Spring使用远程访问和web服务

    Spring提供类用于集成各种远程访问技术。这种对远程访问的支持可以降低你在用POJO实现支持远程访问业务时的开发难度。目前,Spring提供对下面四种远程访问技术的支持:

    远程方法调用(RMI)。通过使用RmiProxyFactoryBean和RmiServiceExporter,Spring支持传统的RMI(使用java.rmi.Remote interfaces 和 java.rmi.RemoteException)和通过RMI调用器(可以使用任何Java接口)的透明远程调用。

    Spring的HTTP调用器。Spring提供一种特殊的远程调用策略支持任何Java接口(象RMI调用器一样),它允许Java序列化能够通过HTTP传送。对应的支持类是HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter。

    Hessian。通过使用HessianProxyFactoryBean和HessianServiceExporter,你可以使用Caucho提供的轻量级基于HTTP的二进制协议透明地提供你的业务。

    Burlap。Burlap是基于XML的,它可以完全代替Hessian。Spring提供的支持类有BurlapProxyFactoryBean和BurlapServiceExporter。

    JAX RPC (TODO).

    当讨论Spring对远程访问的支持时,我们将使用下面的域模型和对应的业务:

    // Account domain object
    public class Account implements Serializable{
      private String name;

      public String getName();
      public void setName(String name) {
        this.name = name;
      }
    }
       

    // Account service
    public interface AccountService {

      public void insertAccount(Account acc);
     
      public List getAccounts(String name);
    }
       

    // ... and corresponding implement doing nothing at the moment
    public class AccountServiceImpl implements AccountService {

      public void insertAccount(Account acc) {
        // do something
      }
     
      public List getAccounts(String name) {
        // do something
      }
    }
       

    我们先演示使用RMI向远程客户提供业务,并且会谈到使用RMI的缺点。然后我们将继续演示一个Hessian的例子。

    16.2. 使用RMI提供业务
    使用Spring的RMI支持,你可以透明地通过RMI提供你的业务。在配置好Spring的RMI支持后,你会看到一个和远程EJB类似的配置,除了没有对安全上下文传递和远程事务传递的标准支持。当使用RMI调用器时,Spring对这些额外的调用上下文提供捕获,所以你可以插入你的安全框架或安全信任逻辑。

    2.1. 使用RmiServiceExporter提供业务
    使用RmiServiceExporter,我们可以将AccountServer对象作为RMI对象输出接口。这个接口可以使用RmiProxyFactoryBean访问,或使用简单RMI把该接口当作传统RMI业务来访问。RmiServiceExporter支持通过RMI调用器提供任何非RMI业务。

    当然,我们首先得在Spring的BeanFactory中设置我们的业务:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>
        

    接下来,我们使用RmiServiceExporter提供我们的业务:

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
     <!-- does not necessarily have to be the same name as the bean to be exported -->
     <property name="serviceName"><value>AccountService</value></property>
     <property name="service"><ref bean="accountService"/></property>
     <property name="serviceInterface"><value>example.AccountService</value></property>
     <!-- defaults to 1099 -->
     <property name="registryPort"><value>1199</value></property>
    </bean>
        正如你看到的,我们更换了RMI注册的端口。通常,你的应用服务器会维护RMI注册,我们最好不要干扰它。业务名被用来绑定业务。所以现在,业务就绑定在rmi://HOST:1199/AccountService上。我们将在客户端使用URL来连接业务。

    注意:我们漏了一个属性,就是servicePort属性,它缺省值为0。这个意味着该业务使用匿名端口通讯。当然你也可以指定一个端口。

    2.2. 客户端连接业务
    我们的客户端是一个使用AccountService管理账户的简单对象:

    public class SimpleObject {
      private AccountService accountService;
      public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
      }
    }
        

    为了在客户端连接业务,我们建立另一个bean工厂,它包含这个简单对象和业务连接的配置信息:

    <bean class="example.SimpleObject">
     <property name="accountService"><ref bean="accountService"/></bean>
    </bean>

    <bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
     <property name="serviceUrl"><value>rmi://HOST:1199/AccountService</value></property>
     <property name="serviceInterface"><value>example.AccountService</value></property>
    </bean>
        这就是我们在客户端访问远程账户业务所需要做的。Spring透明地创建一个调用器,通过RmiServiceExporter远程提供账户业务。在客户端,我们使用RmiProxyFactoryBean来使用该业务。

    3. 使用Hessian或Burlap通过HTTP远程调用业务
    Hessian提供了一个基于HTTP的二进制远程协议。它由Caucho创建,更多有关Hessian的信息可以访问http://www.caucho.com

    3.1. 为Hessian建立DispatcherServlet
    Hessian使用一个特定的servlet来通过HTTP通讯。使用Spring的DispatcherServlet概念,你可以很容易地创建这样的servlet来提供你的业务。首先我们必须在你的应用中创建一个新的servlet(下面来自web.xml):

    <servlet>
     <servlet-name>remote</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
     <load-on-startup>1</load-on-startup>
    </servlet>
        

    你可能熟悉Spring的DispatcherServlet概念,如果是的话,你得在WEB-INF目录下建立一个应用上下文,remote-servlet.xml 。这个应用上下文会在下一节中使用。

    3.2. 使用HessianServiceExporter提供你的bean
    在这个新的应用上下文remote-servlet.xml中,我们将创建一个HessianServiceExporter来输出你的业务:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>

    <bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service"><ref bean="accountService"/></property>
        <property name="serviceInterface">
            <value>example.AccountService</value>
        </property>
    </bean>

    现在我们准备在客户端连接这个业务。我们使用BeanNameUrlHandlerMapping,就不需要指定处理器映射将请求(url)映射到业务上,因此业务提供在http://HOST:8080/AccountService上。

    3.3. 客户端连接业务
    我们在客户端使用HessianProxyFactoryBean来连接业务。和RMI例子中的原则一样。我们将创建一个单独的bean工厂或应用上下文,在SimpleObject使用AccountService来管理账户的地方将会提到下列bean:

    <bean class="example.SimpleObject">
        <property name="accountService"><ref bean="accountService"/></property>
    </bean>

    <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
     <property name="serviceUrl"><value>http://remotehost:8080/AccountService</value></property>
     <property name="ServiceInterface"><value>example.AccountService</value></property>
    </bean>
    就是这样简单。

    3.4. 使用Burlap
    我们不在这里讨论Burlap,它只不过是Hessian的基于XML实现。因为它和上面的Hessian的例子以相同的方式配置。只要你把Hessian替换成Burlap就可以了。

    3.5. 在通过Hessian或Burlap输出的业务中应用HTTP基本认证
    Hessian和Burlap的优点之一就是我们能很容易地应用HTTP认证,因为两者都是基于HTTP的协议。例如,普通的HTTP服务器安全机制可以很容易地通过使用web.xml安全功能来应用。通常,你不会为每个用户都建立不同的安全信任,而是在Hessian/Burlap的ProxyFactoryBean中定义可共享的信任(和JDBC DataSource相类似)。

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
     <property name="interceptors">
      <list>
       <ref bean="authorizationInterceptor"/>
      </list>
     </property>
    </bean>

    <bean id="authorizationInterceptor"
     class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
     <property name="authorizedRoles">
      <list>
       <value>administrator</value>
       <value>operator</value>
      </list>
     </property> 
    </bean>
        

    这个例子中,我们用到了BeanNameUrlHandlerMapping,并设置了一个拦截器,它只允许管理员和操作员调用这个应用上下文中的bean。

    注意:当然,这个例子并没有演示灵活的安全设施。如果考虑更灵活的安全设置,可以去看看Acegi Security System,http://acegisecurity.sourceforge.net

    4. 使用HTTP调用器输出业务
    和Burlap和Hessian使用自身序列化机制的轻量级协议相反,Spring HTTP调用器使用标准Java序列化机制来通过HTTP输出业务。如果你的参数或返回值是复杂类型,并且不能通过Hessian和Burlap的序列化机制序列化,HTTP调用器就很有优势(参阅下一节,选择远程技术时的考虑)。

    实际上,Spring可以使用J2SE提供的标准功能或Commons的HttpClient来实现HTTP调用。如果你需要更先进,更好用的功能,就使用后者。你可以参考jakarta.apache.org/commons/httpclient。

    4.1. 输出业务对象
    为业务对象设置HTTP调用器和你在Hessian或Burlap中使用的方式类似。就象Hessian提供HessianServiceExporter,Spring的HTTP调用器提供了org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter。为了输出AccountService,使用下面的配置:

        <bean name="/AccountService" class="org.sprfr.remoting.httpinvoker.HttpInvokerServiceExporter">
            <property name="service"><ref bean="accountService"/></property>
            <property name="serviceInterface">
                <value>example.AccountService</value>
            </property>
     </bean>
     

    4.2. 在客户端连接业务
    同样,从客户端连接业务与你使用Hessian或Burlap时做的类似。使用代理,Spring可以将你的调用翻译成HTTP 的POST请求到指向输出业务的URL。

     
     <bean id="httpInvokerProxy" class="org.sprfr.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
      <property name="serviceUrl">
       <value>http://remotehost:8080/AccountService</value>
      </property>
      <property name="serviceInterface">
       <value>example.AccountService</value>
      </property>
     </bean>
     

    就象上面说的一样,你可以选择使用你想使用的HTTP客户端。缺省情况下,HttpInvokerPropxy使用J2SE的HTTP功能,但是你也可以通过设置httpInvokerRequestExecutor属性选择使用Commons HttpClient:

    <property name="httpInvokerRequestExecutor">
     <bean class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"/>
    </property>
     

    5. 在选择这些技术时的一些考虑
    这里提到的每种技术都有它的缺点。你在选择这些技术时,应该仔细考虑你的需要,你所输出的业务和你在远程访问时传送的对象。

    当使用RMI时,通过HTTP协议访问对象是不可能的,除非你用HTTP包裹RMI流。RMI是一种很重的协议,因为他支持完全的对象序列化,这样的序列化在要求复杂数据结构在远程传输时是非常重要的。然而,RMI-JRMP只能绑定到Java客户端:它是一种Java-to-Java的远程访问的方案。

    如果你需要基于HTTP的远程访问而且还要求使用Java序列化,Spring的HTTP调用器是一个很好的选择。它和RMI调用器使用相同的基础设施,仅仅使用HTTP作为传输方式。注意HTTP调用器不仅只能用在Java-to-Java的远程访问,而且在客户端和服务器端都必须使用Spring。(Spring为非RMI接口提供的RMI调用器也要求客户端和服务器端都使用Spring)

    当在异构环境中,Hessian和Burlap就非常有用了。因为它们可以使用在非Java的客户端。然而,对非Java支持仍然是有限制的。已知的问题包括含有延迟初始化的collection对象的Hibernate对象的序列化。如果你有一个这样的数据结构,考虑使用RMI或HTTP调用器,而不是Hessian。

    最后但也很重要的一点,EJB优于RMI,因为它支持标准的基于角色的认证和授权,以及远程事务传递。用RMI调用器或HTTP调用器来支持安全上下文的传递是可能的,虽然这不是由核心Spring提供:而是由第三方或在定制的解决方案中插入拦截器来解决的。

    Spring框架对远程访问技术提供了很好的集成机制,Spring目前支持的技术包括:
    1. Remote Method Invocation (RMI)。通过 RmiProxyFactoryBean和RmiServiceExporter,Spring支持以java.rmi.Remote和java.rmi.RemoteException 定义的传统RMI接口,提供经由RMI的透明访问;

    2.Spring内置的HTTP invoker远程调用机制。Spring开发团队意识到在RMI服务和基于HTTP的服务(如Hessian和Burlap)之间的空白。一方面,RMI使用Java标准的对象序列化,但很难穿越防火墙;另一方面,Hessian/Burlap能很好地穿过防火墙工作,但使用自己私有的一套对象序列化机制。因此,Spring的HTTP invoker应运而生。HTTP invoker是一个全新的远程调用模型,作为Spring框架的一部分,来执行基于HTTP的远程调用(可穿越防火墙),并使用Java的序列化机制(从而降低开发工作的难度)。当参数或返回值是复杂类型,并且不能通过Hessian的序列化机制序列化时,Http invoker就很有优势。 Spring中相应的支持类是HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter;

    3.Hessian。作为一个轻量级分布式组件技术,Hessian采用了自己定义的二进制协议,这似乎与目前业界大量应用SOAP的趋势背道而驰,但对于一些简单的远程访问服务,Hessian足够应对。在Spring中,通过HessianProxyFactoryBean和HessianServiceExporter两个类,我们可以透明地将需要对外提供的服务发布出去,当然这里依旧采用Caucho这家来自California的公司的二进制协议;

    4.Burlap。Burlap可以说是Hessian的XML版本。Spring中的支持类包括BurlapProxyFactoryBean和BurlapServiceExporter;

    5.JAX RPC(Java APIs for XML-based Remote Procedure Call)。 Spring支持通过JAX RPC访问的Web Service。事实上,Spring对于Web Service的封装相当简单,Spring提供了一个代理工厂Bean:JaxRpcPortProxyFactoryBean,能让你在应用中无缝集成一个web sevice,作为另一个Bean的协作者,JaxRpcPortProxyFactoryBean类用JAX-RPC来访问远程Web Service。

    6.JMS。Spring中对应的支持类包括JmsInvokerServiceExporter和JmsInvokerProxyFactoryBean。

        这里,我们着重讨论一下Spring对于Web Service的支持。这种支持包括两方面:
        第一,基于JAX-RPC发布Web Service。
        第二,访问Web Service。

        对于需要发布为Web Service的业务逻辑bean,只需要继承Spring的ServletEndpointSupport 类即可。假设有如下业务接口以及相应的实现类:
    public interface SayHello
    {
      public String sayHello(String who);
    }
    public class SayHelloImpl implements SayHello
    {
      public String sayHello(String who)
      {
          return "Hello,"+who;
      }
    }
    如果需要将SayHelloImpl 发布为Web Service,则需要编写一个Web Service类,让该类实现SayHello接口,并继承ServletEndpointSupport类。示例如下:

    public class SayHelloEndPoint extends ServletEndpointSupport implements SayHello
    {
         
      private SayHello sh;

      protected void onInit()
      {
          this.sh = (SayHello) getWebApplicationContext().getBean("sayHello");
      }

      //将业务逻辑bean的业务方法公开发布为WebService
      public String sayHello(String who) throws RemoteException
      {
          return sh.sayHello(who);
      }
    }
       
        而后,定义Spring的配置文件,在配置文件中部署业务bean,示例如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
      "http://www.springframework.org/dtd/spring-beans.dtd">

    <beans>
      <bean id="sayHello" class="com.ws.SayHelloImpl">
            <property name=>
      </bean >
    </beans>

        然后修改web.xml文件,让AxisServlet拦截某些请求 ,示例如下:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD WebApplication 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

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

        <!-- 定义AxisServlet-->
      <servlet>
          <servlet-name>AxisServlet</servlet-name>
          <servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
      </servlet>

          <!-- 映射AxisServlet-->
          <servlet-mapping>
                <servlet-name>AxisServlet</servlet-name>
                <url-pattern>/axis/*</url-pattern>
          </servlet-mapping>
    </web-app>

        在上述配置文件中,可以看到所有匹配/axis/*模式的请求都由AxisServlet处理。即:Spring发布的Web Service都在放在axis路径下。
    然后编写WSDD文件,或者使用工具生成。
    server-config.wsdd 放在/WEB-INF/目录下

    WSDD文件如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <!-- wsdl的根元素,包含schema等信息-->
    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
    <!-- wsdl的全局配置-->
          <globalConfiguration>
                <parameter name="adminPassword" value="admin"/>
                <parameter name="sendXsiTypes" value="true"/>
                <parameter name="sendMultiRefs" value="true"/>
                <parameter name="sendXMLDeclaration" value="true"/>
                <parameter name="axis.sendMinimizedElements" value="true"/>
                <requestFlow>
                      <handler type="java:org.apache.axis.handlers.JWSHandler">
                            <parameter name="scope" value="session"/>
                      </handler>
                      <handler type="java:org.apache.axis.handlers.JWSHandler">
                            <parameter name="scope" value="request"/>
                            <parameter name="extension" value=".jwr"/>
                      </handler>
                </requestFlow>
          </globalConfiguration>
          <handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/>
          <handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/>
          <handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/>
          <!-- 定义WebService的管理台-->
          <service name="AdminService" provider="java:MSG">
                <parameter name="allowedMethods" value="AdminService"/>
                <parameter name="enableRemoteAdmin" value="false"/>
                <parameter name="className" value="org.apache.axis.utils.Admin"/>
                <namespace>http://xml.apache.org/axis/wsdd/</namespace>
          </service>
          <!-- 定义自己的WebService-->
          <service name="SayHelloService" provider="java:RPC">
                <parameter name="allowedMethods" value="*"/>
                <!-- 定义WebService的实现类-->
                <parameter name="className" value="com.ws.SayHelloEndpoint"/>
          </service>
          <!-- 定义WebSerivice的系统服务。-->
          <service name="Version" provider="java:RPC">
                <parameter name="allowedMethods" value="getVersion"/>
                <parameter name="className" value="org.apache.axis.Version"/>
          </service>
          <transport name="http">
                <requestFlow>
                      <handler type="URLMapper"/>
                      <handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/>
                </requestFlow>
          </transport>
          <transport name="local">
                <responseFlow>
                      <handler type="LocalResponder"/>
                </responseFlow>
          </transport>
    </deployment>

    经过上述这些步骤,可将部署在Spring的普通业务逻辑bean发布成Web Service。


      Spring发布的Web Service是标准的基于SOAP协议的Web Service。因此可以使用标准客户端访问,也可以利用IoC容器来管理Web Service,利用IoC容器时,必须借助于JaxRpcPortProxyFactoryBean。
    使用标准客户端访问Web Service非常简单,只需要获得WebService的URL以及方法名即可。根据上面的发布,知道Web Service的URL如下:http://localhost:8080/axis-spring/axis/SayHelloService,则客户端代码示例如下:

    public class WebServiceClient
    {
      public static void main(String args[])
      {
          System.out.println("开始调用WebService");    
          try
          {
              //WebService所在的URL
              String endpoint = "http://localhost:8080/axis-spring/axis/SayHelloService";
              //创建Service对象,Service对用用于创建Call对象
              Service service = new Service();
              //创建Call对象,Call对象用于调用服务
              Call call = (Call)service.createCall();
            //为Call对象设置WebService的url
              call.setTargetEndpointAddress(new java.net.URL(endpoint));
              //为Call对象设置调用的方法名
              call.setOperationName("sayHello");
              //调用WebService的方法,并获得返回值
              String s = (String)call.invoke(new Object[] {"刘德华"});

              System.out.println(s);
          }
          catch (Exception e)
          {
              System.out.println(e.toString());
          }
          System.out.println("调用Web Service正常结束");    
    }
    }

    而如果利用IoC容器管理WebService,必须借助于JaxRpcPortProxyFactoryBean类,该类是个工厂bean,与所有的工厂bean一样,对该bean的请求将返回它的产品。
    配置JaxRpcPortProxyFactoryBean,只需提供WebService的URL、命名空间等必需信息,将可以返回Web Service服务。在Spring配置文件中JaxRpcPortProxyFactoryBean的定义如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans>
          <!-- 配置测试bean-->
      <bean id="test" class="com.ws.Test">
          <property name="sayHello">
              <ref local="sayHelloService"/>
          </property>
    </bean>
    <!-- 配置WebService bean-->
      <bean id="sayHelloService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
                <!-- 配置WebService实现的接口-->
          <property name="serviceInterface">
              <value>com.ws.SayHello</value>
          </property>
                <!-- 配置WebService的wsdl的URL-->
          <property name="wsdlDocumentUrl">
              <value>http://localhost:8080/axis-spring/axis/SayHelloService?wsdl</value>
          </property>
                <!-- 配置WebService的命名空间uri-->
          <property name="namespaceUri">
              <value>http://localhost:8080/axis-spring/axis/SayHelloService</value>
          </property>
                <!-- 配置WebService的服务名-->
          <property name="serviceName">
              <value>SayHelloService</value>
          </property>
                <!-- 配置WebService的portName-->
          <property name="portName">
              <value>SayHelloService"</value>
          </property>
      </bean>
    </beans>


    (声明:本文部分内容来自《Spring Reference》和《Spring 2.0宝典》)

  • 相关阅读:
    Terminologies in MVC: Part 2 (Razor Engine Syntax vs Web Form)
    what is diff. b/w app state & session state
    ASP.NET Web Pages (Razor) FAQ
    _AppStart.cshtml 和 _PageStart.cshtml的妙用
    系统编程--信号
    系统编程--进程间通信
    系统编程--进程
    系统编程--标准IO
    系统编程--文件IO
    网络--路由表&IP选路
  • 原文地址:https://www.cnblogs.com/xiejava/p/15171475.html
Copyright © 2011-2022 走看看