18. oktober 2007 - 11:18Der er
14 kommentarer og 1 løsning
XStream: serialisering af objekter, der arver fra GUI
Jeg har fået til opgave at lave persistens af et større system via XStream. Undervejs i dette arbejde har der vist sig en problemstilling, som jeg gerne vil have hjælp til.
Lettere forenklet har jeg bl.a. disse klasser:
- class Unit (modelklasse, som holder oplysninger om en enhed + reference til UnitPosition + reference til ModelContainer) - class UnitPosition (GUI klasse, som arver fra java.awt.image.BufferedImage) - class ModelContainer (modelklasse, som fungerer som container for en lang række andre modelklasser)
Det er simpelt at serialisere "Unit", men problemet opstår ved, at jeg har brug for at serialisere x- og y-koordinaterne fra "UnitPosition" - og det er ikke holdbart blot at pudse XStream på den klasse også, da den gemmer en masse overflødig information fra java.awt.image.BufferedImage.
Hvordan får jeg serialiseret alle oplysninger fra "Unit" og kun nogle udvalgte oplysninger fra "UnitPosition"?
Jeg har forsøgt at lave en Converter til "UnitPosition", hvilket næsten får mig i mål... Lige indtil jeg skal gemme referencen til "ModelContainer". Hvordan serialiserer og de-serialiserer man en reference til en anden klasse via en Converter??
Hvis der er en nemmere måde at komme i mål på end at lave en Converter, er jeg naturligvis åben for andre løsninger!
Split den op i UnitPosition med ren data og UnitPositionComponent med ren GUI, Unit kan så nemt serialiseres og UnitPositionComponent har en constructor/setter/noget som tager en UnitPosition som argument.
Jeg er helt enig med dig i, at designet ikke er optimalt - og (heldigvis) er det ikke mit design :)
Jeg vil også gerne ændre det, men det vil desværre blive en ret stor opgave, da det er et omfattende projekt. Så inden jeg tager en dyb indånding og meddeler kunden, at designet skal laves om, bliver jeg nødt til at spørge, om der ikke er en genvej til målet?
Følgende er nok nemmere at udvide til flere felter:
public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext mc) { Problem o = new Problem(); while (hsr.hasMoreChildren()) { hsr.moveDown(); if(hsr.getNodeName().equals("good")) { o.setGood(hsr.getValue()); } hsr.moveUp(); } return o; }
Med mit kendskab til dine svar er det sandsynligvis mere end OK :) Men jeg har ikke nået til det endnu grundet pludselig omprioritering af opgaver, men jeg skal nok nå det inden for den kommende uge... Jeg vender tilbage - og beklager responstiden.
Tak for det meget udførlige eksempel, som kan bringes til at løse mit problem :) Men hvad nu hvis mit problem havde været en tand større - og jeg havde haft en reference til en modelklasse i min UnitPosition (svarende til hvis din Problem klasse)?
Altså hvordan serialiserer og de-serialiserer man en reference til en anden klasse via en Converter??
public class CustomSerialize { public static void main(String[] args) throws IOException { C1 o = new C1(); System.out.println(o); // standard XStream xs1 = new XStream(new DomDriver()); xs1.alias("C1", C1.class); xs1.alias("C2", C2.class); xs1.alias("C3", C3.class); OutputStream s1 = new FileOutputStream("C:\\standard.xml"); xs1.toXML(o, s1); s1.close(); XStream xs2 = new XStream(new DomDriver()); xs2.alias("C1", C1.class); xs2.alias("C2", C2.class); xs2.alias("C3", C3.class); InputStream s2 = new FileInputStream("C:\\standard.xml"); C1 o2 = (C1)xs2.fromXML(s2); s2.close(); System.out.println(o2); // let us wait try { Thread.sleep(1000); } catch (InterruptedException e) { } // special XStream xs3 = new XStream(new DomDriver()); xs3.alias("C1", C1.class); xs3.alias("C2", C2.class); xs3.alias("C3", C3.class); xs3.registerConverter(new ProblemConverter()); OutputStream s3 = new FileOutputStream("C:\\special.xml"); xs3.toXML(o, s3); s3.close(); XStream xs4 = new XStream(new DomDriver()); xs4.alias("C1", C1.class); xs4.alias("C2", C2.class); xs4.alias("C3", C3.class); xs4.registerConverter(new ProblemConverter()); InputStream s4 = new FileInputStream("C:\\special.xml"); C1 o4 = (C1)xs4.fromXML(s4); s4.close(); System.out.println(o4); } }
class ProblemConverter implements Converter { public void marshal(Object o, HierarchicalStreamWriter hsr, MarshallingContext mc) { if(o instanceof C1) { hsr.startNode("s"); mc.convertAnother(((C1)o).getS()); hsr.endNode(); hsr.startNode("o2"); mc.convertAnother(((C1)o).getO2()); hsr.endNode(); } else if(o instanceof C2) { hsr.startNode("s"); mc.convertAnother(((C2)o).getS()); hsr.endNode(); hsr.startNode("o3"); mc.convertAnother(((C2)o).getO3()); hsr.endNode(); } else if(o instanceof C3) { hsr.startNode("s"); mc.convertAnother(((C3)o).getS()); hsr.endNode(); } } public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext umc) { if(hsr.getNodeName().equals("C1")) { C1 o = new C1(); while (hsr.hasMoreChildren()) { hsr.moveDown(); if(hsr.getNodeName().equals("s")) { o.setS(hsr.getValue()); } else if(hsr.getNodeName().equals("o2")) { o.setO((C2)umc.convertAnother(null, C2.class)); } else if(hsr.getNodeName().equals("d")) { // ignore d - we will set it later } hsr.moveUp(); } o.setD(new Date()); return o; } else if(hsr.getNodeName().equals("o2")) { C2 o = new C2(); while (hsr.hasMoreChildren()) { hsr.moveDown(); if(hsr.getNodeName().equals("s")) { o.setS(hsr.getValue()); } else if(hsr.getNodeName().equals("o3")) { o.setO((C3)umc.convertAnother(null, C3.class)); } else if(hsr.getNodeName().equals("d")) { // ignore d - we will set it later } hsr.moveUp(); } o.setD(new Date()); return o; } else if(hsr.getNodeName().equals("o3")) { C3 o = new C3(); while (hsr.hasMoreChildren()) { hsr.moveDown(); if(hsr.getNodeName().equals("s")) { o.setS(hsr.getValue()); } else if(hsr.getNodeName().equals("d")) { // ignore d - we will set it later } hsr.moveUp(); } o.setD(new Date()); return o; } return null; } public boolean canConvert(Class c) { return c.equals(C1.class) || c.equals(C2.class) || c.equals(C3.class); } }
class C1 { private String s = "I am a C1"; private Date d = new Date(); private C2 o2 = new C2(); public String getS() { return s; } public void setS(String s) { this.s = s; } public Date getD() { return d; } public void setD(Date d) { this.d = d; } public C2 getO2() { return o2; } public void setO(C2 o2) { this.o2 = o2; } public String toString() { return s + "/" + d + "/" + o2; } }
class C2 { private String s = "I am a C2"; private Date d = new Date(); private C3 o3 = new C3(); public String getS() { return s; } public void setS(String s) { this.s = s; } public Date getD() { return d; } public void setD(Date d) { this.d = d; } public C3 getO3() { return o3; } public void setO(C3 o3) { this.o3 = o3; } public String toString() { return s + "/" + d + "/" + o3; } }
class C3 { private String s = "I am a C3"; private Date d = new Date(); public String getS() { return s; } public void setS(String s) { this.s = s; } public Date getD() { return d; } public void setD(Date d) { this.d = d; } public String toString() { return s + "/" + d; } }
Endnu engang mange tak for det udførlige svar! Jeg har et sidste spørgsmål... Hvis man nu forestiller sig at klassen C3 er meget stor - og at man vil være helt fint tilfreds med at den bliver serialiseret ned ligesom XStream ville gøre som standard, er det så muligt? Eller er man nødt til at gøre det eksplicit i marshal og unmarshal metoderne ligesom i dit eksempel?
PS: Jeg håber ikke, at du føler at jeg driver rovdrift med uddybende spørgsmål...
Jeg har løst mit eget spørgsmål og er stort set tilfreds med løsningen, som du kan se nedenfor. Dog er der den lille detalje, at C1 instansen ikke bliver sat på C3 af XStream i det specielle eksempel, medmindre jeg selv sætter den til sidst (som man kan se ved TODO linien). Kan dette gøres på en anden måde i Converteren??
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.