1. 程式人生 > >基於Spring實現的Rmi, HttpInvoker, Hessian, Web services

基於Spring實現的Rmi, HttpInvoker, Hessian, Web services

基於Spring實現的Rmi, HttpInvoker, Hessian, Web services

因為要寫Ronda的關係,所以就想了解下應該怎麼實現Rmi協議。普通的Rmi必須要繼承Remote
方法,但是在框架裡肯定不會為每個類都繼承Remote。

那麼就來看看官網上怎麼說的,然後再分析一下為什麼這麼實現就可以。

Spring使用不同的技術支援多種遠端呼叫。目前支援四種:

  • Rmoete Method Invocation. 通過使用RmiProxyFactoryBeanRmiServiceExporter支援了傳統的RMI和透明的RMI遠端呼叫。這裡說傳統的是繼承了Remote

    類和丟擲異常java.rmi.RemoteException,後一種說的是隻要有介面就可以。

  • Spring Http invoker. 只要類實現了介面,那麼Spring就支援通過Http進行序列化傳輸。使用到的類是Http
    InvokerProxyFactoryBean
    HttpInvokerServiceExporter

  • Hessian. 使用HessianProxyFactoryBeanHessianServiceExporter你可以使用Hessian協議進行傳輸。

  • Burlap. Burlap是對於Hessian的另外一個格式選擇,即XML。使用BurlapProxyFactoryBean

    BurlapServiceExporter

  • JAX RPC. Spring通過JAX-RPC提供對web service的支援

  • JMS. 使用JmsInvokerServiceExporterJmsInvokerProxyFactoryBean進行實現

從上面的簡介我們可以看到,Spring主要使用ServiceExporter進行服務的匯出,使用ProxyFactoryBean進行生成代理的服務單物件,然後實現遠端呼叫。

RMI

核心就是講Service匯出來,然後在客戶端生成一個Service的代理物件

Server端:

<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"/> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> <!-- defaults to 1099 --> <property name="registryPort" value="1199"/> </bean>

Client端:

<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
    <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

Hessian

Hessian協議使用通過HTTP進行通訊。所以使用DispatcherServlet進行暴露服務.

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

<servlet-mapping>
    <servlet-name>remoting</servlet-name>
    <url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
暴露服務
<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="accountService"/>
  <property name="serviceInterface" value="example.AccountService"/>
</bean>

客戶端

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

<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

HTTP

HTTP其實是使用自己的協議進行HTTP互動,而Hessian、Burlap使用自己的序列化協議。

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

Web services

使用JAX-RPC進行暴露

Spring提供一個ServletEndPointSupport類來方便進行暴露web service服務,業務層的類需要繼承ServletEndpointSupport.

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);
    }
}

訪問web service

spring有兩個工廠bean用來建立web service的代理: LocalJaxRpcServiceFactoryBeanJaxRpcPortProxyFactoryBean。前面的一個類返回的JAX-RPC服務,後一個返回全面的實現業務介面的類。

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

現在可以正常使用service,不過要處理RemoteException. 可以通過配置一個non-RMI的介面來去除。

客戶端註冊Bean Mappings

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));
   }
}

註冊自定義的Handler

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;
    }
}
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);
    }
}
<bean id="accountWebService" class="example.AccountHandlerJaxRpcPortProxyFactoryBean">
    <!-- ... -->
</bean>
Exposing web services using XFire

新增servlet

<servlet>
  <servlet-name>xfire</servlet-name>
  <servlet-class>
    org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
</servlet>
<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 mapping

<beans>

  <bean name="/Echo" class="org.codehaus.xfire.spring.remoting.XFireExporter">
    <property name="serviceInterface" value="org.codehaus.xfire.spring.Echo"/>
    <property name="serviceBean">
        <bean class="org.codehaus.xfire.spring.EchoImpl"/>
    </property>
    <!-- the XFire bean is defined in the xfire.xml file -->
    <property name="xfire" ref="xfire"/>
  </bean>

</beans>

JMS

通過JMS進行互動。 Spring支援JMS是很基本的,在同一個執行緒中進行通訊,不支援事務。

Server和Client都需要的介面

package com.foo;

public interface CheckingAccountService {

   void cancelAccount(Long accountId);
}

Server端實現:

package com.foo;

public class SimpleCheckingAccountService implements CheckingAccountService {

   public void cancelAccount(Long accountId) {
      System.out.println("Cancelling account [" + accountId + "]");
   }
}

Server和Client都有的配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

   <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
      <property name="brokerURL" value="tcp://ep-t43:61616"/>
   </bean>

   <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
      <constructor-arg value="mmm"/>
   </bean>

</beans>

Server端的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

   <bean id="checkingAccountService"
        class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
      <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
      <property name="service">
         <bean class="com.foo.SimpleCheckingAccountService"/>
      </property>
   </bean>

   <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
      <property name="connectionFactory" ref="connectionFactory"/>
      <property name="destination" ref="queue"/>
      <property name="concurrentConsumers" value="3"/>
      <property name="messageListener" ref="checkingAccountService"/>
   </bean>

</beans>

Client端的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

   <bean id="checkingAccountService"
        class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
      <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
      <property name="connectionFactory" ref="connectionFactory"/>
      <property name="queue" ref="queue"/>
   </bean>

</beans>

簡單說下RMI其實還是走Java的那一套,就是判斷你的service有沒有繼承Remote介面,如果沒有就給你生成一個繼承的介面暴露出去。