I am working on building a music.player and have my music-library stored in a HashMap. The User shall be able to add and delete songs. I want to save this HashMap for when the program is beeing restartet.
However did I encounter this warning:
Exception in thread "main" java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: musicplayer.Song
Research showed I have to implement the Serializable Interface in my Song class. Which I did, but with still this warning.
My Song class:
package musicplayer;
//Song-Klasse, speichert alle Attribute und Methoden eines Songs. Funktioniert soweit
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
public class Song implements Serializable {
private static final long serialVersionUID = 4390482518182625971L;
//Attribute
File file;
Clip clip;
String string;
//...
The MusicDaten - Class
package musicplayer;
public class MusicDaten implements Serializable {
private static Map<String,Song> all; //= new HashMap<String,Song>();
private File file = new File("C://Users//ThinkPad T450s//git//testproject//musicplayer//SongInfo.ser");
// ...
public MusicDaten() throws ClassNotFoundException, IOException {
this.setSavedSongs();
}
public void setSavedSongs() throws IOException, ClassNotFoundException { //initialisziert HashMap mit den gespeicherten Songs
FileInputStream fileIn = new FileInputStream(file);
ObjectInputStream in = new ObjectInputStream(fileIn);
all = (HashMap<String,Song>) in.readObject();
in.close();
fileIn.close();
}
public void save() throws IOException { //Speicher HashMap
FileOutputStream fileOut = new FileOutputStream(file);
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(all);
out.close();
fileOut.close();
System.out.println("Songinfo saved");
}
Thank you for the help.
(I have edited this question since before it wasn't quite clear)
Implementing Serializable is not sufficient.
If you attempt to serialize an object, all its non-transient attributes are serialized, too. If any of those attributes is not Serializable, it will not work.
In your case, Song contains an attribute of type File and File is not serializable. With Clip, you have the same problem.
In order to get around this, you can do custom serialization.
Looking at the docs of Serializable, you can find this:
Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore it. The default mechanism for saving the Object's fields can be invoked by calling out.defaultWriteObject. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.
The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism for restoring the object's non-static and non-transient fields.
This means that you can create the methods writeObject and readObject where you specify how to (de)serialize the object.
If you want to keep the default (de)serialization of the attributes supporting serialization, you can mark all fields not supporting serialization transient and call out.defaultWriteObject/in.defaultReadObject in the writeObject/readObject methods.
Marking an attribute transient means that serialization ignores it. You can then use your custom logic.
Note that serialization comes with some problems and you might not want to use it.
On one hand, it can lead to serious denial of service and even remote code execution vulnerabilities if you deserialize untrusted data. This is also noted in the docs of Serializable:
Warning: Deserialization of untrusted data is inherently dangerous and should be avoided. Untrusted data should be carefully validated according to the "Serialization and Deserialization" section of the Secure Coding Guidelines for Java SE. Serialization Filtering describes best practices for defensive use of serial filters.
Another problem of serialization is that it binds your application to a fixed format and makes it difficult to be compatible with old serialized data when update your application if you didn't carefully think it through when initially creating it.
For more information about this, you may want to consider reading the book Effective Java.
Related
i'm using the example from this repo https://github.com/kojenov/serial/tree/master/3-4.%20upload, which is presenting a method for specifying a way to protect form unsafe deserialization in Java by defining a custom ObjectInputStream and overriding a protected method resolveClass in which we have to specify which classes are allowed for deserialisation.
My problem is I added a LocalDate field to the Planet class and when I deserialize a serialized object I get this exception:
invalid class except unsupported class; java.time.Ser
I searched online and I could not find any other encounter with that problem, so I'm really confused. I tried with instead of the LocalDate to add a LocalDateTime, the same error happens again. As far as I found that class java.time.Ser is a protected class somewhere in the hierarchy of the classes in that package.
The class LocalDate is serializable, so this should not happen. I know for sure that the problem is in the LocalDate, because if I make that field transient code works as intended. Am I missing something or it's just a bug of Java Object Serialization?
By the way, the examples are originally from a talk given by Alexei Kojenov, his site is kojenov.com, but i couldn't find his email to ask him personally.
Serialization is recursive progress, which means when you're serializing a complex object, firstly you need to serialize all its properties. The same thing happens with deserialization.
Planet object contains fields of type int, double and java.lang.String which are primitives and don't need special (de)serialization. LocalDate or LocalDateTime aren't primitives and they're serialized and then deserialized with SafeObjectInputStream.
Serialization hack
As it said in java.io.Serializable documentation, objects can modify their serialization behaviour and even delegate serialization to another class by defining method writeReplace.
JavaDoc cite:
Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have private, protected and package-private access. Subclass access to this method follows java accessibility rules.
Both LocalDate and LocalDateTime utilizes this possibility and define writeReplace method.
As an example, java.time.LocalDate's implementation:
private Object writeReplace() {
return new Ser(Ser.LOCAL_DATE_TYPE, this);
}
java.time.Ser is a package-private final class that is used as a delegate for java.time.* objects.
Hereby, when you're serializing java.time.LocalDate or java.time.LocalDateTime, actually java.time.Ser being serialized.
Custom deserializer
Previously we found out that java.time.LocalDate was serialized as java.time.Ser. Now, let's try to deserialize it with SafeObjectInputStream.
Before deserialization, resolveClass method is called:
#Override
protected Class<?> resolveClass(ObjectStreamClass input)
throws IOException, ClassNotFoundException
{
if (!input.getName().equals(Planet.class.getName())) {
throw new InvalidClassException("Unsupported class", input.getName());
}
return super.resolveClass(input);
}
It checks for class name to be equal to Planet.class.getName(), but java.time.Ser is not, that's why you're getting an exception.
Solution
To resolve this issue, you need to add java.time.Ser to the list of trusted classes. I would suggest modifying your SafeObjectInputStream next way:
public class SafeObjectInputStream extends ObjectInputStream {
private final List<String> supportedClasses = List.of(Planet.class.getName(), "java.time.Ser");
public SafeObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
#Override
protected Class<?> resolveClass(ObjectStreamClass input)
throws IOException, ClassNotFoundException
{
if (!supportedClasses.contains(input.getName())) {
throw new InvalidClassException("Unsupported class ", input.getName());
}
return super.resolveClass(input);
}
}
NOTE: List.of was introduced in Java 9. If your Java version is less than 9, you can replace it with Arrays.asList.
I was wondering, if I can cheat serialization by wrapping them in local nested classes, something like this:
I have a service which I need to pass around, but it internally has some very complex data.
interface ComplexService {
IncredibleComplexObject getData();
}
So I thinking about wrapping it in another class that is serializeable via decorator pattern.
public final class Utils {
public static Serializable wrap(final ComplexService service) {
class WrapperService implements ComplexService, Serializeable {
#Override
public IncredibleComplexData getData() {
return service.getData();
}
};
return new WrapperService();
}
}
I actually don't believe that I can cheat serialization like that, because it would be a little bit too much magic if Java could actually recreate my class that is dependent on my final ComplexService-parameter. But I am wondering, why exactly this fails and what exception would be thrown, where and why.
(just for clarification why I would want to do this: I am on android and I need to pass this service to a Fragment, which naturally can only save serializeable objects).
Yes, you can wrap your non-serializable object in a serializable wrapper. No, it won't magically make the wrapped object serializable. You'll get a NotSerializableException if you attempt to serialize the wrapper class (without making that field transient).
I have a class like
class Stundent
{
private static final long serialVersionUID = 1L;
private int no;
private String name;
//setters and getters
}
And then i used the following code for serialization & deserialization
public class Serialization{
public static void main(String args[]) throws Exception {
File file = new File("out.ser");
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerializeMe serializeMe = new SerializeMe(1);
oos.writeObject(serializeMe);
oos.close();
}
}
public class DeSerialization{
public static void main(String args[]) throws Exception {
File file = new File("out.ser");
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
SerializeMe dto = (SerializeMe) ois.readObject();
System.out.println("data : " + dto.getData());
ois.close();
}
}
After running the Serialization class i changed the serialiversionID to 2 and then i run the second program,then i got like
java.io.InvalidClassException:
SerializeMe; local class incompatible
That means whenever i am performing deserialization
serialversionID of Student class is checking.
My doubt is
1)Is it mandatory to have Student class at the time of deserialization ?
2)As per serialization definition, to transfer the java objects as stream of bytes through network we are performing serialization.So if an object is transferred through network ,how the Student class is available in other side.
For example in my application every entity(Hibernate entity) is
Serializable.
Because my web application exist in different server and my database
is in differentserver.That is the reason we have implemented
Serializable.In this case can u explain how serialization is working?
Thanks in advance...
When you deserialize a serialized object, the same class has to be present in order to construct an instance of that class.
The serialVersionUID is to tell whether the same version of the class is present in terms of the serialization process. If the class that is present has a different serialVersionUID value (compared to the value read from the binary data/stream of the serialized object), deserialization will be aborted by throwing an InvalidClassException.
1)Is it mandatory to have Student class at the time of deserialization?
Yes.
2)As per serialization definition, to transfer the java objects as stream of bytes through network we are performing serialization.So if an object is transferred through network ,how the Student class is available in other side.
You have to take care of that. You have to distribute the Student class prior to deserializing a serialized Student.
Deserialization can only happen if the same version (determined by the serialVersionUID value) exists of the serialized class. Serializing instances of the standard lib is not a problem, because those are present in all JRE's (although different versions might be present with different serialVersionUID fields!), so you can "transfer" such objects between different JRE's. If an instance of a custom class is transferred, the same class file must be made available at the destination prior to the deserialization process.
Edit:
You wrote that your "web application exists on different servers". Your web application includes your class files which implicitly means that the class files will be available on all servers. So a serialized object from one server can be deserialized on another.
As far as my understanding is concerned, usually when we transfer objects, it is the attribute values inside the Student object is what we are require.
The Student class should be present on the other side.
I want to write the code, that can deserialize the class even if the class was changed (but you have old class version).
The idea is simple.
Read the serialVersionUID of the serialized class
Check if that serialVersionUID is equal to the serialVersionUID of the current class version.
If not, create new ClassLoader and load the old class version into workspace.
I thought to use something like this:
FileInputStream file = new FileInputStream(filename);
ObjectInputStream o = new ObjectInputStream(file) {
protected Class<?> resolveClass(java.io.ObjectStreamClass desc) throws IOException, ClassNotFoundException {
//5639490496474438904L is the suid of the older version
if (desc.getSerialVersionUID() == 5639490496474438904L) {
Class c01 = null;
URL eineURL = new URL("file://localhost/U:/MyJavaProj/bin/test/oldversion/");
URL[] reiURL = new URL[] {eineURL};
URLClassLoader clazLader = new URLClassLoader(reiURL);
c01 = clazLader.loadClass("test.SerObj");
return c01;
}
return super.resolveClass(desc);
}
};
SerObj obj = (SerObj) o.readObject();
The problem is in the last line. My current class version is placed in U:/MyJavaProj/bin/test/SerObj.class
My old class version is placed in U:/MyJavaProj/bin/test/oldversion/test/SerObj.class
In the last line I read the old class version but cast it to the new version :(
Has anyone some idea or maby any other aproach to add the versioning support for the serialization in Java?
i don't know if you trying to fix an existing problem, or write new functionality. if the latter, then i would look into using the advanced functionality of java serialization. java serialization supports various facilities for being able to handle multiple serial versions of the same class within one classloader (critically, though, you need to keep the serialVersionUID the same for all instances).
here's an example for handling an "incompatible" change, where an integer value was changed to a String value:
public class MyClass {
private static final int CUR_VERSION = 5;
private String _value;
private void readObject(ObjectInputStream in) {
// first value is always the serial version:
int dataVersion = in.readInt();
if(dataVersion == CUR_VERSION) {
// _value is a String
_value = in.readString();
} else {
// in older versions, _value was an int
_value = String.valueOf(in.readInt());
}
}
private void writeObject(ObjectOutputStream out) {
// always write version first
out.writeInt(CUR_VERSION);
out.writeString(_value);
}
}
The simple answer to this is don't change the serialVersionUID.
If you think you've made a serialization-incompatible change to the class, first read the Object Versioning part of the Object Serialization Specification to double-check that, as you probably haven't, and if you really have, change/add the writeObject/readObject methods so that they can cope with the old serialization format.
And certainly don't try to mess around with two versions of the class at runtime. It won't work.
You won't be able to perform the cast in the last line, because it's of a different class (which is not castable) - you might get a confusing message such as test.SerObj is not an instance of test.SerObj.
This arises from the fact that a class is essentially a java.lang.Class instance, which critically is unique within a given classloader. Even if you referenced the exact same *.class file, an instance of SerObj loaded by the default classloader is a different class from an instance of SerObj loaded by clazLader. Neither class could be cast to the other one.
Thus it is not going to be possible for the method to return SerObj if you need to use multiple classloaders to deserialise. (Besides - how could it, when the two class files could have arbitrary differences?)
One possible workaround is to define an interface that defines the behaviour which is fixed between versions of the class. If you set up the temporary classloader such that it can only load SerObj from the oldversion class file, and that it has the right parent classloader, then your old SerObj should still implement the interface class as defined by the root classloader.
In this was, both the new SerObj and old SerObj instances would be castable to the SerObjIface interface, and so you could declare your method to return that. And in fact if you want callers to deal with different yet similar classes (which is what different versions of "the same class" are), you should arguably be returning an interface for this anyway.
For reuse reasons I have wrapped my current serialization/deserialization services in an abstract generic class, which is compiled in a shared JAR across the project. I need to serialize objects to String
The class can be extended and a type can be specified for it in other JARs/WARs (yea, this is a web application).
When I made my first deserialization tests from within the same WAR it all worked fine, but now that I moved the abstract class into another JAR I get a ClassNotFoundError when deserializing.
The base class is structured as follows:
public abstract class ConverterBase<T extends Serializable> {
public final Object getAsObject(String str) {
//Use java.io serialization services from the base64 representation
try {
ByteArrayInputStream ba = new ByteArrayInputStream(decoder
.decodeBuffer(str));
try {
ObjectInputStream is = new ObjectInputStream(ba);
try {
Object ret = is.readObject();
return ret;
} finally {
is.close();
}
} finally {
ba.close();
}
} catch (Throwable ex) {
return null;
}
}
public final String getAsString(Object obj) {
//simply do the opposite
}
}
It is structured such a way in order to allow future changes impact all subclasses (ie. avoid base64, be more efficient...). For now, the java.io solution is a temporary implementation.
Then I have the following inside the same WAR:
public class MyPojo implements Serializable {
//Stuff
}
public final class MyPojoConverter extends ConverterBase<MyPojo> { }
The class that extends this one is in a different archive than the abstract class and is specialized on an type of that WAR.
What could I do to avoid that error?
Thank you
If you want to store the data as String, I would use XML or JSon to serialise your objects with a tool like XStream. These tools are not sensitive to change in packages, class names, parent classes, interfaces or method changes.
The ObjectInputStream must be able to access all the classes which are used in the serialized objects.
Normally it should be enough if the code creating the thread (e.g. its classloader) can load each class mentioned in the stream. Make sure this is the case. (I'm not really sure about your class loader structure in your application container. If you provide more information about this, maybe others can help.)
For more complicated cases, you can create a subclass and override resolveClass there.
This is probably a class loading issue (yeah, of course).
If I got you right, the problem occurs from within your WAR, i.e. a JSP or servlet.
Please provide your stack trace, I'm not sure, which class cannot be found.