BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Java Remoting远程服务(上)

| 作者 李湃 关注 0 他的粉丝 发布于 2012年1月3日. 估计阅读时间: 22 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

今天我们来聊聊Java远程服务的解决方案。Java分布式远程服务的解决方案,近几年在互联网应用越来越普及。我们简单分析下,形成这种格局的背景。

从无到有开发一个产品的时候,如果技术框架没有积累,那么代码的实现会比较随意,很多时候前端web层耦合了很多后端DAL层的代码。接下来,随着产品越来越多,每个产品的技术实现都会有很多重复代码。这就给后期的维护和升级带来了不便(比如针对某个服务做缓存优化或者日志处理,代价会非常高)。服务模块化呼之欲出!

服务模块化,就意味着代码的实现架构不再是Web层与DAL层的简单关系了。很多相似的业务会抽象为一个分布式服务,Java语言支持多种远程服务的实现,像EJB、 WebService、 RMI、Hessian等等。下面我们通过一个具体的例子来简述这些技术的使用以及在实践中如何权衡各种技术的适用场景。

用例:提供一个分布式的动物中心服务,提供猴子的名字。

1. RMI

RMI是Java提供的分布式应用API,远程方法调用RPC的实现。它的宗旨是,某个JVM下的对象可以调用其他JVM下的远程对象。RMI的底层实现是构建于TCP协议之上,将远程对象绑定具体的端口号,监听客户端的请求;客户端与远程对象的通信当中,依赖于预定义的接口,即RMI会生成一个本地Stub代理类,每次客户端调用远程对象的时候,Stub代理类会初始化参数、启动远程连接、将参数进行编组(marshal),通过网络传输送往服务器端,并对返回的结果进行反编组(unmarshal)。对于客户端调用方来讲,RMI隐藏了对象序列化和网络传输的实现细节。

图一:RMI的调用机制[1]

图一描述了RMI调用的大体步骤:首先RMI Server会通过请求RMIRegistry(远程对象联机注册服务)绑定一个远程对象,对象的元数据信息放在一个已有的Web Server上面;然后RMI Client会发送请求到RMIRegistry获取远程对象的地址,并远程调用该对象的方法。

下面我们使用RMI来实现之前所描述的用例。

  1. 接口类IAnimalService.java
  2. import java.rmi.Remote;
    import java.rmi.RemoteException;
    public interface IAnimalService extends Remote {
        String getMonkeyName() throws RemoteException;
    }
    
  3. 实现类AnimalServiceImp.java
  4. import java.rmi.RemoteException;
    import java.rmi.registry.Registry;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.server.UnicastRemoteObject;
    public class AnimalServiceImp implements IAnimalService {
        public AnimalServiceImp() {
        }
    
        @Override
        public String getMonkeyName() throws RemoteException {
            return "I'm Jacky";
        }
    }
  5. 服务端AnimalServer.java
  6. import java.rmi.RemoteException;
    import java.rmi.registry.Registry;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.server.UnicastRemoteObject;
    try {
        final int port = 8009; //绑定的端口号
        final String host = "127.0.0.1"; //本机作为服务host
        final String serviceName = "animalService"; //服务名称
        IAnimalService obj = new AnimalServiceImp();
        IAnimalService stub = (IAnimalService) UnicastRemoteObject.exportObject(obj, port); //端口绑定远程对象
        Registry registry = LocateRegistry.getRegistry();
        registry.unbind(serviceName);
        registry.bind(serviceName, stub); //注册服务地址
        System.out.println("Server Start...");
    } catch (Exception e) {
        System.err.println("Server exception: " + e.toString());
        e.printStackTrace();
    }
  7. 客户端Client.java
  8. import java.rmi.RemoteException;
    import java.rmi.registry.Registry;
    import java.rmi.registry.LocateRegistry;
    Registry registry = null;
    final String host = "127.0.0.1";
    final String serviceName = "animalService"; //服务名称
    try {
        registry = LocateRegistry.getRegistry(host); //获取远程对象联机注册
        //获取动态代理类
        IAnimalService stub = (IAnimalService) registry.lookup(serviceName);
        //远程调用
        String name = stub.getMonkeyName();
        System.out.println("monkey name: " + name);
    } catch (Exception e) {
        e.printStackTrace();
    }
  9. 部署RMI:编译上述代码、启动RMIRegistry、运行服务端的代码(AnimalServer.java)
  10. 客户端调用RMI:运行客户端代码(Client.java)

