Serializing a simple object | |
<!-- padding --> |
In order to serialize an object to XML a series of annotations must be placed within that object. These annotations tell the persister how the object should be serialized. For example take the class shown below. Here there are three different annotations, one used to describe the name of the root element, one that describes an XML message element, and a final annotation for an id attribute. @Root public class Example { @Element private String text; @Attribute private int index; public Example() { super(); } public Example(String text, int index) { this.text = text; this.index = index; } public String getMessage() { return text; } public int getId() { return index; } } To serialize an instance of the above object a Persister is required. The persister object is then given an instance of the annotated object and an output result, which is a file in this example. Other output formats are possible with the persister object. Serializer serializer = new Persister(); Example example = new Example("Example message", 123); File result = new File("example.xml"); serializer.write(example, result); Once the above code is executed the object instance will have been transferred as an XML document to the specified file. The resulting XML file will contain the contents shown below. <example index="123"> <text>Example message</text> </example> As well as the capability of using the field an object name to acquire the XML element and attribute names explicit naming is possible. Each annotation contains a name attribute, which can be given a string providing the name of the XML attribute or element. This ensures that should the object have unusable field or method names they can be overridden, also if your code is obfuscated explicit naming is the only reliable way to serialize and deserialize objects consistently. An example of the previous object with explicit naming is shown below. @Root(name="root") public class Example { @Element(name="message") private String text; @Attribute(name="id") private int index; public Example() { super(); } public Example(String text, int index) { this.text = text; this.index = index; } public String getMessage() { return text; } public int getId() { return index; } } For the above object the XML document constructed from an instance of the object results in a different format. Here the XML element and attribute names have been overridden with the annotation names. The resulting output is shown below. <root id="123"> <message>Example message</message> </root> |
Reading a list of elements | |
<!-- padding --> |
In XML configuration and in Java objects there is often a one to many relationship from a parent to a child object. In order to support this common relationship an ElementList annotation has been provided. This allows an annotated schema class to be used as an entry to a Java collection object. Take the example shown below. @Root public class PropertyList { @ElementList private List<Entry> list; @Attribute private String name; public String getName() { return name; } public List getProperties() { return list; } } @Root public class Entry { @Attribute private String key; @Element private String value; public String getName() { return name; } public String getValue() { return value; } } From the above code snippet the element list annotation can be seen. The field type is reflectively instantiated as a matching concrete object from the Java collections framework, typically it is an array list, but can be any collection object if the field type declaration provides a concrete implementation type rather than the abstract list type shown in the above example. Below an XML document is shown that matches the schema class. Here each entry element will be deserialized using the declared entry class and inserted into the collection instance created. Once all entry objects have been deserialized the object instance contains a collection containing individual property objects. <propertyList name="example"> <list> <entry key="one"> <value>first value</value> </entry> <entry key="two"> <value>first value</value> </entry> <entry key="three"> <value>first value</value> </entry> <entry key="four"> <value>first value</value> </entry> </list> </propertyList> From the above example it can be seen that the entry details are taken from the generic type of the collection. It declares a list with the entry class as its generic parameter. This type of declaration is often not possible, for example if a specialized list contains more than one generic type which one is the correct type to use for deserialization or serialization. In such scenarios the type must be provided explicitly. Take the following example. @Root public class ExampleList { @ElementList(type=C.class) private SpecialList<A, B, C> list; public SpecialList<A, B, C> getSpecialList() { return list; } } In the above example the special list takes three generic parameters, however only one is used as the generic parameter for the collection. As can be seen an explicit declaration of which type to use is required. This can be done with the type attribute of the ElementList annotation. |
Dealing with an inline list of elements | |
<!-- padding --> |
When dealing with third party XML or with XML that contains a grouping of related elements a common format involves the elements to exist in a sequence with no wrapping parent element. In order to accomodate such structures the element list annotation can be configured to ignore the parent element for the list. For example take the following XML document. <propertyList> <name>example</name> <entry key="one"> <value>first value</value> </entry> <entry key="two"> <value>second value</value> </entry> <entry key="three"> <value>third value</value> </entry> </propertyList> In the above XML document there is a sequence of entry elements, however unlike the previous example these are not enclosed within a parent element. In order to achieve this the inline attribute of the ElementList annotation can be set to true. The following code snippet demonstrates how to use the inline attribute to process the above XML document. @Root public class PropertyList { @ElementList(inline=true) private List<Entry> list; @Element private String name; public String getName() { return name; } public List getProperties() { return list; } } There are a number of conditions for the use of the inline element list. Firstly, each element within the inline list must be placed one after another. They cannot be dispersed in between other elements. Also, each entry type within the list must have the same root name, to clarify take the following example. package example.demo; @Root public class Entry { @Attribute protected String key; @Element protected String value; public String getKey() { return key; } } public class ValidEntry extends Entry { public String getValue() { return value; } } @Root public class InvalidEntry extends Entry { public String getValue() { return value; } } @Root(name="entry") public class FixedEntry extends InvalidEntry { } All of the above types extend the same base type, and so all are candidates for use with the <propertyList> <name>example</name> <entry key="one" class="example.demo.ValidEntry"> <value>first value</value> </entry> <entry key="two" class="example.demo.FixedEntry"> <value>second value</value> </entry> <entry key="three" class="example.demo.Entry"> <value>third value</value> </entry> </propertyList> All of the above entry elements within the inline list contain the same XML element name. Also each type is specified as a subclass implementation of the root |
Reading an array of elements | |
<!-- padding --> |
As well as being able to deserialize elements in to a collection arrays can also be serialized and deserialized. However, unlike the @Root public class AddressBook { @ElementArray private Address[] addresses; @ElementArray private String[] names; @ElementArray private int[] ages; public Address[] getAddresses() { return addresses; } public String[] getNames() { return names; } public int[] getAges() { return ages; } } @Root public class Address { @Element(required=false) private String house; @Element private String street; @Element private String city; public String getHouse() { return house; } public String getStreet() { return street; } public String getCity() { return city; } } For the above object both primitive arrays require an entry attribute, this is because primitives can not be annotated with the Root annotation. The entry attribute tells the persister than an extra XML element is required to wrap the entry. This entry element can also be applied to serializable objects that have the <addressBook> <addresses length="3"> <address> <house>House 33</house> <street>Sesame Street</street> <city>City</city> </address> <address> <street>Some Street</street> <city>The City</city> </address> <address> <house>Another House</house> <street>My Street</street> <city>Same City</city> </address> </addresses> <names length="3"> <string>Jonny Walker</string> <string>Jack Daniels</string> <string>Jim Beam</string> </names> <ages length="3"> <int>30</int> <int>42</int> <int>31</int> </ages> </properties> Looking at the above XML it can be seen that each entity within an array index is named the same as its type. So a string is wrapped in a 'string' element and an int is wrapped in an 'int' element. This is done because the default name for the ElementArray annotation is its type name, unless the Root annotation is used with a name. This can be overridden by providing an explicit entry name for the array. For example take the simple object below which contains an array of names as string objects. @Root public class NameList { @ElementArray(entry="name") private String[] names; public String[] getNames() { return names; } } For the above XML the following document is a valid representation. Notice how each of the names within the XML document is wrapped in a 'name' element. This element name is taken from the annotation provided. <nameList> <names length="3"> <name>Jonny Walker</name> <name>Jack Daniels</name> <name>Jim Beam</name> </names> </nameList> |
Adding text and attributes to elements | |
<!-- padding --> |
As can be seen from the previous example annotating a primitive such as a String with the Element annotation will result in text been added to a names XML element. However it is also possible to add text to an element that contains attributes. An example of such a class schema is shown below. @Root public class Entry { @Attribute private String name; @Attribute private int version; @Text private String value; public int getVersion() { return version; } public String getName() { return name; } public String getValue() { return value; } } Here the class is annotated in such a way that an element contains two attributes named version and name. It also contains a text annotation which specifies text to add to the generated element. Below is an example XML document that can be generated using the specified class schema. <entry version='1' name='name'> Some example text within an element </entry> The rules that govern the use of the Text annotation are that there can only be one per schema class. Also, this annotation cannot be used with the |
Dealing with map objects | |
<!-- padding --> |
Although it is possible to deal with most repetitive XML elements within documents using element lists it is often more convenient to use a <properties> <property key="one">first value</property> <property key="two">second value</property> <property key="three">third value</property> <name>example name</name> </properties> In the above XML document the sequence of properties elements can be used to describe a map of strings, where the key attribute acts as the key for the value within the property element. The following code snipped demonstrates how to use the ElementMap annotation to process the above XML document. @Root(name="properties") public class PropertyMap { @ElementMap(entry="property", key="key", attribute=true, inline=true) private Map<String, String> map; @Element private String name; public String getName() { return name; } public Map<String, Entry> getMap() { return map; } } |
Loose object mapping | |
<!-- padding --> |
An important feature for any XML tool is the ability to sift through the source XML to find particular XML attributes an elements of interest. It would not be very convinient if you had to write an object that accurately mapped every attribute an element in an XML document if all you are interested in is perhaps an element and several attributes. Take the following XML document. <contact id="71" version="1.0"> <name> <first>Niall</first> <surname>Gallagher</surname> </name> <address> <house>House 33</house> <street>Sesame Street</street> <city>City</city> </address> <phone> <mobile>123456789</mobile> <home>987654321</home> </phone> </example> If my object only required the some of the details of the specified contact, for example the phone contacts and the name then it needs to be able to ignore the address details safely. The following code shows how this can be done by setting strict to false within the Root annotation. @Root(strict=false) public class Contact { @Element private Name name; @Element private Phone phone; public String getName() { return name.first; } public String getSurname() { return name.surname; } public String getMobilePhone() { return phone.mobile; } public String getHomePhone() { return phone.home; } @Root private static class Name { @Element private String first; @Element private String surname; } @Root private static class Phone { @Element(required=false) private String mobile; @Element private String home; } } The above object can be used to parse the contact XML source. This simple ignores any XML elements or attributes that do not appear in the class schema. To further clarify the implementation of loose mappings take the example shown below. This shows how the entry object is deserialized from the above document, which is contained within a file. Once deserialized the object values can be examined. Serializer serializer = new Persister(); File source = new File("contact.xml"); Contact contact = serializer.read(Contact.class, source); assert contact.getName().equals("Niall"); assert contact.getSurname().equals("Gallagher"); assert contact.getMobilePhone().equals("123456789"); assert contact.getHomePhone().equals("987654321"); Should there be more than a single object that requires loose mapping then using the Root annotation might not be the ideal solution. In such a scenario the persister itself can be asked to perform loose mapping. Simply pass a boolean to the read method indicating the type of mapping required. By default the persister uses strict mapping, which can be overridden on an object by object basis using the Root annotation, as shown in the above example. However, this default can be overridden as can be seen in the code snippet below. Contact contact = serializer.read(Contact.class, source, false); Here the boolean passed to the overridden read method tells the serializer to perform a loose mapping. There is no need to specify anything in the annotations, the serializer will simply map every object loosely. This can be a much more convenient way to perform loose mapping, as you only need to annotate your objects with the elements or attributes you are interested in, all other elements and attributes will be ignored during deserialization. Such a solution is best suited to external XML documents where your annotated objects do not define the schema. |
Default object serialization | |
<!-- padding --> |
If an object grows large it often becomes tedious to annotate each field or method that needs to be serialized. In such scenarios the Default annotation can be used. This will apply default annotations to either the fields or methods of an object that is to be serialized. To specify whether it is the fields or methods that will have default annotations, the DefaultType enumeration can be used. Take the code snippet below, this shows two objects with default annotations, one that will apply defaults to the object fields, and one that will apply defaults to the Java Bean methods of the object. @Root @Default(DefaultType.FIELD) public class OrderItem { private Customer customer; private String name; @Attribute private double price; @Transient private String category; public String getName() { return name; } public Customer getCustomer() { return customer; } } @Root @Default(DefaultType.PROPERTY) private class Customer { private String name; private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } } In the above object the Transient annotation is used to specify that even though default annotations should be applied to the objects fields, the field annotated as transient should not be serialized. Below is the XML that could be produced using the above classes, notice that if defaults apply to Java Bean methods, the fields will not be defaulted, instead they will be ignored. <orderItem price="10.99"> <customer> <name>Elvis Presley</name> </customer> <name>IRT452</name> </orderItem> From the above XML it is obvious, that defaults apply to only those fields or methods requested. If a field or method already has an annotation, that is the annotation that is used. If a field or method is to be omitted from serialization then it can be marked as transient. Applying defaults to an object, can often lead to a cleaner object structure, and makes it much easier to make objects serializable. |
Example using template filters | |
<!-- padding --> |
Another very powerful feature with this XML serialization framework is the ability to use templating when deserializing an XML document. This allows values within elements and attributes to use template variables that can be replaced using a Filter object. The simplest filter object is the map filter, which allows the user to place a Java map within the filter object exposing the key value pairs to the templating system. The template system can now use the filter to find replacement values for template variables within the XML document. To clarify take the following example. @Root public class Layout { @Element private String path; @Element private String user; @Attribute private int id; public String getPath() { return path; } public String getUser() { return user; } public int getId() { return id; } } The above object has declared two elements and an attribute to be deserialized from an XML document. These values are typically static values within the XML source. However using a template variable syntax the deserialization process will attempt to substitute the keys with values from the filter. Take the XML document below with two template variables declared ${home.path} and ${user.name}. <layout id="123"> <path>${home.path}</path> <user>${user.name}</user> </layout>To ensure that these values can be replaced with user specified mappings a map filter can be used. Below is an example of how to create a persister that can be given user specified key value pairs. Here the above XML source is deserialized from a file and the annotated fields are given filter mappings if there is a mapping specified. Map map = new HashMap(); map.put("home.path", "/home/john.doe"); map.put("user.name", "john.doe"); Filter filter = new MapFilter(map); Serializer serializer = new Persister(filter); File source = new File("layout.xml"); Layout layout = serializer.read(Layout.class, source); assert layout.getPath().equals("/home/john.doe"); assert layout.getUser().equals("john.doe"); As well as the map filter there are several stock filters which can be used to substitute template variables with OS environment variables and JVM system properties. Also several template variables can exist within the values. For example take the following XML document, which could be used in the above example given that the mappings for ${first.name} and ${second.name} were added to the map filter. <layout id="123"> <path>/home/${first.name}.${second.name}</path> <user>${first.name}.${second.name}</user> </layout> |
Receiving persister callbacks | |
<!-- padding --> |
Of critical importance to the serialization and deserialization process is that the objects have some control or participation in the process. It is no good to have the persister deserialize the object tree from an XML document only to see that the data is not valid or that further data structures need to be created in many of the deserialized objects. To allow objects to participate in the deserialization process two annotations can be used, these are the Validate and Commit annotations. Both are involved in the deserialization process (not the serialization process) and are called immediately after an object has been deserialized. Validation is performed first, and if the deserialized object contains a method annotated with the validate annotation it is invoked. This allows the object to perform validation of its fields, if the object requirements are met the method returns quietly, if they are not met the object can throw an exception to terminate the deserialization process. The commit method is invoked in much the same way, the persister looks for a method marked with the commit annotation, if one exists it is invoked. However, unlike the validate method the commit method is typically used to build further data structures, for example hash tables or trees. Below is an example of an object making use of these annotations. @Root public class PropertyMap { private Map<String, Property> map; @ElementList private List<Property> list; public PropertyMap() { this.map = new HashMap<String, Entry>(); } @Validate public void validate() { List<String> keys = new ArrayList<String>(); for(Property entry : list) { String key = entry.getKey(); if(keys.contains(key)) { throw new PersistenceException("Duplicate key %s", key); } keys.put(key); } } @Commit public void build() { for(Property entry : list) { insert(entry); } } public void insert(Property entry) { map.put(entry.getName(), entry); } public String getProperty(String name) { return map.get(name).getValue(); } }The above object deserializes a list of property objects into a list. Once the property objects have been deserialized they are validated by checking that an entry with a specific key exists only once. After the validation process has completed the commit method is invoked by the persister, here the object uses the deserialized property object to build a hash table containing the property values keyed via the property key. Below is how the above object would be represented as an XML document. <properties> <list> <entry key="one"> <value>first value</value> </entry> <entry key="two"> <value>first value</value> </entry> <entry key="three"> <value>first value</value> </entry> </list> </properties>As well as annotations involved in the deserialization process there are annotations that can be used to receive persister callbacks for the serialization process. Two annotations can be used, they are the Persist and Complete methods. To receive persister callbacks the methods must be no argument methods marked with the appropriate annotations.
The persist method is invoked before the serialization of the object. This allows the object to prepare in some implementation specific way for the serialization process. This method may throw an exception to terminate the serialization process. Once serialization has completed the complete method is invoked. This allows the object to revert to its previous state, that is, to undo what the persist method has done. Below is an example of how these annotations can be used. @Root public class MailMessage { @Attribute private Stirng format; @Element private String encoded; private byte[] content; private Encoder encoder; public MailMessage() { this.encoder = new Encoder(); } public void setEncoding(String format) { this.format = format; } public String getEncoding() { return format; } public void setMessage(byte[] content) { this.content = content; } public byte[] getMessage() { return content; } @Commit public void commit() { decoded = encoder.decode(encoded, format); encoded = null; } @Persist public void prepare() { encoded = encoder.encode(decoded, format); } @Complete public void release() { encoded = null; } } The above example illustrates how the persist and complete methods can be used in a scenario where the serialization process needs to encode a byte array into a specific encoding format. Before the object is persisted the persistable field is set to an encoded string. When serialization has completed the encoded value is nulled to free the memory it holds. This example is somewhat contrived however it effectively demonstrates how the annotations can be used. Below is an example of what the XML document should look like. <mailMessage format="base64"> U2ltcGxlIGlzIGFuIFhNTCBzZXJpYWxpemF0aW9uIGZyYW1ld29yayBmb3IgSmF2YS4gSXRzIGdv YWwgaXMgdG8gcHJvdmlkZSBhbiBYTUwgZnJhbWV3b3JrIHRoYXQgZW5hYmxlcyByYXBpZCBkZXZl bG9wbWVudCBvZiBYTUwgY29uZmlndXJhdGlvbiBhbmQgY29tbXVuaWNhdGlvbiBzeXN0ZW1zLiBU aGlzIGZyYW1ld29yayBhaWRzIHRoZSBkZXZlbG9wbWVudCBvZiBYTUwgc3lzdGVtcyB3aXRoIG1p bmltYWwgZWZmb3J0IGFuZCByZWR1Y2VkIGVycm9ycy4gVGhlIGZyYW1ld29yayBib3Jyb3dzIGlk ZWFzIGFuZCBjb25jZXB0cyBmcm9tIGV4aXN0aW5nIFhNTCB0b29scyBzdWNoIGFzIEMjIFhNTCBz ZXJpYWxpemF0aW9uIGFuZCBvdGhlciBwcm9wcmlldGFyeSBmcmFtZXdvcmtzIGFuZCBjb21iaW5l cyB0aG9zZSBpZGVhcyByZXN1bHRpbmcgaW4gYSBzaW1wbGUgeWV0IGV4dHJlbWVseSBwb3dlcmZ1 bCB0b29sIGZvciB1c2luZyBhbmQgbWFuaXB1bGF0aW5nIFhNTC4gQmVsb3cgaXMgYSBsaXN0IG9m IHNvbWUgb2YgdGhlIGNhcGFiaWxpdGllcyBvZiB0aGUgZnJhbWV3b3JrLiA= </mailMessage> For the above XML message the contents can be serialized and deserialized safely using persister callbacks. The object can prepare itself before serialization by encoding the contents of the message to the encoding format specified. Once it has been encoded and serialized any resources created for serialization can be released. |
Serializing with CDATA blocks | |
<!-- padding --> |
At times it is nessecary to serialize large text and element data values. Such values may also contain formatting that you wish to preserve. In such situations it is often best to wrap the values within XML CDATA blocks. The CDATA block can contain XML characters and formatting information which will not be modified by other XML parsers. For example take the following XML source. <query type="scrape" name="title"> <data><![CDATA[ <news> { for $text in .//B return $text } </news> ]]></data> </query> The above XML there is an embedded XQuery expression which is encapsulated within a CDATA block. Such a configuration allows the XQuery expression to exist within the XML document without any need to escape the XML characters. Also, if the XQuery expression was very large then this form of encoding would provide better performance. In order to ensure that the data is maintained within the CDATA block the following could be used. @Root public class Query { @Attribute private String scrape; @Attribute private String title; @Element(data=true) private String data; public String getData() { return data; } public String getTitle() { return title; } public String getScrape() { return scrape; } } Here the Element annotation has the data attribute set to true. This tells the serialization process that any value stored within the data field must be written to the resulting XML document within a CDATA block. The data attribute can be used with the Text, ElementArray, and ElementList annotations also. |
Using XML namespaces | |
<!-- padding --> |
Namespaces are used to qualify an element or an attribute in an XML document. In order to use namespaces the Namespace annotation can be used. This allows the declaration of the namespace prefix and reference, often called the namespace URI. Namespace annotations can be used on fields, methods, and even classes. For example take the XML snippet below. <parent xmlns="/parent"> <pre:child xmlns:pre="/child"> <name>John Doe</name> <address xmlns=""> <street>Sin City</street> </address> </pre:child> </parent> In the above XML document, the root element is qualified with a default namespace. A default namespace is a namespace that is inherited by all child elements, for further details see Section 6.2 of the namespaces in XML 1.0 specification. In order to annotate a field, method, or class with a default namespace the Namespace annotation can be declared using only the reference attribute. For example, see the annotated class below that produces the above XML. @Root @Namespace(reference="/parent") public class Parent { @Element @Namespace(reference="/child", prefix="pre") private Child child; public Child getChild() { return child; } } @Root public class Child { @Element private String name; @Element @Namespace private Address address; public Address getAddress() { return address; } } @Root public class Address { @Element private String street; public String getStreet() { return street; } } The above code snippet also shows an annotation with both the namespace reference and prefix attributes declared. Such an annotation declaration will result in a namespace qualified with a prefix. As can be seen in the XML example a prefixed namespace qualifies the XML element with a string prefix followed by a colon. Should your document require more than one namespace declared in a single element the NamespaceList annotation can be used. This allows multiple namespaces to be declared in a single element. Declaring multiple namespaces in a single element can produce a cleaner more readable XML document. Take the XML snippet below from the namespaces in XML 1.0 specification, which shows an element with multiple namespaces. <?xml version="1.0"?> <book xmlns="urn:loc.gov:books" xmlns:isbn="urn:ISBN:0-395-36341-6"> <title>Cheaper by the Dozen</title> <isbn:number>1568491379</isbn:number> </book> This XML snippet shows two namespaces declared in the root element. Here the root element will be qualified with the default namespace, and child elements can if desired be qualified by the prefixed namespace. To illustrate how such namespace declarations can be done, see the annotated class below. @Root @NamespaceList({ @Namespace(reference="urn:loc.gov:books") @Namespace(reference="urn:ISBN:0-395-36341-6", prefix="isbn") }) public class Book { @Element @Namespace(reference="urn:ISBN:0-395-36341-6") private String number; @Element private String title; public String getTitle() { return title; } } As can be seen above, there is no need to redeclare the prefix attribute once it has already been declared. This allows the annotation declarations to be less verbose and ensures a consistent use of a prefix for a given namespace reference. Also, once a namespace has been declared and is in scope then it will not be declared a second time in the resulting XML, this ensures the resulting XML document does not contain redundant namespace declarations. |
Resolving object reference cycles | |
<!-- padding --> |
When there are cycles in your object graph this can lead to recursive serialization. However it is possible to resolve these references using a stock strategy. The CycleStrategy maintains the object graph during serialization and deserialization such that cyclical references can be traced and resolved. For example take the following object relationships. @Root public class Parent { private Collection<Child> children; private String name; @Attribute public String getName() { return name; } @Attribute public void setName(String name) { this.name = name; } @Element public void setChildren(Collection<Child> children) { this.children = children; } @Element public Collection<Child> getChildren() { return children; } public void addChild(Child child) { children.add(child); } } @Root public class Child { private Parent parent; private String name; public Child() { super(); } public Child(Parent parent) { this.parent = parent; } @Attribute public String getName() { return name; } @Attribute public void setName(String name) { this.name = name; } @Element public Parent getParent() { return parent; } @Element public void setParent(Parent parent) { this.parent = parent; } } In the above code snippet the cyclic relation ship between the parent and child can be seen. A parent can have multiple children and a child can have a reference to its parent. This can cause problems for some XML binding and serialization frameworks. However this form of object relationship can be handled seamlessly using the CycleStrategy object. Below is an example of what a resulting XML document might look like. <parent name="john" id="1"> <children> <child id="2" name="tom"> <parent ref="1"/> </child> <child id="3" name="dick"> <parent ref="1"/> </child> <child id="4" name="harry"> <parent ref="1"/> </child> </children> </parent> As can be seen there are two extra attributes present, the id attribute and the ref attribute. These references are inserted into the serialized XML document when the object is persisted. They allow object relationships and references to be recreated during deserialization. To further clarify take the following code snippet which shows how to create a persister that can handle such references. Strategy strategy = new CycleStrategy("id", "ref"); Serializer serializer = new Persister(strategy); File source = new File("example.xml"); Parent parent = serializer.read(Parent.class, source); The strategy is created by specifying the identity attribute as id and the refering attribute as ref. For convinience these attributes have reasonable defaults and the no argument constructor can be used to create the strategy. Although the example shown here is very simple the cycle strategy is capable of serializing and deserializing large and complex relationships. |
Reusing XML elements | |
<!-- padding --> |
As can be seen from using the CycleStrategy in the previous section object references can easily be maintained regardless of complexity. Another benifit of using the cycle strategy is that you can conviniently reuse elements when creating configuration. For example take the following example of a task framework. @Root public class Workspace { @Attribute private File path; @Attribute private String name private File getPath() { return path; } private String getName() { return name; } } @Root public abstract Task { @Element private Workspace workspace; public abstract void execute() throws Exception; } public class DeleteTask extends Task { @ElementList(inline=true, entry="resource") private Collection<String> list; public void execute() { File root = getPath(); for(String path : list) { new File(root, path).delete(); } } } public class MoveTask extends Task { @ElementList(inline=true, entry="resource") private Collection<String> list; @Attribute private File from; public void execute() { File root = getPath(); for(String path : list) { File create = new File(root, path); File copy = new File(from, path); copy.renameTo(create); } } } The above code snippet shows a very simple task framework that is used to perform actions on a workspace. Each task must contain details for the workspace it will perform its specific task on. So, making use of the cycle strategy it is possible to declare a specific object once, using a know identifier and referencing that object throughout a single XML document. This eases the configuration burden and ensures that less errors can creap in to large complex documents where may objects are declared. <job> <workspace id="default"> <path>c:\workspace\task</path> </workspace> <task class="example.DeleteTask"> <workspace ref="default"/> <resource>output.dat</resource> <resource>result.log</resource> </task> <task class="example.MoveTask"> <workspace ref="default"/> <from>c:\workspace\data</from> <resource>input.xml</resource> </task> </job> |
Using utility collections | |
<!-- padding --> |
For convinience there are several convinience collections which can be used. These collections only need to be annotated with the ElementList annotation to be used. The first stock collection resembles a map in that it will accept values that have a known key or name object, it is the Dictionary collection. This collection requires objects of type Entry to be inserted on deserialization as this object contains a known key value. To illustrate how to use this collection take the following example. @Root public class TextMap { @ElementList(inline=true) private Dictionary<Text> list; public Text get(String name) { return list.get(name); } } @Root public class Text extends Entry { @Text public String text; public String getText() { return text; } } The above objects show how the dictionary collection is annotated with the element list annotation. The containing object can not serialize and deserialize entry objects which can be retrieve by name. For example take the following XML which shows the serialized representation of the text map object. <textMap> <text name="name">Niall Gallagher</text> <text name="street">Seasme Street</text> <text name="city">Atlantis</text> </textMap> Each text entry deserialized in to the dictionary can now be acquired by name. Although this offers a convinient map like structure of acquring objects based on a name there is often a need to match objects. For such a requirement the Resolver collection can be used. This offers a fast pattern matching collection that matches names or keys to patterns. Patterns are deserialized within Match objects, which are inserted in to the resolver on deserialization. An example of the resolver is shown below. @Root private static class ContentType extends Match { @Attribute private String value; public ContentType() { super(); } public ContentType(String pattern, String value) { this.pattern = pattern; this.value = value; } } @Root private static class ContentResolver implements Iterable { @ElementList private Resolver<ContentType> list; @Attribute private String name; public Iterator<ContentType> iterator() { return list.iterator(); } public ContentType resolve(String name) { return list.resolve(name); } } The above content resolver will match a string with a content type. Such an arrangement could be used to resolve paths to content types. For example the following XML document illustrates how the resolver could be used to match URL paths to content types for a web application. <contentResolver name="example"> <contentType pattern="*.html" value="text/html"/> <contentType pattern="*.jpg" value="image/jpeg"/> <contentType pattern="/images/*" value="image/jpeg"/> <contentType pattern="/log/**" value="text/plain"/> <contentType pattern="*.exe" value="application/octetstream"/> <contentType pattern="**.txt" value="text/plain"/> <contentType pattern="/html/*" value="text/html"/> </contentResolver> Although the resolver collection can only deal with wild card characters such as * and ? it is much faster than resolutions performed using Java regular expressions. Typically it is several orders of magnitude faster that regular expressions, particularly when it is used to match reoccuring values, such as URI paths. |
Object substitution | |
<!-- padding --> |
Often there is a need to substitute an object into the XML stream either during serialization or deserialization. For example it may be more convinient to use several XML documents to represent a configuration that can be deserialized in to a single object graph transparently. For example take the following XML. <registry> <import name="external.xml" class="example.ExternalDefinition"/> <define name="blah" class="example.DefaultDefinition"> <property key="a">Some value</property> <property key="b">Some other value</property> </define> </registry> In the above XML document there is an import XML element, which references a file external.xml. Given that this external file contains further definitions it would be nice to be able to replace the import with the definition from the file. In such cases the Resolve annotation can be used. Below is an example of how to annotate your class to substitute the objects. @Root private class Registry { @ElementList(inline=true) private Dictionary<Definition> import; @ElementList(inline=true) private Dictionary<Definition> define; public Definition getDefinition(String name) { Definition value = define.get(name); if(value == null) { value = import.get(name); } return value; } } public interface Definition { public String getProperty(String key); } @Root(name="define") public class DefaultDefinition implements Definition { @ElementList(inline=true) private Dictionary<Property> list; public String getProperty(String key) { return list.get(key); } } @Root(name="import") public class ExternalDefinition implements Definition { @Element private File name; public String getProperty(String key) { throw new IllegalStateException("Method not supported"); } @Resolve public Definition substitute() throws Exception { return new Persister().read(Definition.class, name); } } Using this form of substitution objects can be replaced in such a way that deserialized objects can be used as factories for other object instances. This is similar to the Java serialization concept of |
Serializing Java language types | |
<!-- padding --> |
A common requirement of any serialization framework is to be able to serialize and deserialize existing types without modification. In particular types from the Java class libraries, like dates, locales, and files. For many of the Java class library types there is a corrosponding Transform implementation, which enables the serialization and deserialization of that type. For example the @Root public class DateList { @Attribute private Date created; @ElementList private List<Date> list; public Date getCreationDate() { retrun created; } public List<Date> getDates() { return list; } } Here the date object is used like any other Java primitive, it can be used with any of the XML annotations. Such objects can also be used with the CycleStrategy so that references to a single instance within your object graph can be maintained throughout serialization and deserialization operations. Below is an example of the XML document generated. <dateList created="2007-01-03 18:05:11.234 GMT"> <list> <date>2007-01-03 18:05:11.234 GMT</date> <date>2007-01-03 18:05:11.234 GMT</date> </list> </dateList>Using standard Java types, such as the Date type, can be used with any of the XML annotations. The set of supported types is shown below. Of particular note are the primitive array types, which when used with the ElementArray annotation enable support for multidimentional arrays.
char char[] java.lang.Character java.lang.Character[] int int[] java.lang.Integer java.lang.Integer[] short short[] java.lang.Short java.lang.Short[] long long[] java.lang.Long java.lang.Long[] double double[] java.lang.Double java.lang.Double[] byte byte[] java.lang.Byte java.lang.Byte[] float float[] java.lang.Float java.lang.Float[] boolean boolean[] java.lang.Boolean java.lang.Boolean[] java.lang.String java.lang.String[] java.util.Date java.util.Locale java.util.Currency java.util.TimeZone java.util.GregorianCalendar java.net.URL java.io.File java.math.BigInteger java.math.BigDecimal java.sql.Date java.sql.Time java.sql.Timestamp For example take the following code snippet, here points on a graph are represented as a multidimentional array of integers. The array is annotated in such a way that it can be serialized and deserialized seamlessly. Each index of the array holds an array of type @Root public class Graph { @ElementArray(entry="point") private int[][] points; public Graph() { super(); } @Validate private void validate() throws Exception { for(int[] array : points) { if(array.length != 2) { throw new InvalidPointException("Point can not have %s values", array.length); } } } public int[][] getPoints() { return points; } } For the above code example the resulting XML generated would look like the XML document below. Here each index of the element array represents an array of integers within the comma separated list. Such structures also work well with the cycle strategy in maintaining references. <graph> <points length="4"> <point>3, 5</point> <point>5, 6</point> <point>5, 1</point> <point>3, 2</point> </points> </graph> |
Styling serialized XML | |
<!-- padding --> |
In order to serialize objects in a consistent format a Style implementation can be used to format the elements and attributes written to the XML document. Styling of XML allows both serialization and deserialization to be performed. So once serialized in a styled XML format you can deserialize the same document back in to an object. @Root public class PersonProfile { @Attribute private String firstName; @Attribute private String lastName; @Element private PersonAddress personAddress; @Element private Date personDOB; public Date getDateOfBirth() { return personDOB; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public PersonAddress getAddress() { return personAddress; } } @Root public class PersonAddress { @Element private String houseNumber; @Element private String streetName; @Element private String city; public String getHouseNumber() { return houseNumber; } public String getStreetName() { return streetName; } public String getCity() { return city; } } For example, taking the above annotated objects. An instance of the person profile can be serialized in to an XML document that is styled with a hyphenated format. This produces a consistently formated result which is just as deserializable as a serialization that is not styled. <person-profile first-name="Niall" last-name="Gallagher"> <person-DOB>10/10/2008</person-DOB> <person-address> <house-number>10</house-number> <street-name>Sesame Street</street-name> <city>Disney Land</city> </person-address> </person-profile> In order to serialize an object in a styled format either the HyphenStyle or CamelCaseStyle can be used. If neither suits one can always be implemented. Also, for convenience any of the elements or attributes can be overridden with a specific string by setting it to the style instance. The code snippet below shows how to serialize the object in the hyphenated style above. Style style=new HyphenStyle(); Format format = new Format(style); Serializer serializer = new Persister(format); serializer.write(personDetail, file); |
Version tolerant serialization | |
<!-- padding --> |
In order to serialize objects in a version tolerant format a Version annotation can be introduced to the class. This will allow a later, modified class to be read from XML generated by the original class. For example take the following code snippet showing an annotated class. @Root @Namespace(prefix="p", reference="/person") public class Person { @Attribute private String name; @Element private String height; @Element private String weight; public String getName() { return name; } public String getHeight() { return height; } public String getWeight() { return weight; } } The above annotated class schema will generate XML in a format compatible with that class. For example, a serialization of the class could result in the following XML snippet. This shows the height and weight elements as well as the name attribute. <p:person name="John Doe" xmlns:p="/person"> <height>185</height> <weight>84</height> </p:person> Having used this class schema to serialize instances of the Person class, It could later be extended or modified as follows and still read and write in a format compatible with the old class schema like so, even though the resulting XML has changed. @Root @Namespace(prefix="p", reference="/person") public class Person { @Version(revision=1.1) private double version; @Attribute private String name; @Attribute private int age; @Element private int height; public String getName() { return name; } public int getHeight() { return height; } public int getAge() { return age; } } Here the version attribute is annotated with the special Version annotation. This will read the previously generated XML and compare the version attribute of the person element and compare it to the revision attribute of the annotation. If the version annotation does not exist the initial 2.0 version is assumed. So when using the new modified class, which is revision 1.1, with the old serialized XML the serializer will determine that the two have differing versions. So when deserializing it will ignore the excess weight element and ignore the fact that the age attribute does not exist. It will do this for all attributes and elements that do not match. This is quite similar to the C# XML serialization version capability. Where the initial version of each class is 1.0 (implicitly) and subsequent versions increase. This tells the serializer how it should approach deserialization of different versions. The later version of the class when serialized will explicitly write the version as follows. <p:person version="1.1" name="John Doe" age="60" xmlns:p="/person"> <height>185</height> </p:person> |
Overriding serialization with converters | |
<!-- padding --> |
Often times there is a need to serialize an object that can not be annotated. Such objects may exist in third party dependencies or packages. To serialize these objects a Converter object can be implemented. A converter can be used to intercept the normal serialization process. Interception of the normal serialization process can be done in several different ways, for example take the code snippet below. @Root public class Example { @Element @Convert(ExternalConverter.class) private External external; public External getExternal() { return external; } } public class ExternalConverter implements Converter<External> { public External read(InputNode node) { String name = node.getAttribute("name"); String value = node.getAttribute("value"); return new External(name, value); } public void write(OutputNode node, External external) { String name = external.getName(); String value = external.getValue(); node.setAttribute("name", name); node.setAttribute("value", value); } } The above code snippet also shows a field annotated with the Convert annotation. The converter specified by the annotation will be used to intercept the normal serialization process and produce a customized XML element. Take the XML below, this is produced when the example object is serialized. <example> <external name="book" value="Shantaram"/> </example> Interception of the normal serialization process is performed using a Strategy implementation and so does not form part of the core serialization process. Instead a specific strategy needs to be provided to the persister as is shown in the code snippet below. Strategy strategy = new AnnotationStrategy(); Serializer serializer = new Persister(strategy); serializer.read(Example.class, file); Without the specification of the AnnotationStrategy the interception could not be performed, as the core serialization process will not acknowledge the Convert annotation. So in effect this strategy extends the core serialization process in an independent and transparent manner. Another even more transparent way to intercept the normal serialization process is to use another strategy implementation. The RegistryStrategy allows bindings to be registered between classes and converters, there is no need for additional annotations as was required for the previous example. Below is an example of how to establish bindings between a class and a converter. Registry registry = new Registry(); Strategy strategy = new RegistryStrategy(registry); Serializer serializer = new Persister(strategy); registry.bind(External.class, ExternalConverter.class); As many bindings as is required can be established with a registry. Also, if more complex converters are required a converter instance can be registered. Such a converter could have a reference to the Persister object so that nested serialization can be performed. This registry strategy also ensures that objects within Java collection objects can be serialized with registered converters. To illustrate how a complex converter could be registered take a look at the snippet below. Registry registry = new Registry(); Strategy strategy = new RegistryStrategy(registry); Serializer serializer = new Persister(strategy); ComplexConverter converter = new ComplexConverter(serializer); registry.bind(ComplexObject.class, converter); |
Intercepting the serialization process | |
<!-- padding --> |
Interception of the serialization process can be useful in several scenarios, for example if attributes are to be added or removed from an XML element then that element can be intercepted and modified during the serialization process. One useful application of interception is to change attribute names or values. For example, the "class" annotations added by the TreeStrategy could be intercepted and changed to a language neutral format that does not contain the Java class name. Below is an example of how to use a Visitor to add debug comments to an obect which is to be serialized. @Root @Default public class CommentExample { private String name; private BigDecimal number; private Date date; } public class CommentVisitor implements Visitor { public void write(Type type, NodeMap<OutputNode> node) { OutputNode element = node.getNode(); Class type = type.getType(); String comment = type.getName(); if(!element.isRoot()) { element.setComment(comment); } } } The above visitor implementation will get the OutputNode that represents the XML element for the provided map of attributes. If the element does not represent the root element in the XML document then every element will have an associated comment, which descrives the class it represents. Such a visitor can be useful when serializing large document structures. The XML snippet below provides an example of what would be written. <commentExample> <!-- java.lang.String --> <name>John Doe</name> <!-- java.math.BigDecimal --> <number>100.0</number> <!-- java.lang.Integer --> <value>18</value> </commentExample> To add a visitor to serialization the VisitorStrategy must be used. This strategy takes a visitor implementation and if required a strategy to delegate to. As usual, this strategy implementation can then be used to construct a persister, which can then serialize and deserialize objects. |
Mapping with XPath expressions | |
<!-- padding --> |
At times it is useful to have your object model map to complex XML documents, without having to write an annotated class to map to the required elements and attributes. For such scenarios the Path annotation can be used. This requires the user to specify an XPath expression for a field or method. For example take annotated fields below. @Root public class ServerDeployment { @Attribute @Path("group") private ServerType type; @Element @Path("group/server[1]/details") private Server primary; @Element @Path("group/server[2]/details") private Server secondary; public Server getPrimary() { return primary; } public Server getSecondary() { return secondary; } } @Root public class Server { @Attribute private String host; @Attribute private int port; } public enum ServerType { WINDOWS, LINUX, SOLARIS } The above code shows annotations applied to two objects. One contains XPath expressions that tell the serialization process how to read and write the details to an from the document. Here the expression defines a server within wrapper elements. When serializing such objects, the following XML results. <serverDeployment> <group type="LINUX"> <server> <details> <primary host="host1.domain.com" port="4567"/> </details> </server> <server> <details> <secondary host="host2.domain.com" port="4567"/> </details> </server> </group> </serverDeployment> As can be seen the XPath expressions defined have been used to determine the structure of the XML document. Such expressions allow a complex XML format to be serialized in to two simple objects. This can greatly reduce the number of types required to map an object to an XML structure. Both attributes and elements can be mapped in this manner. When ordering elements with the Order annotation these wrapper elements can be sorted. To order wrapper elements an XPath expression can be used to identify the wrapper. Simply place the expression in the order annotation along with any element or attribute names and it is ordered as required. For example, take the following code snippet.
@Default @Order(elements={"name[1]/first", "name[1]/surname", "age/date", "name[2]/nickname"}) public class Person { @Path("name[1]") private String first; @Path("name[1]") private String surname; @Path("name[2]") private String nickname; @Path("age") private String date; public String getName() { return first; } public String getSurname() { return surname; } } Just like ordering of elements or attributes without XPath expressions, a reference is all that is needed to ensure order. For the above code the serialization of the object will result in the following XML. <person> <name> <first>Jack</first> <surname>Daniels</surname> </name> <age> <birth>19/10/1912</birth> </age> <name> <nickname>JD</nickname> </name> </person> In the above XML snippet we have serialized a single object in to multiple elements and ensured the order of the elements is as we required. Ordering can be applied to elements and attributes with Path annotations as easily as it can to those without, and both can be mixed within the same annotation. Using this type of ordering it is possible to generate very predictible results. One thing to note when using such annotations, is that only a subset of the XPath expression syntax is supported. For example, element and attribute references can not be taken from the root of the document, only references within the current context are allowed. |
Dynamic serialization with unions | |
<!-- padding --> |
In order to perform dynamic serialization where element names bind to specific types the ElementUnion annotation can be used. This allows different XML schema classes to be associated with a single annotated field or method. Serialization of the associated instance determines the XML element name using the instance type. On deserialization the XML element name is then used to determine the schema class to use. For example, take the following set of annotated classes. public interface Shape { public double area(); } @Root public class Circle implements Shape { @Element private double radius; public Circle(@Element(name="radius") double radius) { this.radius = radius; } public double area() { return Math.PI * Math.pow(radius, 2.0); } } @Root public class Rectangle implements Shape { @Element private double width; @Element private double height; public Rectangle( @Element(name="width") double width, @Element(name="height") double height) { this.height = height; this.width = width; } public double area() { return width * height; } } @Root public class Diagram { @ElementUnion({ @Element(name="circle", type=Circle.class), @Element(name="rectangle", type=Rectangle.class) }) private Shape shape; public Diagram() { super(); } public void setShape(Shape shape){ this.shape = shape; } public Shape getShape() { return shape; } } The above set of classes can now be used to dynamically deserialize different XML documents using a single schema class. For example, take the XML snippet below, this shows what is generated when the shape is assigned an instance of the circle type. <diagram> <circle> <radius>3.0</radius> </circle> </diagram> However, if the shape field is assigned an instance of the square type then serialization of the diagram results in a different XML document. See the XML snippet below. <diagram> <rectangle> <width>5.0</width> <height>11.0</heigth> </rectangle> </diagram> Providing dynamic serialization capabilities via the ElementUnion annotation ensures that more complex XML documents can be handled with ease. Typically, such unions will be required for a list of similar types. To tackle lists the ElementListUnion annotation can be used. This can be used as a union of inline lists to collect similar XML declarations in to a single list. For example, take the annotated classes below. public interface Operation { public void execute(); } @Default public class Delete implements Operation { private File file; public Delete(@Element(name="file") File file) { this.file = file; } public void execute() { file.delete(); } } @Default public class MakeDirectory implements Operation { private File path; private MakeDirectory(@Element(name="path") File path) { this.path = path; } public void execute() { path.mkdirs(); } } @Default public class Move implements Operation { private File source; private File destination; public Move( @Element(name="source") File source, @Element(name="destination") File destination) { this.source = source; this.destination = destination; } public void execute() { source.renameTo(destination); } } @Root public class Task { @ElementListUnion({ @ElementList(entry="delete", inline=true, type=Delete.class), @ElementList(entry="mkdir", inline=true, type=MakeDirectory.class), @ElementList(entry="move", inline=true, type=Move.class) }) private List<Operation> operations; @Attribute private String name; public Task(@Attribute(name="name") String name) { this.operations = new LinkedList<Operation>(); this.name = name; } public void add(Operation operation) { operations.add(operation); } public void execute() { for(Operation operation : operations) { operation.execute(); } } } For the above set of annotated classes a list of operations can be defined in an XML document. Each type inserted in to the list can be resolved using the XML element name. Below is an example XML document generated from the annotated classes. <task name="setup"> <delete> <file>C:\workspace\classes</file> </delete> <mkdir> <path>C:\workspace\classes</path> </mkdir> <move> <source>C:\worksace\classes</source> <destination>C:\workspace\build</destination> </move> </task> |
相关推荐
在Java编程中,持久化是将数据保存到可长期存储的介质中,以便在后续的程序运行中可以恢复和使用这些数据。JavaBean和XML在Java持久化中扮演着重要的角色。JavaBean是一种符合特定规范的Java类,通常用于封装数据,...
ORM技术的核心理念是通过映射机制将数据库中的数据自动转化为对象,反之亦然,而XMLUtil则是在XML文档与Java对象间提供了类似的转换功能。在Java开发中,尤其是在处理配置文件、数据交换或序列化场景时,XMLUtil能够...
- 数据库持久化:对象-关系映射(ORM)工具如Hibernate使用XML配置数据库连接。 6. XML Schema和DTD: - XML Schema提供更强大的数据类型和命名空间支持,用于定义XML文档结构和数据类型。 - DTD是较早的数据...
4. **持久化**:Hibernate等ORM(Object-Relational Mapping)工具利用XML来描述数据库映射,使Java对象可以直接与数据库交互。 5. **文档生成与解析**:Java也可以用于生成和解析XML文档,例如Apache FOP用于生成...
通过DOM(Document Object Model)、SAX(Simple API for XML)或StAX(Streaming API for XML)等解析器,Java可以读取和操作XML文档。此外,Java还提供了JAXB(Java Architecture for XML Binding)用于对象到XML...
**Java和XML在数据持久化中的作用** Java Persistence API (JPA) 和Hibernate等ORM(Object-Relational Mapping)框架允许Java对象与XML数据库,如JAXB(Java Architecture for XML Binding),进行交互。JAXB可以...
我们可以使用JAXB(Java Architecture for XML Binding)进行XML与Java对象之间的双向绑定,或者使用DOM(Document Object Model)、SAX(Simple API for XML)和StAX(Streaming API for XML)等API来解析和生成XML...
ORM(Object-Relational Mapping)框架如Hibernate和JPA(Java Persistence API)进一步简化了这一过程,将Java对象映射到数据库表,实现了面向对象编程与关系数据库之间的无缝衔接。 在实际开发中,EJB通常用于...
它提供了一种抽象层,使得开发者可以使用面向对象的方式来处理数据库操作,而无需直接编写SQL语句,实现了ORM(Object-Relational Mapping)。 7. **ORM(Object-Relational Mapping)** ORM是将关系数据库的数据...
它简化了对象关系映射(ORM),允许开发者以面向对象的方式处理数据,而无需直接编写SQL语句。JPA通过在Java类和数据库表之间建立映射,使得Java对象可以直接存储到数据库中,并能方便地进行查询和更新。 **JTA...
4. 数据持久化:通过Java Persistence API (JPA) 或 Hibernate 等ORM框架,可以直接将对象序列化为XML,实现对象与数据库之间的映射。 三、XML在网络通信中的应用 1. Web服务:XML是构建Web服务(如RESTful API和...
Hibernate是一个流行的ORM(对象关系映射)框架,它允许开发者使用面向对象的方式操作数据库。在处理XML配置文件或XML格式的SQL脚本时,Xerces的解析能力可以帮助Hibernate正确理解和解析这些文件,实现数据映射和...
通过Xerces提供的强大解析功能,Hibernate能够高效地读取和解析XML配置,实现对象的持久化。 标签中的"xerces hibernate SAXXML"暗示了这些技术之间的联系。Xerces作为Java平台上优秀的XML解析库,不仅在SAX解析...
此外,DOM(Document Object Model)和SAX(Simple API for XML)是解析XML的两种主要方法,它们提供了读取和操作XML文档的API。 这套教学课件通过深入讲解这些核心技术,旨在帮助学习者掌握企业级Java开发的关键...
6. **Hibernate ORM**:虽然标题和描述中没有直接提及Hibernate,但标签中出现意味着Xerces的XML解析能力可能在配置Hibernate应用时有所涉及,例如解析Hibernate的XML配置文件或持久化映射文件(hbm.xml)。...
在实际应用中,比如在与Hibernate框架集成时,Xerces-J的SAX解析器可以帮助解析ORM映射文件(如.hbm.xml),从而生成Java对象和数据库表之间的映射关系。这在处理复杂的数据库交互和数据持久化时,显得尤为重要。 ...
在处理XML文档时,解析器扮演了至关重要的角色,而Xerces-J正是Java平台上的一个开源XML解析器,它提供了SAX(Simple API for XML)和DOM(Document Object Model)两种解析方式。本文将重点介绍Xerces-J的2.6.2版本...