Monday, October 5, 2009

Java Serialization & final class attributes

Today I had to face a problem with Java Serialization and here is the report of what I've achieved.
The SmartWeb BusinessObject class defines a protected attribute named logger carrying the logger for subclasses. The BusinessObject class implements Serializable thus it needs to define the logger attribute as transient because Commons Logging loggers are non serializable.

The problem arises whenever you deserialize a BusinessObject subclass because the logger attribute will not be deserialized (it has not be serialized at all!) and this makes all your logging statements producing NullPointerExceptions! BTW, those errors are very difficult to understand for two reasons:
  1. you always consider that attribute valid and you will hardly consider tha logger attribute to be null
  2. every logging statement you try to add to your code to understand what's going wrong will fail on it's own
Well, the solution to the problem is re initialize the logger attribute upon object deserialization implementing a custom readObject method as stated in the Serializable interface documentation:
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
The preceeding code is not going to work in my specific case because the logger attribute has been declared as final to avoid unwanted replacements and potential errors. The first option I took in consideration was "ok, I've no exit, let's make that attribute non final" but the idea was suddenly replaced by "but standard Java Serialization is normally able to deserialize final fields... how?" and I googled and digged a little bit into the problem ending to the following solution:
<br />	/**<br />	 * Custom deserialization. We need to re-initialize a logger instance as loggers<br />	 * can't be serialized.<br />	 */<br />	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {<br />		try {<br />			Class type = BusinessObject.class;<br />			// use getDeclaredField as the field is non public<br />			Field logger = type.getDeclaredField("logger");<br />			// make the field non final<br />			logger.setAccessible(true);<br />			logger.set(this, LogFactory.getLog(type));<br />			// make the field final again<br />			logger.setAccessible(false);<br />		} catch (Exception e) {<br />			LogFactory.getLog(this.getClass())<br />				.warn("unable to recover the logger after deserialization: logging statements will cause null pointer exceptions", e);<br />		}<br />		in.defaultReadObject();<br />	}<br />


No comments: