For me historically maybe the least approachable part of the Spring Framework is the system to define custom XML schemas to use in the configuration files for defining beans (The ones like aop, security, integration , util, etc).
I think this might be the case to most people as well. In this article I will show step by step how to create custom namespaces for their use with Spring. It won’t be a very involved example but neither will it be a trivial useless one. As currently I’m working with recommendations engines and in particular looking at Mahout, I will define a Spring namespace for using Mahout recommenders in Spring. The following is what I want to achieve with my namespace:
1.
<
user-based-recommender
id =”recommender”>
2.
<
euclidean-distance-similarity
/>
3.
<
file-model
path = “/tmp/model.csv”/>
4.
<
nearest-neighborhood
size = “2”/>
5.
</
user-based-recommender
>
The
idea would be to allow any kind of similarity to be used and any kind
of recommender and any kind of data model and any kind of neighbour
definition. But for my example I will just admit the ones defined in the
previous XML snippet.
When we create a namespace in Spring we
are simply creating a better way to express standard Spring beans in a
more Domain Specific Language. In our case we are using a language to
express recommendations backed by Mahout. What this means is that our
little XML snippet will be equivalent to the following standard beans
definitions
01.
<
bean
id
=
"recommenderRegularBean"
class
=
"org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender"
>
02.
<
constructor-arg
ref
=
"dataModel"
/>
03.
<
constructor-arg
ref
=
"userNeighbourhood"
/>
04.
<
constructor-arg
ref
=
"similarity"
/>
05.
</
bean
>
06.
07.
<
bean
id
=
"dataModel"
08.
class
=
"org.apache.mahout.cf.taste.impl.model.file.FileDataModel"
>
09.
<
constructor-arg
value
=
"/tmp/model.csv"
/>
10.
</
bean
>
11.
12.
<
bean
id
=
"similarity"
class
=
"org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity"
>
13.
<
constructor-arg
ref
=
"dataModel"
/>
14.
</
bean
>
15.
16.
<
bean
id
=
"userNeighbourhood"
class
=
"org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood"
>
17.
<
constructor-arg
value
=
"2"
/>
18.
<
constructor-arg
ref
=
"similarity"
/>
19.
<
constructor-arg
ref
=
"dataModel"
/>
20.
</
bean
>
The
first thing we need is to create a xsd schema for our XML. As I’m not
particularly good at this, I will generate one from my xml with a tool
and configure it if needed. I will use the tool “trang”
I generate the following xsd:
01.
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
02.
<
xs:schema
xmlns
=
"http://www.mycompany.com/schema/recommendations"
xmlns:xs
=
"http://www.w3.org/2001/XMLSchema"
elementFormDefault
=
"qualified"
targetNamespace
=
"http://www.mycompany.com/schema/recommendations"
>
03.
<
xs:element
name
=
"user-based-recommender"
>
04.
<
xs:complexType
>
05.
<
xs:sequence
>
06.
<
xs:element
ref
=
"euclidean-distance-similarity"
/>
07.
<
xs:element
ref
=
"file-model"
/>
08.
<
xs:element
ref
=
"nearest-neighborhood"
/>
09.
</
xs:sequence
>
10.
<
xs:attribute
name
=
"id"
use
=
"required"
type
=
"xs:ID"
/>
11.
</
xs:complexType
>
12.
</
xs:element
>
13.
<
xs:element
name
=
"euclidean-distance-similarity"
>
14.
<
xs:complexType
/>
15.
</
xs:element
>
16.
<
xs:element
name
=
"file-model"
>
17.
<
xs:complexType
>
18.
<
xs:attribute
name
=
"path"
use
=
"required"
/>
19.
</
xs:complexType
>
20.
</
xs:element
>
21.
<
xs:element
name
=
"nearest-neighborhood"
>
22.
<
xs:complexType
>
23.
<
xs:attribute
name
=
"size"
use
=
"required"
type
=
"xs:integer"
/>
24.
</
xs:complexType
>
25.
</
xs:element
>
26.
</
xs:schema
>
The next step is to define a NamespaceHandler which will take care of registering a particular BeanDefinitionParser for each of the top elements we have in our XML namespace. In our case we create this class:
01.
package
org.springframework.recommendations;
02.
03.
04.
import
org.springframework.beans.factory.xml.NamespaceHandlerSupport;
05.
06.
07.
public
class
RecommendationsNamespaceHandler
extends
NamespaceHandlerSupport{
08.
09.
10.
public
void
init() {
11.
registerBeanDefinitionParser(
"user-based-recommender"
,
new
UserBasedRecommenderBeanDefinitionParser());
12.
}
13.
}
Next
we need to implement the BeanDefinitionParsers. This is the one class
that will actually take care of the parsing of the XML elements.
That
is a simple code to understand. It is simply getting values from the
XML and wiring components through constructors as is required by the
Mahout implementations.
The next and final steps is to make Spring
aware of this new configuration we have just created and give it a
namespace value for use in our Spring application context.
Making
Spring aware is done by creating two special files that Spring
recognizes when starting up. the following two files must exist in the
META-INF folder for them to be found:
1.
spring.handlers
2.
3.
http\://www.mycompany.com/schema/recommendations=org.springframework.recommendations.RecommendationsNamespaceHandler
4.
5.
6.
spring.schemas
7.
8.
http\://www.mycompany.com/schema/recommendations.xsd=org/springframework/recommendations/recommendations.xsd
Then in our Spring configuration file we simply add the schema namespace specification in the “beans” element as we do with any of the other namespaces. The final XML application context looks like this:
01.
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
02.
<
beans
xmlns
=
"http://www.springframework.org/schema/beans"
03.
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
04.
xmlns:recommendations
=
"http://www.mycompany.com/schema/recommendations"
05.
xsi:schemaLocation="http://www.springframework.org/schema/beans
07.
http://www.mycompany.com/schema/recommendations http://www.mycompany.com/schema/recommendations.xsd">
08.
09.
10.
<
recommendations:user-based-recommender
id
=
"recommender"
>
11.
<
recommendations:euclidean-distance-similarity
/>
12.
<
recommendations:file-model
path
=
"/tmp/model.csv"
/>
13.
<
recommendations:nearest-neighborhood
size
=
"4"
/>
14.
</
recommendations:user-based-recommender
>
15.
</
beans
>
That’s it. We have created a functional custom xml extension for our Spring configuration.