zoukankan      html  css  js  c++  java
  • 【RPC】远程接口调用实例 的几种方式比较

    pring中,用JMS搞RPC时会用到:

    • org.springframework.jms.remoting.JmsInvokerServiceExporter
    • org.springframework.jms.remoting.JmsInvokerProxyFactoryBean


    spring在实现RPC的几种方式上都提供了风格一致的支持。
    在这里我打算把几种RPC模型记录下来并作比较。

    • RMI
    • Hessian/Burlap
    • HTTP Invoker
    • JAX-WS

     

     

    RMI


    先从最基本的RMI开始。
    RMI相关的API早在JDK1.1时就有了,我在这里简单描述一下RMI的原生实现(代码可以从别的地方参考)。

    • 声明一个远程接口,接口必须继承java.rmi.Remote,方法需要抛java.rmi.RemoteException
    • 为远程接口提供实现,实现类需要继承UnicastRemoteObject。
    • 或者可以使用rmi相关命令创建skelton和stub。
    • 启动一个RMI注册表并注册。


    如果是spring实现RMI,方法会简单很多。
    我们只需要用到两个类:

    • org.springframework.remoting.rmi.RmiServiceExporter
    • org.springframework.remoting.rmi.RmiProxyFactoryBean


    我简单定义一下接口和实现类:

    package pac.testcase.ws;
    public interfaceMyService {
        public boolean inviteMeIn();
        public String welcome();
    }
    

     

    package pac.testcase.ws.impl;
    import pac.testcase.ws.MyService;
    public classMyServiceImplimplementsMyService{
        public boolean inviteMeIn() {
            return true;
        }
        public String welcome() {
            return "Everybody is welcome!!";
        }
    }
    


    简简单单,不需要继承其他任何东西,非常pojo。

    下面是spring相关配置:

    <bean id="myService"class="pac.testcase.ws.impl.MyServiceImpl" />
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter"
        p:service-ref="myService"
        p:serviceName="welcomeService"
        p:serviceInterface="pac.testcase.ws.MyService"
    />
    


    将我们的pojo导出为RMI服务,在这里我采用默认配置。
    地址在默认情况时如下:

    /**
     * Set the host of the registry for the exported RMI service,
     * i.e. {@code rmi://HOST:port/name}
     * <p>Default is localhost.
     */
    public void setRegistryHost(String registryHost) {
        this.registryHost = registryHost;
    }
    
    /**
     * Set the port of the registry for the exported RMI service,
     * i.e. {@code rmi://host:PORT/name}
     * <p>Default is {@code Registry.REGISTRY_PORT} (1099).
     *@see java.rmi.registry.Registry#REGISTRY_PORT
     */
    public void setRegistryPort(int registryPort) {
        this.registryPort = registryPort;
    }
    


    客户端方面使用RmiProxyFactoryBean,被代理的服务就像一个简单的bean一样:

    <bean id="clientSideService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
        p:serviceUrl="rmi://localhost:1099/welcomeService"
        p:serviceInterface="pac.test.RemoteService"
    />
    


    配置中的pac.test.RemoteService就是那个简单的bean,根据客户端的需要,在这里重新定义一下。

    package pac.test;
    public interface RemoteService {
        public String welcome();
    }
    


    这样就可以在服务端调用了,不用做什么Naming.lookup(serviceUrl)之类的操作,远程调用变得透明。

    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    RemoteService service = (RemoteService)context.getBean("clientSideService");
    System.out.println(service.welcome());
    



    RMI虽然简单高效,但使用RMI会存在一些问题,比如java序列化的版本问题或者防火墙问题(RMI不是基于HTTP的)。

     

     

    Hessian / Burlap 


    Hessian和Burlap,现在进Caucho的网站都几乎见不到这方面的内容了。
    我也不知道有没有人还会用这两个东东,虽然去年出了一个版本,但上一个版本是在2010年。
    刚才在群里问了一下有没有人用,结果还真有人用Hessian,他们是C#和Java做通信。
    Burlap性能更令人头疼,不知道还有没有人提及。
    虽然不知道使用情况如何,但也在这里简单记录一下,拓展一下思维。


    Hessian和Burlap都是由Caucho提供的,Hessian是Resin的一部分。
    这两个东西就像同一件事物的两个部件,比如像这样的枪+链锯?


    Hessian是binary transport protocol,但与RMI不同的是他不是java序列化对象,所以他可以和其他语言的程序通信,比如C++、C#、Python、Ruby什么的。
    Burlap是基于XML的,自然也可以支持很多不同的语言。
    当然,同样地传输内容下,XML的传输量会大一些。
    如果要说有什么好处的话也只有可读性了。


    实在懒得添加依赖再提供原生实现,但他并不复杂。 

    Creating a Hessian service using Java has four steps:

    • Create an Java interface as the public API
    • Create a client using HessianProxyFactory
    • Create the Service implementation class
    • Configure the service in your servlet engine.


    在这里我主要记录一下如何在spring中导出与调用Hessian service。
    正如上面所说,我需要把服务配置到servlet engine中;
    服务端和客户端都需要添加一个dependency:

    <dependency>
        <groupId>com.caucho</groupId>
        <artifactId>hessian</artifactId>
        <version>4.0.33</version>
    </dependency>
    


    正好我这边有个使用springMVC的应用,我就在这个基础上导出Hessian service。

    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    

    简单写一个接口与实现:

    package pac.king.common.rpc;
    public interface MyHessianService {
        public String justHadEnoughParties();
    }
    

     

    package pac.king.common.rpc.impl;
    import pac.king.common.rpc.MyHessianService;
    public classMyHessianServiceImplimplementsMyHessianService{
        public String justHadEnoughParties() {
            return "Please save me..";
        }
    }
    


    我在spring-mvc.xml中曾经做了如下配置,并在*Controller中使用了RequestMapping注解去给URL做映射。
    但这并不妨碍我导出Hessian service再为其映射一个URL:

    <context:component-scan base-package="pac.king.controller"use-default-filters="false">
        <context:include-filter type="annotation"
            expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /service=myHessianService
            </value>
        </property>
    </bean>
    


    导出Hessian service:

    <bean id="myHessianServiceImpl"class="pac.king.common.rpc.impl.MyHessianServiceImpl" />
    <bean id="myHessianService"class="org.springframework.remoting.caucho.HessianServiceExporter"
        p:service-ref="myHessianServiceImpl"
        p:serviceInterface="pac.king.common.rpc.MyHessianService"
    />
    


    现在可以调用了,我需要在客户端声明一个接口(pac.test.HessianService),再用代理去调用:

    <bean id="myHessianClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"
        p:serviceUrl="http://localhost:8080/runtrain/service"
        p:serviceInterface="pac.test.HessianService"
    />
    


    调用:

    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    HessianService service = (HessianService)context.getBean("myHessianClient");
    System.out.println(service.justHadEnoughParties());
    


    console输出:

    对于Burlap,几乎与Hessian的配置没什么区别;
    只需要把HessianServiceExporter改为BurlapServiceExporter,
    并将HessianProxyFactoryBean改为BurlapProxyFactoryBean即可。

    RMI使用Java的序列化,而Hessian/Burlap则为了不同语言之间通信而使用私有的序列化。
    如果我需要基于HTTP,但我并不需要多语言支持,我只想用Java...

     

     

    HttpInvoker


    我应该说这是基于Http的RMI吗?
    虽然看起来两全其美,但也存在让人"遗憾"的地方,
    (事实上不怎么遗憾的说,我曾经做过没有Spring的项目,连持久层框架都是自己实现,做得越久越痛苦...)
    他没有所谓"原生"的实现,他是Spring的一部分,只能在Spring应用中使用。


    Spring为这些RPC通信模型提供的相关类在命名上都有一致,都是:

    • 服务端:*ServiceExporter
    • 客户端:*ProxyFactoryBean


    自然地,HttpInvoker将用到

    • org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
    • org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean


    基于HttpInvoker的服务也像Hessian那样由DispatcherServlet进行分发。
    鉴于很多相同的地方,我打算继续使用在上一篇中用Hessian通信的接口和实现类。

    我几乎不用做任何工作,URL映射也不需要修改,我只需要将服务端的配置修改一下:

    <bean id="myHessianServiceImpl"class="pac.king.common.rpc.impl.MyHessianServiceImpl" />
    <!-- <bean id="myHessianService"class="org.springframework.remoting.caucho.HessianServiceExporter"
        p:service-ref="myHessianServiceImpl"
        p:serviceInterface="pac.king.common.rpc.MyHessianService"
    /> -->
    <bean id="myHessianService"class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"
        p:service-ref="myHessianServiceImpl"
        p:serviceInterface="pac.king.common.rpc.MyHessianService"
    />
    


    相应地,客户端也只需要修改一下class:

    <!-- <bean id="myHessianClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"
        p:serviceUrl="http://localhost:8080/runtrain/service"
        p:serviceInterface="pac.test.HessianService"
    /> -->
    <beanid="myHessianClient"class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"p:serviceUrl="http://localhost:8080/runtrain/service"p:serviceInterface="pac.test.HessianService"
    />
    


    这样就保证了效率又解决了防火墙的问题,
    后来我听有人说,他们用Hessian做跨语言通信时,基于Http这个特征并不能解决防火墙的问题。
    不知道他们具体情况如何,似乎没说到一块儿...


    看了Hessian之后突然感觉Web service这种东西好笨重啊(虽然也有一些方法可以克服部分问题)。
    既然有Hessian,那为什么还要用Web service这种东西呢?
    我突然开始怀疑起他存在的意义。
    搜了一下,结果都是比较RPC通信模型的效率,没有人说他们为什么还要用(都应该有存在的意义吧)...
    如果仅仅是效率的话都用Hessian不就得了?
    带着这个问题我逛了逛stackoverflow,然后我得到了下面几种答案。

    • 多数人手中拿着锤子的时候,他们倾向于将所有问题都当作钉子,他们通常不会试着去寻找别的工具。导致Web service泛滥的原因也是这个。
    • 我觉得你应该重新看看Web service的优势(结果有人说了跨语言和SOA...果然关键还是相对什么做比较...)
    • Web service比那些non-xml的通信方式慢?这种相对的速度问题更多的取决于业务需求和你自己的代码实现(这个说法也同样适用于反射)。


    最后我还是没有得到让我满意的答案,倒是复习了Web service...
    很多类似场景下人们都将Web service视为"standard"option。 既然如此...那就看看Web service吧。

     

     

    JAX-WS


    看来使用web service是不可避免的。
    我曾对这个有些抵触,因为他给我印象总是麻烦+慢(后来虽然方便了许多,但还是很慢)。
    然后再去搜索"advantages of web service"什么的试着再让自己接受他。
    简单记录一下如何用Spring导出Endpoint。

    假设我想在有一个Spring应用,我需要把一个Pojo或者一部分方法导出为Web Service。
    但这会有一个问题——Endpoint的生命周期是由JAX-WS runtime来管理(The lifecycle of such an endpoint instance will be managed by the JAX-WS runtime),
    Spring context中的Bean无法autowire到Endpoint中,而我要导出的那些东东都用到了Spring管理的Bean。


    对此,我们有两个解决方法:

    • org.springframework.web.context.support.SpringBeanAutowiringSupport
    • JaxWsServiceExporter


    我上面括号中的那段话是引用的SpringBeanAutowiringSupport的javaDoc。
    使用该类的典型案例就是bean注入到JAX-WS endpoint类中(人家注释上写的),任何一个生命周期不是由Spring来管理的场景都可以用到他。
    而我们只需要继承这个类,也就是说创建一个实例时会调用父类的无参构造方法,我们来看看他的构造方法:

    /**
     * This constructor performs injection on this instance,
     * based on the current web application context.
     * <p>Intended for use as a base class.
     *@see #processInjectionBasedOnCurrentContext
     */
    public SpringBeanAutowiringSupport() {
        processInjectionBasedOnCurrentContext(this);
    }
    
    /**
     * Process {@code@Autowired} injection for the given target object,
     * based on the current web application context.
     * <p>Intended for use as a delegate.
     *@param target the target object to process
     *@see org.springframework.web.context.ContextLoader#getCurrentWebApplicationContext()
     */
    public static void processInjectionBasedOnCurrentContext(Object target) {
        Assert.notNull(target, "Target object must not be null");
        WebApplicationContext cc = ContextLoader.getCurrentWebApplicationContext();
        if (cc != null) {
            AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
            bpp.setBeanFactory(cc.getAutowireCapableBeanFactory());
            bpp.processInjection(target);
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Current WebApplicationContext is not available for processing of " +
                        ClassUtils.getShortName(target.getClass()) + ": " +
                        "Make sure this class gets constructed in a Spring web application. Proceeding without injection.");
            }
        }
    }
    


    那就试试看:

    @Service
    @WebService(serviceName="testMyService")
    public classMyServiceEndpointextendsSpringBeanAutowiringSupport{
        @Autowired
        MyService myService;
    
        @WebMethod
        public String sayHiFarAway(String name){
            return myService.sayHiTo(name);
        }
    }
    

     

    接着发布一下:

    ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:applicationContext*.xml");
    Endpoint.publish("http://localhost:8080/myservices", (MyServiceEndpoint)context.getBean(MyServiceEndpoint.class));
    


    调用:

    javax.xml.ws.Service service = javax.xml.ws.Service.create(url, new QName("http://endpoint.king.pac/","testMyService"));
    QName q = new QName("http://endpoint.king.pac/","MyServiceEndpointPort");
    MyClientService client = service.getPort(q,MyClientService.class);
    System.out.println(client.sayHiFarAway("King"));
    


    写一个EndPoint还要继承和业务无关的类,让人不爽...而且发布和调用都麻烦。
    那试试SimpleJaxWsServiceExporter,只需要简单的配置就可以导出一个EndPoint。
    但是他也有需要注意的地方,引用一下该类的javaDoc:

    Note that this exporter will only work if the JAX-WS runtime actually supports publishing with an address argument, i.e. if the JAX-WS runtime ships an internal HTTP server. This is the case with the JAX-WS runtime that's inclued in Sun's JDK 1.6 but not with the standalone JAX-WS 2.1 RI.


    SimpleJaxWsServiceExporter会自动detect所有被WebService注解的类,因此只需要在配置中声明即可;此时Endpoint地址直接使用默认的localhost:8080:

    <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter" />
    


    接着改善一下客户端的调用,使用JaxWsPortProxyFactoryBean:

    <bean id="clientSide" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"
        p:wsdlDocumentUrl="http://localhost:8080/testMyService?wsdl"
        p:serviceName="testMyService"
        p:portName="MyServiceEndpointPort"
        p:serviceInterface="pac.king.endpoint.MyClientService"
        p:namespaceUri="http://endpoint.king.pac/"
    />
    


    这样就可以像使用普通bean一样使用service了:

    MyClientService client = (MyClientService)context.getBean("clientSide");
    System.out.println(client.sayHiFarAway("King"));
    


    用起来方便了不少,但仍然无法改变一个事实:

    Web service requests are larger than requests encoded with a binary protocol.

    还有就是http/https的问题。
    如果使用不当,我可能需要多做些工作去处理HTTP做不来的事情,而这时候RMI又非常适合。

     

  • 相关阅读:
    Oracle11g 干净卸载
    linux中的帮助命令
    Linux Samba文件共享服务,安装与案例配置
    linux文件系统相关命令(df/du/fsck/dumpe2fs)
    虚拟机VMware下CentOS6.6安装教程图文详解
    vnc连接远端linux服务器
    mysql57重新安装后无法再次启动mysql57服务“本地计算机上的MySQL服务启动后停止。某些服务在未由其他服务或程序使用时将自动。”--解决方法
    (二)分布式数据库tidb-事务
    Centos 6下使用cmake编译安装MariaDB
    MySQL数据库基本知识
  • 原文地址:https://www.cnblogs.com/bruce1992/p/13882267.html
Copyright © 2011-2022 走看看