2.6. Serialization and Its Effects

Each JavaSpace object is a local object that acts as a proxy to the remote space implementation—it is not a reference to a remote object. As a result, all space operations go through the intermediate proxy and then on to the remote space (see Figure 2.1).

Figure 2.1. Entries are serialized by a local proxy object and transmitted and stored in the remote space in serialized form.


To transfer an entry to or from the remote space, the proxy makes use of serialization. Serialization provides a way to turn an object into a stream of bytes that can be stored in a file or transmitted over a network and later reconstituted into a copy of the original object.

When an entry is written into a space, the proxy first serializes its fields and then transmits it to the space. While in the space, an entry is stored in its serialized form. When you read or take an entry, it is transmitted back to the proxy in serialized form and then the fields are deserialized into an entry before it is returned by the read or take methods.

Recall from earlier in the chapter that the read and take operations may throw an UnusableEntryException when an entry is retrieved from the space but cannot be fully deserialized by the proxy. The UnusableEntryException contains information about how the deserialization failed and any fields that could be deserialized. You can find out more about this exception in the official JavaSpaces™ Specification from Sun Microsystems, which is provided in Appendix C.

2.6.1. Entry Serialization

The way that write serializes entries is a bit different from normal object serialization. When write serializes an entry object, the proxy first writes the class information for the entry into the stream and then serializes each public field and writes its serialization into the stream. In this way each public field is serialized independently of the others. Private fields are not written into the serialized stream.

This manner of serializing has some unexpected consequences: if two fields of the entry happen to refer to the same object, the serialized form of the entry that is written into the space will have two separate copies of that object. When you read or take the entry from a space, the returned entry will be structured differently from the one you put into the space: the two fields, which before both referenced the same object, now point to different objects. Consider the following entry:

public class TestEntry implements Entry {
  public Integer obj1;
  public Integer obj2;

  public TestEntry() {
  }
}

TestEntry is a simple entry that just contains two Integer fields. Now, let's look at a code snippet that makes use of the TestEntry:

Integer obj = new Integer(0);

TestEntry entry = new TestEntry();
entry.obj1 = obj;
entry.obj2 = obj;

System.out.println("obj1 and obj2 are the same object? " +
  (entry.obj1 == entry.obj2));

System.out.println("obj1 and obj2 are equivalent? " +
  (entry.obj1.equals(entry.obj2)));

Here we create an Integer object and assign the same object to both fields of a TestEntry object. We then print out a comparison of the two fields in the entry and get:

obj1 and obj2 are the same object? true
obj1 and obj2 are equivalent? true

Next we write the entry into a space and then take it back from the space:

space.write(entry, null, Lease.FOREVER);

TestEntry template = new TestEntry();
TestEntry transferredEntry =
  (TestEntry)space.take(template, null, Long.MAX_VALUE);

System.out.println("obj1 and obj2 are the same object? " +
  (transferredEntry.obj1 == transferredEntry.obj2));

System.out.println("obj1 and obj2 are equivalent? " +
  (transferredEntry.obj1.equals(transferredEntry.obj2)));

Again we print out the comparison of the two fields, and this time we see:

obj1 and obj2 are the same object? false
obj1 and obj2 are equivalent? true

This potentially confusing result is due to the separate serialization of fields that we just mentioned. The entry stored in the space was serialized with separate copies of the integer obj and as a result the two fields are different objects but have equivalent values.

You should expect to see the same behavior anywhere you have two fields that reference the same object, whether directly or indirectly via an intermediate object. For instance, if an entry field references a tree data structure and another field points into that structure to some node in the tree, then the structure that the two fields have in common will be replicated in any entries obtained from a space.

As we have already discussed, private fields are not used in entry matching, and they are also not serialized along with the public fields. As a result, private fields will lose their values once they are written into a space. When an entry is deserialized by the proxy, the private fields will contain their default values, unless their no-arg constructor assigns values to them (we will return to the no-arg constructor shortly).

2.6.2. Matching and Equality