使用RMI的利弊:

  • 优势:面向对象的远程服务模型;基于TCP协议上的服务,执行速度快。
  • 劣势:不能跨语言;每个远程对象都要绑定端口,不易维护;不支持分布式事务JTA

早在Applet时期,Applet+RMI是Java业内广泛推崇的方式来实现分布式计算。笔者认为RMI框架对于安全性、事务、可扩展性的支持非常有限,进而限制了其发展。

2. EJB

EJB是之前Sun公司推出的基于面向对象的服务器端组件模型。它旨在成为一个可移植的、可扩展的、事务处理的、带有安全策略的分布式解决方案。

图二:EJB在J2EE解决方案中的角色[2]

EJB的核心有三个部分:会话Bean、实体Bean、消息Bean。EJB3.0对组件模型做了很多简化,降低了开发以及配置的复杂度。本节讨论的都已EJB3.0为准。

图三:EJB3.0架构图[3]

如图三描述的那样会话Bean主要负责将业务逻辑抽象出来,会话Bean分为有状态Bean和无状态Bean,有状态Bean记录客户端的信息,无状态Bean反之。实体Bean负责持久层ORMapping的工作,EJB3.0对实体Bean做了很大的调整,提供了持久化API(JPA),简化了开发和配置。消息Bean主要用来处理JMS中间件接受的客户端消息,即JMS队列的消费者,本质上它是一个异步的无状态会话Bean。

对于本文的用例来说,最适合使用无状态的会话Bean,下面我们来看下具体的实现。

  1. 接口类AnimalBeanLocal.java
  2. import javax.ejb.Remote;
    @Remote
    public interface AnimalBeanLocal {
        String getMonkeyName();
    }
  3. 无状态会话Bean AnimalBean.java
  4. import javax.ejb.Stateless;
    /**
    * Session Bean implementation class AnimalBean
    */
    @Stateless
    public class AnimalBean implements AnimalBeanLocal {
        /**
        * Default constructor.
        */
        public AnimalBean() {
        }
        public String getMonkeyName() {
            return "I'm Jacky";
        }
    }
  5. 客户端Client.java
  6. import javax.naming.InitialContext;
    //经由JNDI命明和目录服务获取EJB
    Properties props = new Properties();
    props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
    props.setProperty("java.naming.provider.url", "localhost:1099");
    props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming");
    try {
        InitialContext ctx = new InitialContext(props);
        AnimalBeanLocal proxy;
        proxy = (AnimalBeanLocal) ctx.lookup("AnimalBean/remote");
        System.out.println(helloworld.getMonkeyName());
    } catch (Exception e) {
        e.printStackTrace();
    }
  7. 部署EJB:启动JBOSS,并将EJB组件注册进JNDI服务
  8. 客户端调用EJB:运行客户端代码

使用EJB的利弊:

  • 优势:可扩展性好,安全性强,支持分布式事务处理。
  • 劣势:不能跨语言;配置相对复杂,不同J2EE容器之间很难做无缝迁移。

EJB是被诟病最多的分布式解决方案,主要原因是EJB配置复杂而且不同容器迁移起来困难。尽管EJB3.0做了很多的简化,配置起来还是相对笨重。对于学习曲线如此陡峭的技术来说,并不是企业放心采用的解决方案。

3. Web Service

Web Service是一组分布式应用模型的规范, 它定义了网络传输协议和访问服务的数据格式。该模型隐藏了技术的实现细节,旨在提供松散耦合、跨平台、跨语言的服务组件。

图四:Web Service架构图[4]

图四描述了SOAP协议实现的Web Service模型(本节讨论都以SOAP协议实现为准),首先客户端通过UDDI(发现整合平台)找到对应的Web Service,下载对应WSDL文件,生成本地代理类,继而请求Web Service服务。UDDI的概念一直被弱化,因为客户端一般都知道Web Service的地址。

