写点什么

Java Remoting 远程服务(上)

  • 2012 年 1 月 02 日
  • 本文字数:4599 字

    阅读完需:约 15 分钟

今天我们来聊聊 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
复制代码
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IAnimalService extends Remote {
String getMonkeyName() throws RemoteException;
}
  1. 实现类 AnimalServiceImp.java
复制代码
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";
}
}
  1. 服务端 AnimalServer.java
复制代码
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();
}
  1. 客户端 Client.java
复制代码
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();
}
  1. 部署 RMI:编译上述代码、启动 RMIRegistry、运行服务端的代码 (AnimalServer.java)

  2. 客户端调用 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
复制代码
import javax.ejb.Remote;
@Remote
public interface AnimalBeanLocal {
String getMonkeyName();
}
  1. 无状态会话 Bean AnimalBean.java
复制代码
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";
}
}
  1. 客户端 Client.java
复制代码
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();
}
  1. 部署 EJB:启动 JBOSS,并将 EJB 组件注册进 JNDI 服务

  2. 客户端调用 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
复制代码
import javax.jws.WebService;
@WebService
public interface IAnimalService {
public String getMonkeyName();
}
  1. 实现类 AnimalServiceImp.java
复制代码
import javax.jws.WebService;
@WebService(endpointInterface = "IAnimalService", serviceName = "AnimalService")
public class AnimalServiceImp implements IAnimalService {
@Override
public String getMonkeyName() {
return "I'm Jacky";
}
}
  1. 服务端 Server.java
复制代码
import javax.xml.ws.Endpoint;
IAnimalService serviceInstance = new AnimalServiceImp();
final String address = "http://localhost:9000/animalService"; // 服务名称
Endpoint.publish(address, serviceInstance); // 绑定并发布服务

  1. 客户端 Client.java(无需手动下载 WSDL 文件,动态调用 Web Service)
复制代码
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 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2012 年 1 月 02 日 00:0010599

评论

发布
暂无评论
发现更多内容

你的数据产品应该是一套解决方案

第519区

数据产品经理 解决方案 数据产品 2月月更

今天踩了一个基础坑

编程三昧

JavaScript 2月月更

【附赠PPT】 KubeMeet 成都站回顾:让云原生应用交付和管理变得更简单!

阿里巴巴云原生

阿里云 Kubernetes 云原生 活动 开源项目

实力卓越,旺链科技与IBM、华为等共登「超级账本」年度贡献榜!

旺链科技

区块链 超级账本 产业区块链

如何用建木CI发送邮件

Jianmu

html 自动化 发送邮件

被催稿了,所以聊聊 长链接在移动端开发中如何做到和短链接一样高效

百瓶技术

TCP 计算机网络 websocket

网络安全kali渗透学习 web渗透入门 如何进行基于Nmap的扫描方式

学神来啦

Serverless 架构开发手册 — “人人都是 Serverless 架构师”先导篇

阿里巴巴云原生

阿里云 Serverless 架构 云原生

http请求中的payload

喀拉峻

网络安全

运营给产品送的情人节礼物是?

阿里云弹性计算

产品运营 情人节 轻量征文 用户投稿

1月月更获奖名单公布!快来获取专属海报

InfoQ写作社区官方

热门活动

从IPv4 到 IPv6 的过渡技术

郑州埃文科技

ipv6 ipv4 过渡技术

7*24 小时业务不中断!菜鸟乡村应用多活落地实践

阿里巴巴云原生

阿里云 云原生 实践案例 多活

KubeDL HostNetwork:加速分布式训练通信效率

阿里巴巴云原生

阿里云 云原生 分布式训练 KubeDL

买贵不买对?这个情人节,你的礼物选对了吗?

易观分析

情人节 美妆

阿里巴巴如何进行测试提效 | 阿里巴巴DevOps实践指南

阿里云云效

阿里云 DevOps 云原生 测试 研发提效

【网络安全】记一次挖矿病毒的应急响应

H

网络安全 应急响应

阿里云容器服务差异化 SLO 混部技术实践

阿里巴巴云原生

阿里云 Kubernetes 云原生 混部技术

小程序框架与平台编译对比

Speedoooo

编译 ios开发 APP开发 Andriod开发 小程序框架

DGIOT物联网架构设计

dgiot

物联网 2月月更 2月日更 dgiot dgiot物联网

年轻用户逐渐成为数字营销主流受众,品牌营销方式该如何创新?

易观分析

内容营销

SAE 最佳实践范本:助力视野数科进入云原生“快车道”

阿里巴巴云原生

阿里云 Serverless 云原生 SAE

一篇文章讲懂prometheus

流沙

云原生 监控 Prometheus

从冬奥看中国科技(三):数字人的觉醒与进化

脑极体

Spring Boot Serverless 实战 | Serverless 应用的监控与调试

阿里巴巴云原生

阿里云 Serverless 云原生

dart系列之:集合使用最佳实践

程序那些事

flutter dart 程序那些事 2月月更

情人节,码了一个程序员专属冰墩墩(内含源码免费获取)

ZEGO即构

前端 html/css 情人节 表白 冬奥会

用python制作心型照片墙

36度道

Python基础

Java&Go高性能队列之Disruptor性能测试

FunTester

Disruptor 性能测试 高性能 消息队列 FunTester

我们找回了泄露的内存

Qunar技术沙龙

如何利用 AHAS 保障 Web 服务稳如磐石?

阿里巴巴云原生

阿里云 高可用 云原生 AHAS

金融行业数据库架构实践与运维

金融行业数据库架构实践与运维

Java Remoting远程服务(上)_Java_李湃_InfoQ精选文章