Serialization of objects to persistent storage presents a potential problem with class version mismatches.
This is because the class itself is not serialized with the object. Instead, an instance of
java.io.ObjectStreamClass
that describes the class of the object is serialized. The
java.io.ObjectStreamClass
contains the class name and a version identifier for the serialized object.
This allows the local version of the class to evolve independently of the serialized data.
The compatible class modifications, such as to add a method, modify the signature of the class. If we try to read
a serialized object with a class that changed like this, the JVM returns a
java.io.InvalidClassException (Local class not compatible)
exception.
To tell to the JVM that nothing significant changed since the last compatible class, we have to mark the class with the same stream identification, named SUID. The JDK provides the serialver program that gives the SUID for all serializable class.
The static member variable called serialVersionUID can be included in the code of the new class version that implements a compatible modification. So, all objects serialized by the original class are compatible with the new class.
Each versioned class must identify the original class version for which it is capable of writing streams and from which it can read. For example, a versioned class must declare:
private static final long serialVersionUID = 3487495895819393L;
For more information about the versioning, see the Sun documentation, and the java.io.ObjectStreamClass.getSerialVersionUID() method.
The net.sourceforge.serialver
package consists of a framework to support
the serialver functionality:
serialver | Gets the SUID of serializable classes. |
getclassname | FilterReader to get the full classname from a source file. |
commentsuid | FilterReader to comment out the SUID declaration. |
replacesuid | FilterReader to replace the SUID value. |
Example | Shows a project to use these functionalities together. |
Gets the serialVersionUID of serializable classes, in a form suitable for copying into evolving classes.
This task wraps the serialver executable provided in the JDK and echoes the result. The result can also be saved in a project property to be used later, such as in a FilterReader. The content is like a property file, where the key is the classname and the value is the SUID value. If the project property name doesn't exist, it will be created, otherwise the content will be replaced.
Attribute | Description | Required |
serializableclass | Comma separated list of serializable classnames. | Yes |
classpath | Specify where to find user class files. Default: CLASSPATH of the system. | No |
failonerror | Stop the build process if the command exits with a return code other than 0. Default: yes. | No |
failifnotserializable | Stop the build process if any of the classes specified for ammendment are not Serializable Default: yes. | No |
property | Project property to store the serialver result. | No |
The classpath attribute is a PATH like structure and can also be set via nested classpath element.
Note: The classpath argument has no effect under the JDK1.1 because this argument is not supported in the serialver executable.
Same as one entry in the list given by serializableclass
.
Attribute | Description | Required |
serializableclass | The serializable classname | Yes |
The following example echoes a message with the result of the serialver executable and
save it in the project property named result.properties
. The serialver executable treats the
TestSerialverA
and com.mypackage.TestSerialverB
classes, compiled in the "./classes
" path.
<taskdef name="serialver" classname="net.sourceforge.serialver.SerialVer"/> <serialver classpath="./classes" serializableclass="TestSerialverA, com.mypackage.TestSerialverB" property="result.properties"/>Or:
<taskdef name="serialver" classname="net.sourceforge.serialver.SerialVer"/> <serialver property="result.properties"> <classpath path="./classes"/> <serializableclass name="TestSerialverA"/> <serializableclass name="com.mypackage.TestSerialverB"/> </serialver>
The content of the message looks like:
TestSerialverA: static final long serialVersionUID = 8266471551516979661L; com.mypackage.TestSerialverB: static final long serialVersionUID = 1434592923469599839L;
The content of the result.properties
property looks like:
#serialver #Mon Jul 08 15:26:45 PDT 2002 com.mypackage.TestSerialverB=1434592923469599839L TestSerialverA=8266471551516979661L
This filter parses a source code to get the classname. The classnames are returned via a project property that contains a comma-separated list. This filter creates the project property if it doesn't exist, otherwise merges the content.
Parameter | Description | Required |
property | Name of a project property to store the classname of the source file. | No |
This copies Java source files from the ${dir.src}
directory to the ${dir.dest}
directory.
A message lists the classnames of these files in a coma-separated format.
<copy todir="${dir.dest}"> <fileset dir="${dir.src}"> <include name="**/*.java"/> </fileset> <filterchain> <filterreader classname="net.sourceforge.serialver.GetClassname"> <param name="property" value="value.classnames"/> </filterreader> </filterchain> </copy> <echo message="The classnames are: ${value.classnames}"/>
The content of the message looks like:
The classnames are: TestSerialverA, com.mypackage.TestSerialverB
package
" that starts a line (can be indented).;
" character.class
" that is not in comment.This filter comments out the serial version declaration (SUID) in Java source file.
This copies Java source files from the ${dir.src}
directory to the ${dir.dest}
directory.
During the copy, the SUID declarations are put in comment. If it was already in comment, the line is untouched.
<copy todir="${dir.dest}"> <fileset dir="${dir.src}"> <include name="**/*.java"/> </fileset> <filterchain> <filterreader classname="net.sourceforge.serialver.CommentSUID"/> </filterchain> </copy>
For a source file like:
public class TestSerialverA implements Serializable { private static final long serialVersionUID = 8266471551516979661L; // SUID ...
The result looks like:
public class TestSerialverA implements Serializable { //AUTO_SUID private static final long serialVersionUID = 8266471551516979661L; // SUID ...
static
", "final
", "long
" and
"serialVersionUID
".//
" before the "serialVersionUID
" position.This filter parses the source code to update the serial version UID declaration. If the SUID declaration was
in comment, the line comment is removed. The filter takes parameters composed of
the format "classname=suidValue
". The parameter can be the content of a property file.
Parameter | Description | Required |
suidproperties | A property or a list of properties that contains the SUID values for the classnames. | No |
replaceAny | A property that if set to Yes means that SUIDs already in comment i.e. // will not be overwritten with a new calculated value if different (default = No) | No |
This copies Java source files from the ${dir.src}
directory to the ${dir.dest}
directory.
During the copy, the SUID is updated and the line comment of the SUID declaration is removed.
The ${suid.file.values}
property file and the FilterReader parameter provide the new SUID values.
<loadfile srcfile="${suid.file.values}" property="suid.property.values"/> <copy todir="${dir.dest}"> <fileset dir="${dir.src}"> <include name="**/*.java"/> </fileset> <filterchain> <filterreader classname="net.sourceforge.serialver.ReplaceSUID"> <param name="replaceAny" value="Yes"/> <param name="suidproperties" value="com.mypackage.TestSerialverB=1434592923469599839L"/> <param name="suidproperties" value="${suid.property.values}"/> </filterreader> </filterchain> </copy>
For a source file like:
package com.mypackage; public class TestSerialverB implements Serializable { // static final long serialVersionUID = 0L; // SUID ...
The result looks like:
package com.mypackage; public class TestSerialverB implements Serializable { // static final long serialVersionUID = 1434592923469599839L; // SUID ...
static
", "final
", "long
" and
"serialVersionUID
".=
" character placed after the "serialVersionUID
" key word.=
" character, and stoping at the next ";
"
character (the number of spaces is preserved).//AUTO_SUID
" that is placed before the "serialVersionUID
" position.serialVersionUID
" key word.This example shows how to work with the serialver task and associated FilterReaders. In summary, the project has a task to stop the compatibility of the serialized classes, and has a second task to create a new version of compatible classes. A simple scenario could be like:
TestSerialver.java
is a source file that implements Serializable
.TestSerialver.class
, and the serialized file of an instance is
TestSerialver1.ser
.TestSerialver.java
, the new
TestSerialver.class
is not anymore able the load the TestSerialver1.ser
file, because the
signature of the serialized class is different than the compiled class signature (SUID values).To avoid this incompatibly, when the developer just splits a method for instance, Java provides a way to force the signature of the compiled class by inserting a special declaration in the source code. An executable is provided in the JDK to give this declaration from a compiled class. ANT can helps the developer on this point, and also when he needs to create a new version of compatible classes. In this case, he has to remove the SUID declaration from the source code, compile, run the executable and insert the new declaration in the source code. The next compilations (and serialized objects) will be compatible with this version, but not anymore with the first one.
In this example, the developer can add in the "demo.src.suid
" FileSet the TestSerialver.java
file and run the "Replace SUID
" target before to do a compatible modification in the source code. It is also a good
practice to do it once the serializable class is stable. All the next compilations and serializations will be
compatible because this task has inserted a SUID declaration in the source code of TestSerialver.java
.
When a big modification is planed on the TestSerialver.java
file that will break the compatibilities
of the classes, the developer can run the "Comment SUID
" target. All the next compilation will have
a different signature than the serialized objects of the first version, because the task has put in comment the SUID
declaration. When the modifications in TestSerialver.java
are stable, the developer can create a new
version of compatible classes by running the "Replace SUID
" target.
The tasks are:
<taskdef name="serialver" classname="net.sourceforge.serialver.SerialVer"/> <property name="demo.dir.src" value="${dir.work}"/> <property name="demo.dir.dest" value="${dir.work}"/> <property name="demo.dir.bak" value="${dir.work}/backup"/> <property name="demo.dir.tmp" value="${dir.work}/tmp"/> <property name="demo.suid.file" value="${demo.dir.dest}/suid.properties"/> <property name="demo.suid.values" value=""/> <path id="demo.classpath"> <pathelement location="${demo.dir.tmp}"/> <!-- pathelement location="Project Classpath"/ --> </path> <fileset dir="${demo.dir.src}" id="demo.src.suid"> <include name="**/TestSerialver*.java"/> <!-- include name="Serializable Classes"/ --> </fileset> <!-- Task: Comment out the SUID declaration. --> <target name="Comment SUID" description="Comment out the SUID in the src files"> <delete dir="${demo.dir.tmp}" includeEmptyDirs="yes" failonerror="false" /> <mkdir dir="${demo.dir.dest}"/> <mkdir dir="${demo.dir.bak}"/> <mkdir dir="${demo.dir.tmp}"/> <!-- Backup src --> <copy todir="${demo.dir.bak}" overwrite="yes"> <fileset refid="demo.src.suid" /> <mapper type="glob" from="*.java" to="*.java.bak"/> </copy> <!-- Comment SUIDs --> <copy todir="${demo.dir.tmp}"> <fileset refid="demo.src.suid" /> <filterchain> <filterreader classname="net.sourceforge.serialver.GetClassname"> <param name="property" value="demo.suid.values"/> </filterreader> <filterreader classname="net.sourceforge.serialver.CommentSUID"/> </filterchain> </copy> <!-- Replace scr files --> <copy todir="${demo.dir.src}" overwrite="yes"> <fileset dir="${demo.dir.tmp}" > <include name="**/*.java"/> </fileset> </copy> </target> <!-- Task: Replace SUID values. --> <target name="Replace SUID" description="Replace the SUID in the src files" depends="Comment SUID" > <!-- get SUIDs --> <javac srcdir="${demo.dir.tmp}" destdir="${demo.dir.tmp}" debug="on" > <classpath refid="demo.classpath"/> </javac> <echo message="Serializable classes: ${demo.suid.values}" level="verbose" /> <serialver serializableclass="${demo.suid.values}" property="demo.suid.values"> <classpath refid="demo.classpath"/> </serialver> <echo message="SUIDs: ${demo.suid.values}" level="verbose" /> <!-- replace SUIDs --> <copy todir="${demo.dir.src}" overwrite="yes"> <fileset dir="${demo.dir.tmp}" > <include name="**/*.java"/> </fileset> <filterchain> <filterreader classname="net.sourceforge.serialver.ReplaceSUID"> <param name="suidproperties" value="${demo.suid.values}"/> </filterreader> </filterchain> </copy> <!-- Save SUIDs --> <echo message="${demo.suid.values}" file="${demo.suid.file}"/> <echo message="${demo.suid.file}: ${demo.suid.values}" level="verbose"/> </target>
Copyright © 2002 Apache Software Foundation. All rights Reserved.