接下来我们使用Web Service来实现本文的用例。本节使用的Web Service第三方库是CXF(http://cxf.apache.org/),规范使用的是JAX-WS。

  1. 接口类IAnimalService.java
  2. import javax.jws.WebService;
    @WebService
    public interface IAnimalService {
        public String getMonkeyName();
    }
  3. 实现类AnimalServiceImp.java
  4. import javax.jws.WebService;
    @WebService(endpointInterface = "IAnimalService", serviceName = "AnimalService")
    public class AnimalServiceImp implements IAnimalService {
        @Override
        public String getMonkeyName() {
            return "I'm Jacky";
        }
    }
  5. 服务端Server.java
  6. import javax.xml.ws.Endpoint;
    IAnimalService serviceInstance = new AnimalServiceImp();
    final String address = "http://localhost:9000/animalService"; //服务名称
    Endpoint.publish(address, serviceInstance); //绑定并发布服务

  7. 客户端Client.java(无需手动下载WSDL文件,动态调用Web Service)
  8. import org.apache.cxf.interceptor.LoggingInInterceptor;
    import org.apache.cxf.interceptor.LoggingOutInterceptor;
    import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
    JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
    factory.getInInterceptors().add(new LoggingInInterceptor()); //日志输入拦截器
    factory.getOutInterceptors().add(new LoggingOutInterceptor()); //日志输出拦截器
    factory.setServiceClass(IAnimalService.class);
    factory.setAddress("http://localhost:9000/animalService");
    IAnimalService client = (IAnimalService) factory.create();
    System.out.println(client.getMonkeyName());

使用Web Service的利弊:

  • 优势:跨语言、跨平台,SOA思想的实现;安全性高;可以用来兼容legacy系统的功能
  • 劣势:性能相对差,不支持两阶段事务

Web Service使用的范围非常广,比如SalesForces(http:// www.salesforce.com),世界上最大的在线CRM提供商, 它的产品卖给使用不同技术平台的企业(.Net, Java, Ruby),SalesForces云计算的数据接口是以Web Service的方式发布的[8];Web Service另一个适用场景是,企业很多时候会有新老系统做数据交互,而新老系统使用的技术平台不一致,Web Service是个不错的选择。

引用

[1] http://www.tcs.uj.edu.pl/~krawczyk/programowanie_w_sieci_internet/wyklad/wyklad5-rmi/rmi/slajd1.html

[2] http://www.ibm.com/developerworks/cn/websphere/library/bestpractices/enterprise_javabean.html

[3] http://publib.boulder.ibm.com/infocenter/radhelp/v7r5/topic/com.ibm.jee5.doc/topics/cejb3vejb21.html

[4] http://www.emeraldinsight.com/journals.htm?articleid=862014&show=abstract

作者简介

李湃,上海交通大学计算机硕士毕业,5年互联网的行业经验,现就职于国内某互联网公司,喜欢开源技术,对于Java企业架构、分布式技术、高性能高可靠软件设计有极大的热情,希望能对国内社区有所贡献。博客地址:http://haperkelu2011.iteye.com/


感谢崔康对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

废话连篇 by yuan yunchang

废话连篇

Re: 废话连篇 by lin kaixiong

尊重他人成果,如果你有更好IDEA,请SHOW出来给大家分享!

Re: 废话连篇 by Wu Joe

个人认为,一楼应该先学会做人,再去谈论别人会不会做事

下篇呢? by yate my

下篇呢?这上篇只是概念性的介绍啊!希望看到深入一点的介绍~~

希望看到深入一点的介绍~~ by ye yunhua

希望看到深入一点的介绍~~

是穿越文么? by X Ion

这个是穿越文么? 如果穿越到5~6年前, 应该还是有一定的科普价值的, 但是这样的文章更适合发在iteye,csdn 之类的JAVA 基础知识板块, 而不是发在INFOQ; 受众有区别。

Re: 是穿越文么? by lee jacky

有道理

大家都错了 by Zeng Abrams

其实是一篇软文

看到最后是软文,还缺少了JMS by 谢 邵虎

看到最后是软文,还缺少了JMS

Re: 是穿越文么? by lan jie

这篇文章真的很水。。

教条了 by JI jk

教条了,没有深入去说明。

期待下 by qin peng

写的挺好,很细致,科普佳作

Re: 看到最后是软文,还缺少了JMS by X Ion

谢谢提醒, md,果然是一篇隐藏的很深的软文,还有不少帮腔的。

还缺少Spring的HTTP INVOKER by tao wu

还缺少Spring的HTTP INVOKER

介绍的太“入门”了 by 张 晓庆

基于RMI的JRMP只能Java用,但是CORBA可以跨平台;JBoss Remoting对象序列化与Java native的比较都没有谈到。

RMI代码有错误!!! by 北 夜人

RT

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

16 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT