The Source for Java Technology Collaboration
User: Password:



Kohsuke Kawaguchi

Kohsuke Kawaguchi's Blog

JAXB 2.0 and Immutable Objects/Fields

Posted by kohsuke on February 22, 2005 at 05:59 PM | Comments (8)

In the last week, I wrote about how you can use JAXB 2.0 as an XML persistence engine, and I got a lot of comments about the support for immutable objects. So I'd like to elaborate on it today.

First, disclaimer. As usual, everything I write here can be changed by the time the spec becomes final. That's the whole point of writing this --- so that I can get more feedbacks and send them to the expert group.

It is often common to make some fields settable only by a constructor. This is done by the final keyword, and it usually improves the code readability. If you do this for all the fields of an object, it's called an immutable object, and it is also considered as a useful design pattern.

The following is a simple example of an immutable object:
public class Point {
    public final int x,y;
    public Point(int x,int y) {
       this.x = x;
       this.y = y;
    }
}
Unfortunately, the above class is not supported by the current release of the JAXB RI 2.0, and here's the various issues surrounding this.

Portability
First, creating an instance of an object without calling a constructor is not a committed part of the JDK core class library. It is doable through sun.misc.Unsafe, and that's what the Java serialization uses, but unless you are a part of the J2SE, you cannot reliably use this method. Even though, this undocumented API is actually available on many JREs, including IBM's and BEA's, it's still awkward for a spec to require a functionality that's not publicly committed. This is one reason why the current release of the JAXB RI doesn't support it.

Final field
Some XML data-binding works by injecting additional byte code into the class files. For them, setting a value to a final field is more difficult, if not impossible. Since a spec usually prefers to enable many different ways to implement, this is an issue.

I should mention that this is NOT a problem for the RI, because we can fall back to Java reflection. An user voidmain reported that this doesn't work in the current RI, and he's right that it doesn't. But this is just a bug in the RI, not a fundamental issue. This problem has been already fixed internally.

Constructor as a setter
Another user mgrev mentioned that perhaps we could invent an annotation to put on a parameter of the constructor, perhaps like this:
public class Point {
    public final int x,y;
    public Point(@XmlConstructor("x") int x, @XmlConstructor("y") int y) {
       this.x = x;
       this.y = y;
    }
}
While this works, it is somewhat awkward. It gets even more awkward with a case where a constructor does a non-trivial computation (for example, taking an ID and a JDBC RowSet and filling in the rest of the fields from this row set), although this is very common. You have to define a separate constructor just for making JAXB work.

It is also worth pointing out that the Java class files do not retain the names of the method parameters (only as debug information, and reliably accessible at the runtime), so you have to give those names manually to the annotations.



Those are the issues that surround this problem. Personally, I prefer to use sun.misc.Unsafe. That's the simplest for users, and it's available in the most of the JREs available today (I mean, what other JREs are there beside those three?), and even if this particular interface is not available in your favorite JRE, there still must be an equivalent functionality in it, or else Java serialization won't work. But this is a slippery slope.

Meanwhile, to work around this in the RI, you can define a private no-arg constructor, and eliminate all final fields, like this:
public class Point {
    public /*final*/ int x,y; // can't mark them final because of JAXB
    public Point(int x,int y) {
       this.x = x;
       this.y = y;
    }
    private Point() {} // for JAXB
    public int getX() { return x; }
    public int getY() { return y; }
}
If you have any thoughts, please let us hear it at the forum.

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Hi,
    I Was thinking a little more in the line of:


    public class Point {
    public final int x,y;

    @XmlConstructor("x", "y")
    public Point(int x, int y) {
    this.x = x;
    this.y = y;
    }
    }

    Which is a little less verbose.
    It basically tells the deserializer to first instanciate the object by using properties x and y and calling that constructor. The rest will be handled as usual.

    Now, I'm not good with annotations since we taget 1.4 a.t.m and I know very little about how you actually serialize and deserialize to XML with JAXB, so this might not be possible at all.

    Maybe your implementation is too unlike that of XmlEndoder and XMLDecoder for this to be possible at all.

    I just think it is important so support all types of objects so that you can use JAXB on any class rather than building the class to be serializable by JAXB.

    Cheers,
    Mikael Grev

    Posted by: mgrev on February 22, 2005 at 08:18 PM

  • Another thing,
    The x and y fields doedn't have to be public do they?

    Posted by: mgrev on February 22, 2005 at 08:22 PM

  • What about using some kind of client-written type converter?


    @XmlTypeConverter(PointConverter.class)
    public class Point {
    private final int x,y;
    //public getters would go here

    public Point(final int x, final int y)
    {
    this.x = x;
    this.y = y;
    }

    public static class PointConverter implements TypeConverter {
    public PointConverter() { } //must have a no-arg constructor

    public Point convert(//xml state passed in here)
    {
    //construct a Point from xml data and return it
    }
    }
    }


    The JAXB implementation would then simply be responsible
    for constructing an instance of the appropriate type converter, passing the xml representation to it (via DOM, SAX, or whatever), and adding the result to the result tree. The JAXB impl would be responsible for ensuring that only the neccessary portion of the document (the element in question and its contents, I would think) is sent to the type converter.

    How does this sound?

    Sean Reilly

    Posted by: seanreilly on February 23, 2005 at 07:19 AM

  • My appologies. PointConverter should implement TypeConverter<Point>. (The html tag eater doesn't like generic syntax ;-)

    Posted by: seanreilly on February 23, 2005 at 08:00 AM

  • Here's another reason why non-empty constructors need to be supported:

    Think of an object that MUST have an id, and it must not be null. You usually create this class by having the id in every constructor and checking that it is not null at the constructor(s). If you intruduce a no-arg constructor you now have to suround all id handling with null checks since you can not be certain is wont ever be null. Someone might have changed the xml and removed or changed the id element

    Something that is very clean and simple will if no-arg constructors are required have to be enforced with schemas and null checks.

    IMO that is too big of a price and it would limit the number of uses for JAXB. At least my company will not be able to use it, and will have to use XMLEncoder/decoder in the future as well, even though the XML produced for those are very verbose and kind of ugly.

    Cheers,
    Mikael Grev

    Posted by: mgrev on February 23, 2005 at 09:15 PM

  • This is slightly off topic: there is a compiler that supports immutables:

    https://pec.dev.java.net/nonav/compile/index.html

    And a RFE for immutables (on which the compiler is based):

    http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4617197

    Posted by: hlovatt on February 24, 2005 at 02:56 PM


  • Regarding the XmlTypeConverter idea: its all starting to sound a lot like (a better version of) Serialization. Java Serailization does have something similar with the "readReplace()" method, which basically allows the in-stream object to be silently replkaced with a completely different type of object directly after de-serialization.

    Its a pretty solid pattern, and works better than the method-based "readReplace()" pattern which is a bit cryptic.


    Brian Maso

    Posted by: jdcmember on March 02, 2005 at 04:29 PM

  • Hey,
    shouldn't XmlAdapter.unmarshal(String value) throw an Exception. This was the case with type customization in JAXB 1.0. Now, it seems as though you must manually edit the generated code, catch the exception, wrap it up in an exception that extends RuntimeException, and rethrow it.

    Posted by: brand1337 on March 24, 2005 at 02:24 PM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds