您的位置:首页 > 编程语言 > Java开发

java序列化和RMI

2016-05-17 17:00 501 查看
深入了解序列化“契约”

由于Java提供了良好的默认支持,实现基本的对象序列化是件比较简单的事。待序列化的Java类只需要实现Serializable接口即可。Serializable仅是一个标记接口,并不包含任何需要实现的具体方法。实现该接口只是为了声明该Java类的对象是可以被序列化的。实际的序列化和反序列化工作是通过ObjectOuputStream和ObjectInputStream来完成的。ObjectOutputStream的writeObject方法可以把一个Java对象写入到流中,ObjectInputStream的readObject方法可以从流中读取一个Java对象。在写入和读取的时候,虽然用的参数或返回值是单个对象,但实际上操纵的是一个对象图,包括该对象所引用的其它对象,以及这些对象所引用的另外的对象。Java会自动帮你遍历对象图并逐个序列化.

在通过ObjectInputStream的readObject方法读取到一个对象之后,这个对象是一个新的实例,但是其构造方法是没有被调用的,其中的域的初始化代码也没有被执行。对于那些没有被序列化的域,在新创建出来的对象中的值都是默认的.

把一个Java对象序列化之后,所得到的字节数组一般会保存在磁盘或数据库之中跨JVM使用。反序列化的时候不能仅根据Java类的全名来判断,这个类可能处于另外一个JVM,而当前JVM中可能存在名称相同,但是含义完全不同的Java类。这个对应关系是通过一个全局惟一标识符serialVersionUID来实现的。通过在实现了Serializable接口的类中定义该域,就声明了该Java类的一个惟一的序列化版本号。JVM会比对从字节数组中得出的类的版本号,与JVM中查找到的类的版本号是否一致,来决定两个类是否是兼容的。对于开发人员来说,需要记得的就是在实现了Serializable接口的类中定义这样的一个域,并在版本更新过程中保持该值不变。当然,如果不希望维持这种向后兼容性,换一个版本号即可。该域的值一般是综合Java类的各个特性而计算出来的一个哈希值,可以通过Java提供的serialver命令来生成。

虽然一个对象要序列化,只需要实现一个没有任何方法的接口Serializable,但是,深入研究你会发现,java序列化其实包含很多不符合java语法规则的"契约",或者说这些实现不是通过常规java实现的,推测应该是通过反射实现的,因此成为契约。

1.要把一些域不序列化,可以在器前边加上关键字"trasient",这个容易理解。

2.只把部分域序列化,可以在类中声明一个固定域名和类型的静态字段,这个就诡异了

private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("firstName", String.class) };

3.如果要深度修改序列化过程,可以在类中实现writeObject和readObject方法,奇怪的是,这两个方法既不是Object的方法,也不是Serializable的方法,但是他就是被ObjectInputStream和ObjectOutputStream调用了,应该是通过反射,这也是"契约"的一部分,如下(这两个方法需要在要序列化的类中实现):

private void writeObject(ObjectOutputStream output) throws IOException {

output.defaultWriteObject();

output.writeUTF("Hello World");

}

private void readObject(ObjectInputStream input) throws IOException,

ClassNotFoundException {

input.defaultReadObject();

String value = input.readUTF();

System.out.println(value);

}

4.更离奇的契约还有,如果我们一个订单包含客户信息,但是我们不想序列化客户全部信息,而客户类又不能改动,这样,我们需要创建一个类来代替订单类,我们的实现如下:

private static class OrderReplace implements Serializable {// 创建一个新的类用来代替Order

private static final long serialVersionUID = 4654546423735192613L;

private String orderId;

public OrderReplace(Order order) {

this.orderId = order.getId();

}

private Object readResolve() throws ObjectStreamException {// 契约方法,用来读取时重新组装对象

// 根据orderId查找Order对象并返回

}

}

private Object writeReplace() throws ObjectStreamException {// 契约方法,更WriteObject不同,可以替换对象

return new OrderReplace(this);

}

}

/**************************************************************************/

/**************************************************************************/

RMI:

RMI(Remote Method Invocation)是Java中的远程过程调用(Remote Procedure Call,RPC)实现,是一种分布式Java应用的实现方式。

RMI采用的是典型的客户端-服务器端架构。首先需要定义的是服务器端的远程接口,只需要继承自RMI中的Remote接口即可。Remote和Serializable一样,也是标记接口。远程接口中的方法需要抛出RemoteException。

如下:

public interface Calculator extends Remote {

String calculate(String expr) throws RemoteException;

}

public class CalculatorServer implements Calculator {

public String calculate(String expr) throws RemoteException {// 实现远程接口

return expr;

}

public void start() throws RemoteException, AlreadyBoundException {

Calculator stub = (Calculator) UnicastRemoteObject.exportObject(

this, 0);// 创建一个能够被发布的对象

Registry registry = LocateRegistry.getRegistry();

registry.rebind("Calculator", stub);// 把自己注册并发布出去

}

}

在客户端直接访问就行:

public class CalculatorClient {

public void calculate(String expr) {

try {

Registry registry = LocateRegistry.getRegistry("localhost");// 服务器地址

Calculator calculator = (Calculator) registry

.lookup("Calculator");// 查找远程对象

String result = calculator.calculate(expr);// 直接调用

System.out.println(result);

} catch (Exception e) {

e.printStackTrace();

}

}

}

为了通过Java的序列化机制来进行传输,远程接口中的方法的参数和返回值,要么是Java的基本类型,要么是远程对象,要么是实现了 Serializable接口的Java类。当客户端通过RMI注册表找到一个远程接口的时候,所得到的其实是远程接口的一个动态代理对象。除了序列化之外,RMI还使用了动态类加载技术。当需要进行反序列化的时候,如果该对象的类定义在当前JVM中没有找到,RMI会尝试从远端下载所需的类文件定义。可以在RMI程序启动的时候,通过JVM参数java.rmi.server.codebase来指定动态下载Java类文件的URL。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: