The JAXB Well Known Secret
Introduction
I rediscovered a library that Java offers to the masses. When I first read the specification, I was confused and thought I needed all these special tools to implement. I found recently that all was needed was some annotations and a POJO.
JAXB
JAXB stands for Java Architecture for XML Binding. This architecture allows a developer to turn the data from a class to be turned into a XML representation. This is called marshalling. The architecture also allows a developer to reverse the process turning a XML representation to be turned into a class. This is called unmarshalling. There are tools that can create Java classes from XML Schema files. The tool is called xjc. There is another tool that creates a xsd files by using schemagen.
Marshalling
Marshalling and unmarshalling happens several places in Java. The first I was exposed to this was RMI. Objects are sent over being used as parameters for remote method calls, hence the name Remote Method Invocation (RMI). Another place it happens is writing objects to a stream. The streams that implement this are ObjectOutputStream and ObjectInputStream. Another place that it happens are ORM classes. Another way of course is writing a XML representation of an instance. Classes that want to be marshalled need to implement Serializable and all of its member attributes need to implement Serializable too with the exception of classes going through JAXB. Serializable is a marker interface. It has no methods to implement but it shows that a class can be serialized or marshalled. An object that has been marshalled has had its data put into some persistent fashion. Unmarshalled objects have had their data read from a persistent state and joined with a class. This makes classpaths very important. For a fun fact, a valid entry in a classpath is http://ip:port/path/to/jar. I imagine some organizations make use of this by centralizing their jar files and the latest version is just a download away.
Example
I used maven and spring to do this example. The reason was not to make it more complicated but to make the code cleaner to read and focus more on using the technology that I am showing. The dependencies in the pom.xml file are below:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.8-b01</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>3.2.3.RELEASE</version> </dependency> </dependencies>
The wonderful thing about JAXB is that it uses POJOs. Contact.java is the central POJO class in the collection of three.
package org.mathison.jaxb.beans; import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Contact { private String lastName; private String firstName; private String middleName; private String jobTitle; @XmlElementWrapper(name = "addresses") @XmlElement(name = "address") private List<Address> addresses; @XmlElementWrapper(name = "phone-numbers") @XmlElement(name = "phone-number") private List<PhoneNumber> numbers; public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getMiddleName() { return middleName; } public void setMiddleName(String middleName) { this.middleName = middleName; } public String getJobTitle() { return jobTitle; } public void setJobTitle(String jobTitle) { this.jobTitle = jobTitle; } public List<Address> getAddresses() { return addresses; } public void setAddresses(List<Address> addresses) { this.addresses = addresses; } public List<PhoneNumber> getNumbers() { return numbers; } public void setNumbers(List<PhoneNumber> numbers) { this.numbers = numbers; } @Override public String toString() { return "Contact{" + "lastName=" + lastName + ", firstName=" + firstName + ", middleName=" + middleName + ", jobTitle=" + jobTitle + ", addresses=" + addresses + ", numbers=" + numbers + '}'; } @Override public int hashCode() { int hash = 3; hash = 23 * hash + (this.lastName != null ? this.lastName.hashCode() : 0); hash = 23 * hash + (this.firstName != null ? this.firstName.hashCode() : 0); hash = 23 * hash + (this.middleName != null ? this.middleName.hashCode() : 0); hash = 23 * hash + (this.jobTitle != null ? this.jobTitle.hashCode() : 0); hash = 23 * hash + (this.addresses != null ? this.addresses.hashCode() : 0); hash = 23 * hash + (this.numbers != null ? this.numbers.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Contact other = (Contact) obj; if ((this.lastName == null) ? (other.lastName != null) : !this.lastName.equals(other.lastName)) { return false; } if ((this.firstName == null) ? (other.firstName != null) : !this.firstName.equals(other.firstName)) { return false; } if ((this.middleName == null) ? (other.middleName != null) : !this.middleName.equals(other.middleName)) { return false; } if ((this.jobTitle == null) ? (other.jobTitle != null) : !this.jobTitle.equals(other.jobTitle)) { return false; } if(!listEquals(this.addresses, other.addresses)) { return false; } if(!listEquals(this.numbers, other.numbers)) { return false; } return true; } private boolean listEquals(List first, List second) { for(Object o: first) { if(!second.contains(o)) { return false; } } return true; } }
The main part to look at is the annotations. @XmlRootElement defines that this is the start of a class. @XmlAccessorType(XmlAccessType.FIELD) tells the architecture that the fields will be used to define the elements in the xml. The annotations can be put on the getters as well. If the annotation is not used, JAXB gets confused as to which to use. For instances where a list is present, @XmlElementWrapper is used to tell JAXB what the outer tag will be. For example, there are a list of addresses. The wrapper takes a parameter named “name” and it is filled with “addresses.” When the XML is rendered, there will be the tag “addresses” wrapped around the collection of addresses. The @XmlElement annotation is used when one wants to change the tag of a property. To come back to our address list, the annotation has redefined the addresses list to “address.” This will cause each address object to have a tag of “address” instead of “addresses” which is already taken up. The same pattern is used for numbers. The rest of the properties will be have tags that match the name of them. For example, lastName will be turned into the tag “lastName.” The other two POJOs, PhoneNumber.java and Address.java have public enum classes. Here is PhoneNumber.java:
package org.mathison.jaxb.beans; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlRootElement public class PhoneNumber { @XmlType(name="phone-type") public enum Type { HOME, WORK, HOME_FAX, WORK_FAX; } private Type type; private String number; public Type getType() { return type; } public void setType(Type type) { this.type = type; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } @Override public String toString() { return "PhoneNumber{" + "type=" + type + ", number=" + number + '}'; } @Override public int hashCode() { int hash = 7; hash = 37 * hash + (this.type != null ? this.type.hashCode() : 0); hash = 37 * hash + (this.number != null ? this.number.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final PhoneNumber other = (PhoneNumber) obj; if (this.type != other.type) { return false; } if ((this.number == null) ? (other.number != null) : !this.number.equals(other.number)) { return false; } return true; } }
The annotation of note is @XmlType. This tells JAXB that a class of limited number of values. It takes a name parameter. The last POJO also uses @XmlType to define its public enum class. It can be found at Address.java.
Putting It All Together
With all of this annotation and class definition, it is time to pull it all together into one main class. Here is App.java, the main class:
package org.mathison.jaxb.app; import java.io.StringReader; import java.io.StringWriter; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import org.mathison.jaxb.beans.Contact; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; public class App { public static void main( String[] args ) { ApplicationContext cxt = new GenericXmlApplicationContext("jaxb.xml"); Contact contact = cxt.getBean("contact", Contact.class); StringWriter writer = new StringWriter(); try { JAXBContext context = JAXBContext.newInstance(Contact.class); //create xml from an instance from Contact Marshaller m = context.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.marshal(contact, writer); String xml = writer.getBuffer().toString(); System.out.println(xml); //Take xml to Contact StringReader reader = new StringReader(xml); Unmarshaller u = context.createUnmarshaller(); Contact fromXml = (Contact)u.unmarshal(reader); System.out.println("Are the instances equivalent: " + contact.equals(fromXml)); } catch(Exception e){ e.printStackTrace(); } } }
First, the instance of contact is retrieved from the ApplicationContext. Second, an instance of JAXBContext is created with the Contact class as the root class. The context will analyze the class structure and create a context that can marshall or unmarshall the Contact, Address and PhoneNumber classes. In the next section, a marshaller is created from the JAXBContext. The property, Marshaller.JAXB_FORMATTED_OUTPUT is set to true. This creates a XML output that is formatted. If the property was not set, the XML would come out as one line of text. The the marshaller is called to marshall contact and be written to a StringWriter. Then the XML is printed to System.out. The output should look like the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <contact> <lastName>Mathison</lastName> <firstName>Daryl</firstName> <middleName>Bob</middleName> <jobTitle>Developer</jobTitle> <addresses> <address> <addressLine>123 Willow View</addressLine> <city>Cibolo</city> <state>TX</state> <type>HOME</type> <zipCode>78228</zipCode> </address> <address> <addressLine>411 Grieg</addressLine> <city>San Antonio</city> <state>TX</state> <type>WORK</type> <zipCode>78228</zipCode> </address> </addresses> <phone-numbers> <phone-number> <number>210-123-4567</number> <type>WORK</type> </phone-number> <phone-number> <number>210-345-1111</number> <type>HOME</type> </phone-number> </phone-numbers> </contact>
In the next section, the xml is unmarshalled back into an instance of Contact with its data. An Unmarshaller is created by the JAXBContext. Next, the unmarshaller is passed a StringReader with the just created XML as its contents. The Unmarshaller returns an Object that gets cast to a Contact. The original instance of Contact is tested against the new Contact instance to see if they are equivalent. The output should show:
Are the instances equivalent: true.
Summary
In this example, an instance of Contact was turned into XML and the resulting XML was turned back to a Contact instance with the help of JAXB. JAXB is an architecture that maps the state of an object into XML and maps XML back into an object.
References
- http://www.techrepublic.com/blog/programming-and-development/jaxb-20-offers-improved-xml-binding-in-java/498
- http://www.vogella.com/articles/JAXB/article.html
- http://en.wikipedia.org/wiki/JAXB
Reference: | The JAXB Well Known Secret from our JCG partner Daryl Mathison at the Daryl Mathison’s Java Blog blog. |