----- Original message -----
> Peter, I am really trying hard to understand what you find the most
> frustrating
> about Serialization.
Boilerplate code, complexity, trust, immutability and safe publication.
Eg, if I want final fields and I want to defensively copy arrays or
collections, my code has to be trusted enough to grant permission to use
reflection to write a private final field after it has already been
initialised. Otherwise someone can use serialisation to break encapsulation or
I must give up safe publication and immutability. But that's the issue, code
for objects being deserialised cannot be granted trust and objects should't be
deserialised in privileged context.
So presently I can't have immutability with guaranteed safe publication and
protect invarients as well, I must accept compromise, with the new solution I
can have all, I can have my cake and eat it too :).
If I forget to implement readObject and also forget to call defaultReadObject
and if I later add a serializable field, earlier versions of my class can no
longer deserialse objects from my new version.
Serialisation also publishes the internal form of my class and that means I'm
stuck with that class forever.
With the new solution, I can replace my class completely, delete every existing
field and use a totally different object structure, without breaking
compatibility, without needing to know the intricacies of serialization.
Also out of the box, for very little effort, it uses externalizable, which is
at least an order of magnitude or two faster than default serialisation.
The new solution stands on the shoulders of giants, it seems unfair to be able
to have so much flexibility and freedom for so little work.
I don't really see new mechanisms in use here, just a
> repackaging/layering of the same things.
Well that's not entirely true, the new mechanism uses reflection to call
constructors and methods for object instantiation.
Serialization only calls a public no arg constructor, then reflectively sets
fields even if private, it breaks encapsulation, Distributed doesn't break
encapsulation.
This is similar to reflective proxy's replacing stubs.
The beauty of this solution is, it retains full compatibility, it's easier to
implement, it uses public construction methods so developers don't need special
permission and it's simple to maintain.
Superclasses that don't implement Serializable and don't have a public no arg
constructor can now be extended by implementing Distributed.
The good news is, you can still use serialisation, it's completely compatible
with existing code.
More comments inline below...
In my history of dealing with version
> compatibility with serialization, I've always done the following steps and
> solved the problems I had created by not using an explicit serialVersionUID
> field.
>
> 1. In general, I just change classes to have new fields and features that I
> need
> for a new "version". I usually have at least two servers using a particular
> object/class and so, deploying new -dl jar files, I end up with a server that
> has old versions of classes, talking to a new client, with the new -dl.jar
> files, that will bark out a Serialization error due to incompatible
> serialVersionUID values. 2. In the above exception, are the
> serialVersionUIDs
> of the old version and the new version. If there is really new compatibility
> issues related to how the fields in the old version of the class, are used in
> the new version of the class, there is no reason why serialVersionUID of the
> old
> class can not be used in the new class. 3. As a general rule, I just don't
> initially specify a serialVersionUID in any serializable classes, because I
> want
> to make sure I see failure between versions, and can then "fix" the actual
> compatibility by either declaring that they are compatible, or leaving them as
> incompatible. 4. When the classes are compatible, I just add the
> serialVersionUID field to the new version of the class, and copy the old value
> out of the exception message. 5. Once I've done this, the classes are
> compatible, and I now have to worry about "initialization" of the new fields
> (if
> any, because it might just be new methods that changed the serialVersionUID).
> 6.
> The most typical thing that I do, to fix the initialization issue, is what
> I've
> discussed here before. If there is no "Object" field that is new and
> uninitialized, or no "native" field, which would always have a non-default
> value
> from the new cons activity, then I will add a new field that is the
> "not-new-version-yet" indicator. It can just be a boolean, as in
>
> private final boolean version2up = true;
>
> so that the default cons activities will initialize it, but deserialization of
> the old data, into the new class, will not. Then, I just provide
> readObject(),
> or modify it to do the appropriate initialization/version upgrade of the old
> data to the new data.
>
> private void readObject( ObjectInputStream is ) throws IOException,
> ClassNotFoundException { is.defaultReadObject();
> if( !version2up ) {
> // initialize new data values and/or convert old data values here.
> }
> }
>
> This has always worked for me, and I've not really understood why everyone
> always complains about versioning at this "level". Certainly, the layer below
> this with object replacement and all the rest of the more dramatic object
> lifecycle/version management can be used as well. The recent activity around
> the Levels class, indicates times when you want to try and pass a different
> "object" into the serialization stream, then what you are holding. So,
> downgrading by reversing the above scenario might also be necessary in some
> cases, and when that is necessary, you have to have a common root, like
> "Level"
> is to "Levels" or else you can't make the the "old" or "don't have that class"
> JVM instances do the right thing.
>
> If you don't mind some hints into what (other) issues are the most frustrating
> or unwieldy to you, that would help me understand how to look at what you are
> doing here.
>
Levels is the perfect storm, a Distrbuted implementation subclass simply calls
the protected constructor. It will only break if Oracle removes the protected
constructor. At present our subclass is dependant on Oracle's serial form, a
Distributed implementation is only dependant on publicly visible api. In fact
in the distributed version, Level wouldn't even be serialised, only it's class,
a string and an int.
Hope this helps explain it.
> Gregg
>
> On Feb 27, 2013, at 3:09 AM, Peter Firmstone <[email protected]> wrote:
>
> > I've just uploaded this to svn, package org.apache.river.api.io
> >
> > This will blow your mind, how's this for simple mistake free alternative to
> > Serialization?
> >
> > 3 examples of different construction methods are demonstrated:
> >
> > 1. Constructor
> > 2. Static factory method
> > 3. Builder
> >
> > All creation methods are public, fields are private and final, internal
> > class
> > state is not exposed and is free to evolve separately all invarients are
> > checked during construction objects safely published on deserialisation.
> >
> > You can even replace your objects with alternatives that use completely
> > different classes, provided referencing fields are declared to use a common
> > superclass or interface.
> >
> > Serial form is fixed and it uses Externalizable, it's faster than
> > serialization, there's no serialVersionUID required, just one little method
> > to
> > implement.
> >
> > We now have the ability to create immutable distributed value objects that
> > are
> > thread safe and can be evolved though public api.
> >
> > It took two nights to code and it worked first time!
> >
> > This is a game changer, plug this into JERI and we've got a perfect
> > compliment
> > to reflective proxy's.
> >
> > Penny for your thoughts?
> >
> > Peter.
> >
> > package tests.support;
> >
> > import java.io.Serializable;
> > import org.apache.river.api.io.Distributed;
> > import org.apache.river.api.io.SerialFactory;
> >
> > /**
> > *
> > * @author peter
> > */
> > public class DistributedObject implements Distributed {
> >
> > public static DistributedObject create(String str){
> > return new DistributedObject(str);
> > }
> >
> > private final String testString;
> > /* 0 - constructor
> > * 1 - static factory method
> > * 2 - builder
> > */
> > private final int method;
> >
> > public DistributedObject(String str){
> > testString = str;
> > method = 0;
> > }
> >
> > public DistributedObject(String str, int method){
> > testString = str;
> > this.method = method;
> > }
> >
> > @Override
> > public SerialFactory substitute() {
> > Class[] signature = new Class[1];
> > Object[] parameters = new Object[1];
> > parameters[0] = testString;
> > if (method == 0){
> > signature[0] = String.class;
> > return new SerialFactory(this.getClass(), null,
> >signature,
> > parameters ); }
> > if (method == 1){
> > signature[0] = String.class;
> > return new SerialFactory(this.getClass(), "create",
> >signature,
> > parameters); }
> > if (method == 2){
> > Builder builder = new Builder().setString(testString);
> > return new SerialFactory(builder, "build", null, null);
> > }
> > return null;
> > }
> >
> > public String toString(){
> > return testString;
> > }
> >
> > public static class Builder implements Serializable {
> > private static final long serialVersionUID = 1L;
> >
> > private String str;
> >
> > public Builder(){
> >
> > }
> >
> > public Builder setString(String str){
> > this.str = str;
> > return this;
> > }
> >
> > public DistributedObject build(){
> > return new DistributedObject(str);
> > }
> > }
> >
> > }
> >
>