Spring supports adding custom schema-based extensions to the basic Spring XML format for defining and configuring objects. This section is devoted to detailing how you would go about writing your own custom XML object definition parsers and integrating such parsers into the Spring IoC container.
To facilitate the authoring of configuration files using a schema-aware XML editor, Spring's extensible XML configuration mechanism is based on XML Schema. If you are not familiar with Spring's current XML configuration extensions that come with the standard Spring distribution, please first read the appendix entitled Appendix A, XML Schema-based configuration.
Creating new XML configuration extensions can be done by following these (relatively) simple steps:
-
Authoring an XML schema to describe your custom element(s).
-
Coding a custom
INamespaceParser
implementation (this is an easy step, don't worry). -
Coding one or more
IObjectDefinitionParser
implementations (this is where the real work is done). -
Registering the above artifacts with Spring (this too is an easy step).
What follows is a description of each of these steps. For the
example, we will create an XML extension (a custom XML element) that
allows us to configure objects of the type Regex
(from the System.Text.RegularExpressions
namespace) in
an easy manner. When we are done, we will be able to define object
definitions of type Regex
like this:
<myns:regex id="regex" pattern="(^\d{5}$)|(^\d{5}-\d{4}$)" options="Compiled"/>
Creating an XML configuration extension for use with Spring's IoC
container starts with authoring an XML Schema to describe the extension.
What follows is the schema we'll use to configure
Regex
objects.
<?xml version="1.0" encoding="utf-8" ?> <xsd:schema id="myns" xmlns="http://www.mycompany.com/schema/myns" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:objects="http://www.springframework.net" xmlns:vs="http://schemas.microsoft.com/Visual-Studio-Intellisense" targetNamespace="http://www.mycompany.com/schema/myns" elementFormDefault="qualified" attributeFormDefault="unqualified" vs:friendlyname="Spring Regex Configuration" vs:ishtmlschema="false" vs:iscasesensitive="true" vs:requireattributequotes="true" vs:defaultnamespacequalifier="" vs:defaultnsprefix="" > <xsd:import namespace="http://www.springframework.net"/> <xsd:element name="regex"> <xsd:complexType> <xsd:complexContent> <xsd:extension base="objects:identifiedType"> <xsd:attribute name="pattern" type="xsd:string" use="required"/> <xsd:attribute name="options" type="xsd:string" use="optional"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
The emphasized line contains an extension base for all tags that
will be identifiable (meaning they have an id
attribute
that will be used as the object identifier in the container). We are able
to use this attribute because we imported the Spring-provided
'objects'
namespace. The vs:
prefixed elements are for better integration with intellisense in
VS.NET.
The above schema will be used to configure
Regex
objects, directly in an XML application
context file using the <myns:regex/>
element.
<myns:regex id="usZipCodeRegex" pattern="(^\d{5}$)|(^\d{5}-\d{4}$)" options="Compiled"/>
Note that after we've created the infrastructure classes, the above
snippet of XML will essentially be exactly the same as the following XML
snippet. In other words, we're just creating an object in the container,
identified by the name 'usZipCodeRegex'
of type
Regex
, with a couple of constructor arguments
set.
<object id="usZipCodeRegex" type="System.Text.RegularExpressions.Regex, System"> <constructor-arg name="pattern" value="(^\d{5}$)|(^\d{5}-\d{4}$)"/> <constructor-arg name="options" value="Compiled"/> </object>
Note | |
---|---|
The schema-based approach to creating configuration format allows for tight integration with an IDE that has a schema-aware XML editor. Using a properly authored schema, you can use intellisense to have a user choose between several configuration options defined in the enumeration. The schema for creating IDbProvider instances shows the use of XSD enumerations. |
In addition to the schema, we need an
INamespaceParser
that will parse all
elements of this specific namespace Spring encounters while parsing
configuration files. The INamespaceParser
should in our case take care of the parsing of the
myns:regex
element.
The INamespaceParser
interface is
pretty simple in that it features just two methods:
-
Init()
- allows for initialization of theINamespaceParser
and will be called by Spring before the handler is used -
IObjectDefinition Parse(Element, ParserContext)
- called when Spring encounters a top-level element (not nested inside a object definition or a different namespace). This method can register object definitions itself and/or return a object definition.
Although it is perfectly possible to code your own
INamespaceParser
for the entire namespace
(and hence provide code that parses each and every element in the
namespace), it is often the case that each top-level XML element in a
Spring XML configuration file results in a single object definition (as in
our case, where a single <myns:regex/>
element
results in a single Regex
object definition).
Spring features a number of convenience classes that support this
scenario. In this example, we'll make use the
NamespaceParserSupport
class:
using Spring.Objects.Factory.Xml; namespace CustomNamespace { [NamespaceParser( Namespace = "http://www.mycompany.com/schema/myns", SchemaLocationAssemblyHint = typeof(MyNamespaceParser), SchemaLocation = "/CustomNamespace/myns.xsd" ) ] public class MyNamespaceParser : NamespaceParserSupport { public override void Init() { RegisterObjectDefinitionParser("regex", new RegexObjectDefinitionParser()); } } }
Notice that there isn't actually a whole lot of parsing logic in
this class. Indeed... the NamespaceParserSupport
class has a built in notion of delegation. It supports the registration of
any number of IObjectDefinitionParser
instances, to which it will delegate to when it needs to parse an element
in it's namespace. This clean separation of concerns allows an
INamespaceParser
to handle the
orchestration of the parsing of all of the custom
elements in it's namespace, while delegating to
IObjectDefinitionParsers
to do the grunt work of the
XML parsing; this means that each
IObjectDefinitionParser
will contain just
the logic for parsing a single custom element, as we can see in the next
step.
To help in the registration of the parser for this namespace, the
NamespaceParser
attribute is used to map the XML
namespace string, i.e.
http://www.mycompany.com/schema/myns
, to the location
of the XML Schema file as an embedded assembly resource.
A IObjectDefinitionParser
will be
used if the INamespaceParser
encounters an
XML element of the type that has been mapped to the specific object
definition parser (which is 'regex'
in this case). In
other words, the IObjectDefinitionParser
is
responsible for parsing one distinct top-level XML
element defined in the schema. In the parser, we'll have access to the XML
element (and thus it's subelements too) so that we can parse our custom
XML content, as can be seen in the following example:
using System; using System.Text.RegularExpressions; using System.Xml; using Spring.Objects.Factory.Support; using Spring.Objects.Factory.Xml; using Spring.Util; namespace CustomNamespace { public class RegexObjectDefinitionParser : AbstractSimpleObjectDefinitionParser { protected override Type GetObjectType(XmlElement element) { return typeof (Regex); } protected override void DoParse(XmlElement element, ObjectDefinitionBuilder builder) { // this will never be null since the schema explicitly requires that a value be supplied string pattern = element.GetAttribute("pattern"); builder.AddConstructorArg(pattern); // this however is an optional property string options = element.GetAttribute("options"); if (StringUtils.HasText(options)) { RegexOptions regexOptions = (RegexOptions)Enum.Parse(typeof (RegexOptions), options); builder.AddConstructorArg(regexOptions); } } protected override bool ShouldGenerateIdAsFallback { get { return true; } } }
We use the Spring-provided
| |
We supply the
|
In this simple case, this is all that we need to do. The creation of
our single IObjectDefinition
is handled by
the AbstractSingleObjectDefinitionParser
superclass, as is the extraction and setting of the object definition's
unique identifier. The property
ShouldGenerateIdAsFallback
will generate a throw-away
object id incase one is not specified, this is useful when nesting object
definitions.
The coding is finished! All that remains to be done is to somehow
make the Spring XML parsing infrastructure aware of our custom element; we
do this by registering our custom
INamespaceParser
using a special
configuration section handler. The location of the XML Schema in this
example has been directly assoicated with the parser though the use of the
Namespace
attribute.
The custom configuration section handler is of the type
Spring.Context.Support.NamespaceParsersSectionHandler
and is registered with .NET in the normal manner. The custom
configuration section will simply point to the
INamespaceParser
implementation that has the
Namespace
attribute. For our example, we need to
write the following:
<configuration> <configSections> <sectionGroup name="spring"> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="CustomNamespace.MyNamespaceParser, CustomNamespace" /> </parsers> </spring> </configuration>
Using a custom extension that you yourself have implemented is no
different from using one of the 'custom' extensions that Spring provides
straight out of the box. Find below an example of using the custom
<regex/>
element developed in the previous steps
in a Spring XML configuration file.
<?xml version="1.0" encoding="utf-8" ?> <objects xmlns="http://www.springframework.net" xmlns:myns="http://www.mycompany.com/schema/myns"> <!-- as a top level object definition --> <myns:regex id="usZipCodeRegex" pattern="(^\d{5}$)|(^\d{5}-\d{4}$)"/> <object id="jobDetailTemplate" abstract="true"> <property name="regex"> <!-- as an inner object definition --> <myns:regex pattern="(^\d{5}$)|(^\d{5}-\d{4}$)" options="Compiled"/> </property> </object> </objects>
Find below links to further resources concerning XML Schema and the extensible XML support described in this chapter.