Serialization also affects matching semantics. As we mentioned previously, corresponding fields in a template and entry match if the two have the same value. What precisely do we mean by “same value”?

An entry is stored in a space in its serialized form. When a template arrives at the space for matching, it is also in serialized form. Matching is done by comparing the serialized versions field-by-field. Two fields match if the bytes of their serialized forms match (however, some bytes of the serialized field include annotations that describe where the class came from, and these bytes are ignored in the comparison), and has nothing to do with type-specific value matching such as Object.equals.

2.6.3. The No-arg Constructor

When we first stated the requirement that all entries have a no-arg constructor, we promised we'd explain why. The reason has to do with the way entries are returned as the result of a read or take operation. To return an entry, the space proxy does the following:

1.
Retrieves a serialized copy of the entry

2.
Reads the entry's type from the serialized stream

3.
Instantiates an object of that type, using its no-arg constructor

4.
Reads and deserializes each public field from the stream

5.
Assigns the deserialized value to each field of the object

So, to recreate an entry from its serialized form, the proxy first needs to create an instance of that object; in order to do that, the proxy needs a constructor, so it uses the no-arg constructor. Once the entry is instantiated, the proxy can then assign a value to each field from the serialized stream of field values.

2.6.4. The Entry Interface Revisited

When we first presented the Entry interface, we skipped over the fact that it extends the Serializable interface. The Serializable interface is another marker interface that contains no methods and serves to mark a class as appropriate for serialization. Here is the Entry interface again:

public interface Entry extends java.io.Serializable {
  // this interface is empty
}

The fact that the Entry interface extends the Serializable interface causes a bit of confusion. The proxy assumes that each field of an Entry is serializable because each field has to be serialized and transmitted to the remote space, but it is not technically necessary that Entry itself be (because the entry itself is not serialized by the proxy, only its fields). The designers of the JavaSpaces technology defined Entry to extend Serializable out of common sense. Each field of the entry is serializable, so the entry itself might as well be serializable too. It's important to note that if an entire entry object is serialized—for example, passed as a parameter in an RMI call—it will be serialized in the normal fashion, not field-by-field. It is only the JavaSpace write method that uses field-by-field serialization of Entry objects.

2.6.5. Improving Entry Serialization Using snapshot

In cases where you repeatedly use the same entry over and over without modification, you will incur unnecessary overhead, because the entry is reserialized every time it is used. For instance, it is common to use the same template multiple times in take or read operations, say within a loop.

In these cases the JavaSpaces API provides a way to avoid this overhead, with the snapshot method:

Entry snapshot(Entry e) throws RemoteException;

The snapshot method takes an entry and returns a “snapshotted” version, which can be used in space operations to avoid unnecessary serializations. Obtaining a snapshot may require communication with a space, so snapshot throws RemoteException like all other space operations. Once you have a snapshot of an entry, any changes to the original entry will not affect the snapshot.

A snapshot is only guaranteed to work with the space that created it. You should not count on a snapshot working across multiple spaces. Let's look at a short example and then we will explore the use of snapshot further in Chapter 10 and Chapter 11.

In the following code the MyEntry template is used multiple times in the call to read. Each time read is called, it passes the template to the proxy, which serializes it and sends it to the space to be compared against existing entries.

MyEntry template = new MyEntry();
for (int i = 0; i < 100; i++) {
   MyEntry result = (MyEntry)
    space.read(template, null, Long.MAX_VALUE);
}

Because the template never changes, clearly the serialization on each iteration isn't optimal. Here is a more efficient version that uses snapshot:

MyEntry template = new MyEntry();
Entry snapTemplate = space.snapshot(template);
for (int i = 0; i < 100; i++) {
   MyEntry result = (MyEntry)
    space.read(snapTemplate, null, Long.MAX_VALUE);
}

Here we've created a snapshotted version of template that is assigned to the variable snapTemplate. When snapTemplate is used in the read, the entry is not reserialized by the proxy. So, this version serializes the template just once, not 100 times.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset