Logo

dev-resources.site

for different kinds of informations.

Effective Java: Consider Serialization Proxies Instead of Serialized Instances

Published at
2/21/2022
Categories
java
effective
serializable
serialization
Author
kylec32
Author
7 person written this
kylec32
open
Effective Java: Consider Serialization Proxies Instead of Serialized Instances

Throughout all the recent items as we have discussed Java serialization, we have been discussing many of the challenges that come along with it. While on the surface it looks simple to implement, in reality, it is far from it. Due to the effectively hidden constructor provided by the serialization framework Serializable code is open to many potential issues that need to be protected against. Thankfully there is a pattern, called the serialization proxy pattern that can help us sidestep many of these issues.

One of the best parts of the serialization proxy pattern is that it is rather straightforward, especially compared to some of the alternatives. The first step is creating a private static nested class that holds all the necessary information to create your target object, this is your serialization proxy. This class will have a single constructor of the type of the enclosing class. This constructor simply copies the data from the parameter into its internal state, with no need for consistency checks or defensive copies. Now the serialization proxy and enclosing class need to add implements Serializable to their class signature. Let's look at an example of the serialization proxy that we would write for the Period class we have been discussing in recent items.

private static class SerializationProxy implements Serializable {
  private final Date start;
  private final Date end;

  SerializationProxy(Period period) {
    this.start = period.start;
    this.end = period.end;
  }

  // Any number will do here.
  private static final long serialVersionUID = 1234567890L
}
Enter fullscreen mode Exit fullscreen mode

Our next step is to add a writeReplace method to the enclosing class. This method will look exactly like this in every implementation (assuming you call your private static serialization proxy SerializationProxy)

private Object writeReplace() {
  return new SerializationProxy(this);
}
Enter fullscreen mode Exit fullscreen mode

When this method is on a class and it is serialized it causes the serialization system to return a SerializationProxy instance instead of an instance of the enclosing class. With this code in place that means that the serialization system will never create an instance of the enclosing class. To make sure no one tries to craft one maliciously we can add the following. (Again this code could be copied verbatim in a class implementing this pattern)

private void readObject(ObjectInputStream stream) throws InvalidObjectException {
  throw new InvalidObjectException("Proxy required");
}
Enter fullscreen mode Exit fullscreen mode

The final step in the pattern is to provide a readResolve method in the SerializationProxy class that returns a logically equivalent instance of the enclosing class. The way it does this is by using only the enclosing class's public API. The benefit this provides is that it doesn't need to do anything special to protect the creation, all the protections can live within the enclosing class which it would already have to protect itself from "regular" API consumers. This is the benefit of this pattern, it doesn't use any "magic" constructors or capabilities, it simply forces the serialization framework to use the regular language primitives. In our example the readResolve function would be:

private Object readResolve() {
  return new Period(start, end);
}
Enter fullscreen mode Exit fullscreen mode

Additional benefits this pattern provides to us above and beyond what the previously discussed patterns do is that the member variables in Period can once again be final which they always wanted to be to enforce immutability. The other huge benefit is the pattern is simple. A good proportion of it is simply copying and pasting the same code. That is to say, the effort you put in versus the benefit you get out with this pattern is great. The final additional benefit would be that the readObject method can return a different type of object than the originally serialized instance was. This benefit is taken advantage of by the EnumSet class in the core of the language. EnumSet uses the serialization proxy pattern to make for safer serialization and to allow for it to use the most efficient implementation (RegularEnumSet or JumboEnumSet) depending on how many types are in a particular enum.

There is always a cost to whatever we do though so what are the costs here? First, it is not usable with classes that are built to be extended. It is also not compatible with classes that can have circular references in their object graphs. Finally, it comes at a computational cost with measurements in the 14% slower range for using this pattern. All this being said, this pattern can be extremely useful to know. By taking some simple actions and accepting some potentially minimal drawbacks we can have far safer code.

With the end of this item, we have reached the end of the book Effective Java. There is a lot of insight that can be gleaned from this book and practicing its guidance. I know that I have learned a lot from the book about some lesser-known features of the language and some of the sharp edges I can watch out for in my day-to-day work. I hope you have gleaned some benefits too. I would like to do some more reviews like this in the future so be sure to subscribe for updates.

effective Article's
30 articles in total
Favicon
What is The Best Time to Learn New Things Effectively?
Favicon
Designing an Efficient Biomass Generator Set System
Favicon
Top 10 Essential Features for an Effective Knowledge Base
Favicon
Effective Java: Consider Serialization Proxies Instead of Serialized Instances
Favicon
Effective Java: For Instance Control, Prefer Enum types to readResolve
Favicon
Effective Java: Write readObject Methods Defensively
Favicon
Effective Java: Consider Using a Custom Serialized Form
Favicon
Effective Java: Implement Serializable With Great Caution
Favicon
Effective Java: Prefer Alternatives To Java Serialization
Favicon
Effective Java: Don't Depend on the Thread Scheduler
Favicon
Effective Java: Use Lazy Initialization Judiciously
Favicon
Effective Java: Document Thread Safety
Favicon
Effective Java: Prefer Concurrency Utilities Over wait and notify
Favicon
Effective Java: Prefer Executors, Tasks, and Streams to Threads
Favicon
Effective Java: Avoid Excessive Synchronization
Favicon
Effective Java: Synchronize Access to Shared Mutable Data
Favicon
Effective Java: Don't Ignore Exceptions
Favicon
Effective Java: Strive for Failure Atomicity
Favicon
Effective Java: Include Failure-Capture Information in Detail Messages
Favicon
Effective Java: Document All Exceptions Thrown By Each Method
Favicon
Effective Java: Throw Exceptions Appropriate To The Abstraction
Favicon
Effective Java: Favor The Use of Standard Exceptions
Favicon
Effective Java: Avoid Unnecessary Use of Checked Exceptions
Favicon
Effective Java: Use Checked Exceptions for Recoverable Conditions
Favicon
Effective Java: Use Exceptions for Only Exceptional Circumstances
Favicon
Effective Java: Adhere to Generally Accepted Naming Conventions
Favicon
Effective Java: Optimize Judiciously
Favicon
Effective Java: Use Native Methods Judiciously
Favicon
Effective Java: Prefer Interfaces To Reflection
Favicon
10 effective tips for New Developers

Featured ones: