Serialization (Part 2)- Customization and Compatibility

Implementing customized version of serialization to save and reload the saved data back to the object instance.

this process can be done automatically just by implementing Serializable interface, however, if we have a different version of classes or different class pattern than what has been already serialized and saved, we have a problem in restoring.

how we can address this case:

by customizing the process of reloading and storing our object we can take control of how this process can be done and what needs to be serialized.

private static final long SerialVerUID = -122005682;

How to Maintain Compatibility between Different Type Versions?

just by using SerialVer utility on the first version and then use the same value of the long variable for the rest of different versions of the same type. by this way, we can make sure all of the future and old serialized and deserialized objects are using exact same amount and they are compatible with each other.

C:\Users\Unknown_>serialver 
use: serialver [-classpath classpath] [-show] [classname...]

or if you enjoy working with GUI, you can type:

C:\Users\Unknown_>serialver -show

which bring a UI tool to generate a specific id for the class. and by using the same amount for all our of class version we won’t face any issues with regards to reloading already change classes which have been stored and saved using serialization.

Upcoming Issues:

  1. future types might have introduced new fields which older serialized items didn’t have them, so how the serializable could address that?
  2. older types might have had new fields which newer serialized items don’t have them, so how the serializable could address that?

basically, both of the questions referring to a similar scenario, and the solution is very similar. we can customize the serialization process. we can make sure what fields are being fetched in deserialization process and then define some default value for new fields which do not exist in time of serialization of the older version, and then create an object and reload fields into that.

and in case that we have got some fields that newer versions don’t have then we can easily discard them.

we are checking these process in code and details below:

for example, we have a previous User class in Serialization (Part 1) – Basics, and we serialized, saved and retrieved objects successfully. but in future, we decided to add more fields to our User type. this introducing new fields will cause incompatibility, next time we try to retrieve previously serialized User objects, below is a new class along with exception stack trace.


import java.io.Serializable;

/**
*
* @author Unknown_
*/
public class User implements Serializable {

private String username, password;
private int securityCode;

public User() {
}

public User(String username, String password) {
this.username = username;
this.password = password;

}

public void updateUsername(String username) {
System.out.println("username's changed to ... " + username);
this.username = username;
}

public void setSecurityCode(int securityCode) {
this.securityCode = securityCode;
}

@Override
public String toString() {
return "Name: " + this.username + " - passwd: " + this.password + " - SecCode: " + this.securityCode;
}

public void showcontent() {
System.out.println("" + this.toString());
}

}

NOTE: I didn’t touch constructor to make the change easy, just by making the constructor exactly similar and adding new setter method to update the new field.

run:
Name: MyOldUserName - passwd: myPasswd1 - SecCode: 0
username's changed to ... myNewUsername
Name: myNewUsername - passwd: myPasswd1 - SecCode: 0
Saving Object ... 
Reloading Object ... 
Nov 26, 2016 9:58:00 PM com.navid.practice.serialization.TestScenario1 reloadUser
SEVERE: null
java.io.InvalidClassException: com.navid.practice.serialization.banking.User; 
local class incompatible: stream classdesc serialVersionUID = 2171997951914866539,
 local class serialVersionUID = -607661653725142657
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at com.navid.practice.serialization.TestScenario1.reloadUser(TestScenario1.java:54)
    at com.navid.practice.serialization.TestScenario1.main(TestScenario1.java:74)

Exception in thread "main" java.lang.NullPointerException
    at com.navid.practice.serialization.TestScenario1.main(TestScenario1.java:74)
Java Result: 1
BUILD SUCCESSFUL (total time: 0 seconds)

as you can easily see the issue is referring to the Class incompatibility which we made it by intention in newer version of User class Type by just adding a new fields to the class. as a result the hashCode generated by serialVer is different since it depends on ClassName, methods and fields.

so the way, we can fix this is to take control of generating the SerialVersionUID and port that into out class as a private variable with the same name. in this way, whenever the serializable process will try to check the class versions, regardless of how many times we are changing that, it could still find the them compatible wioth what has been already serliaized and saved.

NOTE: just make sure you are opening the file in StandardOpenOption.APPEND, just to make sure you are not rewriting the existed files with the older serialized objects in it.we need them to prove only after adding the same serialVersionUID the incompatibility could be resolved.

just added the field which was mentioned in the exception to User class and the probelem was resolved.

import java.io.Serializable;

public class User implements Serializable {

private static final long serialVersionUID = 2171997951914866539L;

private String username, password;
private int securityCode;

public User() {
}
}

the output should be something like …

run:
Name: MyOldUserName - passwd: myPasswd1 - SecCode: 0
username's changed to ... myNewUsername
Name: myNewUsername - passwd: myPasswd1 - SecCode: 0
Saving Object ... 
Reloading Object ... 
Name: myNewUsername - passwd: myPasswd1 - SecCode: 0
BUILD SUCCESSFUL (total time: 0 seconds).

Reflection Example (Part 5)

the main idea of this example is from one of the plural course from Mr. Jim Wilson.

Legacy Beans/Objects

Two beans of example, two types of BankAccount object.

BankAccount:


package com.navid.practice.reflection.legacy.beans;

/**
*
* @author Unknown_
*/
public class BankAccount {

private String accountNum;
private int balance;

public BankAccount(String accountNum, int balance) {
this.accountNum = accountNum;
this.balance = balance;
System.out.println("BankAccount Object constructor 2");
}

public BankAccount() {
System.out.println("BankAccount Object constructor 1 ");
}

public String getId() {
return this.accountNum;
}

public synchronized int getBalance() {
return this.balance;
}

public synchronized void deposit(int amount) {
balance += amount;
}

public synchronized void whithraw(int amount) {
balance -= amount;
}

}

 

HighVolumeBankAccount:


package com.navid.practice.reflection.legacy.beans;

/**
*
* @author Unknown_
*/
public class HighVolumeAccount extends BankAccount implements Runnable {

private int[] readDailyDeposit() {
System.out.println("HighVolumeAccount process all Daily Deposits.");
return null;
}

private int[] readDailyWithraw() {
System.out.println("HighVolumeAccount process all Daily Withrwals.");
return null;
}

@Override
public void run() {

this.readDailyDeposit();
this.readDailyWithraw();

}

}

 

 

Worker concepts:

WorkerThread is a good concepts, in this example we provide a simple form of threadworker nad then adding a better implemenation best practice to it to make the desing more robus and flexible and also loosely coupled.

AccountWorker


package com.navid.practice.reflection.newsrc.worker;

import com.navid.practice.reflection.legacy.beans.BankAccount;
import com.navid.practice.reflection.legacy.beans.HighVolumeAccount;

/**
*
* @author Unknown_
*/
public class AccountWorker implements Runnable {

private BankAccount bankAccount;
private HighVolumeAccount highVol;

public AccountWorker(BankAccount acc) {
this.bankAccount = acc;
}

public AccountWorker(HighVolumeAccount hva) {
this.highVol = hva;
}

/**
* do the actual work in here ...
*/
public void doWork() {

Thread thread;
thread = new Thread(this.highVol != null ? this.highVol : this);
//starting the thread instance in here.
thread.start();
}

@Override
public void run() {
// get this operation from BATCH file, or from other files, or from databases, or webservice, etc. any other none type related
//        //way to findout the operation.

System.out.println("Running Worker Thread in Run method - and target type is: " + (this.bankAccount == null ? this.highVol.getClass() : this.bankAccount.getClass()));
}

}

 

Testing the Scenario:

and for testing the application we can use below main class to trigger the whole process of worker thread.

package com.navid.practice.reflection.newsrc.worker;

package com.navid.practice.reflection.newsrc.worker;

import com.navid.practice.reflection.legacy.beans.BankAccount;
import com.navid.practice.reflection.legacy.beans.HighVolumeAccount;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
*
* @author Unknown_
*/
public class ReflectionPractice {

public void startWork(String workerTypeName, Object workerTarget) {

try {

// providing the worker thread information.
Class<?> workerType = Class.forName(workerTypeName);

// need to create a reference to the object that we have already
// in heap for BankAccount -- we need this to find proper constructor
Class<?> targetType = workerTarget.getClass();

//prepareing worker - specify what constructor you need
Constructor<?> workerConstructor = workerType.getConstructor(targetType);
// make an actual instance from worker class.
Object workerThread = workerConstructor.newInstance(workerTarget);
// get the proper methods to execute from worker
Method doWork_method = workerType.getMethod("doWork");

//then execute that method
doWork_method.invoke(workerThread);

} catch (ClassNotFoundException ex) {
Logger.getLogger(ReflectionPractice.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchMethodException ex) {
Logger.getLogger(ReflectionPractice.class.getName()).log(Level.SEVERE, null, ex);
} catch (SecurityException ex) {
Logger.getLogger(ReflectionPractice.class.getName()).log(Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
Logger.getLogger(ReflectionPractice.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(ReflectionPractice.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalArgumentException ex) {
Logger.getLogger(ReflectionPractice.class.getName()).log(Level.SEVERE, null, ex);
} catch (InvocationTargetException ex) {
Logger.getLogger(ReflectionPractice.class.getName()).log(Level.SEVERE, null, ex);
}

}

public static void main(String[] args) {

BankAccount bankAccount = new BankAccount("1234", 100);

new ReflectionPractice().startWork("com.navid.practice.reflection.newsrc.worker.AccountWorker", bankAccount);

HighVolumeAccount highVolumeAccount = new HighVolumeAccount();

new ReflectionPractice().startWork("com.navid.practice.reflection.newsrc.worker.AccountWorker", highVolumeAccount);
}

}

output

run:
BankAccount Object constructor 2
BankAccount Object constructor 1 
Running Worker Thread in Run method - and target type is: class com.navid.practice.reflection.legacy.beans.BankAccount
HighVolumeAccount process all Daily Deposits.
HighVolumeAccount process all Daily Withrwals.
BUILD SUCCESSFUL (total time: 0 seconds)