Straight to the point: XML is not meant for humans. Fullstop. The only way for humans to deal with XML is when it is hidden behind proper tooling support. Without a tool hiding XAML you wouldn't write XAML code by hand, would you? Currently being on a greenfield project with my team collegues not familar with Spring.NET it quickly turned out that XML configuration can be quite a hurdle, burying the gain of power in the pain of configuration. Using the simple MovieFinder example from the Spring.NET quickstart examples, I would like to introduce you to a new way to do familar things.
Introducing MovieFinder
In the following I will use the following - very simple - example to show you different ways of wiring the components. A MovieLister can be used to obtain a list of movies directed by a particular director. To access persistent storage, it uses the MovieFinder repository component:
Here's how the client code might look like:
IMovieLister lister = ...
var movies = lister.MoviesDirectedBy("Roberto Benigni")
The traditional way of wiring
The traditional way of configuring the Spring.NET container looks like this:
<objects xmlns='http://www.springframework.net'> <object id='movieLister' type='MovieFinder.Core.MovieLister, MovieFinder.Core'> <constructor-arg index='0' ref='movieFinder' /> </object> <object id='movieFinder' type ='MovieFinder.Data.SimpleMovieFinder, MovieFinder.Data' /> </objects>
Code Example: Traditional Configuration
There are a couple of problems with that approach, here are some:
- not pleasant to read
- much 'noise', xml syntax hiding the actual wiring structure
- lack of refactoring support
- unmanageable for large object numbers
You can imagine that in large applications without any tool support this can be become very painful. Already less known is the fact that Spring is able to autowire components. Here's the example to autowire components based on type-matching contructor arguments (see the reference documentation for other values of 'default-autowire'):
<objects xmlns='http://www.springframework.net' default-autowire='constructor'> <object id='movieLister' type='MovieFinder.Core.MovieLister, MovieFinder.Core' /> <object id='movieFinder' type ='MovieFinder.Data.SimpleMovieFinder, MovieFinder.Data' /> </objects>
Code Example: Using constructor autwiring
This saves you from having to manually specifiying all dependencies. Of course, renaming your classes still may break this configuration.
Mark Pollack blogged an extensive post about configuring the Spring.NET container, including the even less known container API for configuring the container.
Going fluent
A lot of frameworks out there have already introduced a configuration style known as fluent API. A nice example for the Windsor container can be found here. There's a lot more, Fluent NHibernate being another very populare example.
Recently, Tom Farnbauer released such a fluent configuration API for Spring.NET, Recoil for Spring.NET. Using Recoil, our MovieFinder example could look like this:
public class MyWiringContainer : WiringContainer { public override void SetupContainer() { Define( () => new MovieFinder() ) .As<IMovieFinder>; Define( () => new MovieLister( Wire<IMovieFinder>() ) ) .As<IMovieLister>; }
} var myContext = new GenericApplicationContext(); myContext.Configure() .With<MyWiringContainer>(); myContext.Refresh();
Code Example: Wiring using Recoil for Spring.NET
Unfortunately I quickly found while introducing Recoil into my current project, that the API has some flaws and - after all - when looking at fluent apis, they usually add as much codenoise to your configuration as xml already does. Also fluent APIs tend to be less extensible than other approaches. Imho Fluent NHibernate suffers this fate a lot (probably others too, but this is the latest example crossing my way).
Of course we already achieved at least 1 important goal: We are safe against refactoring. Any class moves or renames will automatically be reflected in our configuration - because it is code.
Back to the whiteboard
I didn't find any of those approaches really satisfying. After all, all I want to do is
var movieFinder = new MovieFinder(); var movieLister = new MovieLister( movieFinder );
So lets start with the least minimal configuration container I can think of. A simple class, who's member methods return the objects you are asking for:
public class MovieFinderConfiguration { public IMovieFinder MovieFinder() { return new MovieFinder(); } public IMovieLister MovieLister() { return new MovieLister( MovieFinder() ) } }
Code Example: Wiring using ... plain C#
This already does a lot of what we want:
- it is readable
- it doesn't cause more codenoise than your standard code would have
- we have full refactoring support
- we automatically get the whole object graph once we call MovieLister():
var container = new MovieFinderConfiguration(); var movieLister = container.MovieLister(); var movies = movieLister.MoviesDirectedBy("Roberto Bergnini");
Code Example: Obtaining the object graph from our C# "container"
Unfortunately our minimal container is lacking a couple of things:
- no support for managing the object lifecycle - everytime we call MovieLister() it will return a new graph
- no support for applying aspects to care for crosscutting concerns
- no support for all the other features you get from the containers out there
New on stage: CodeConfig for Spring.NET
Rooting in a great idea of Rod Johnson, Chris Beams started working on bringing this idea to the Java world, the result is JavaConfig (and since yesterday's release of our mother-project integral part of Spring 3.0, congrats at this point to the Java team!). Mark Pollack implemented the first POC for Spring.NET, due to the needs in my current project, I decided to take that POC and continue develop it.
Yet again, Spring.NET surprised me. Due to being incredibly flexible, it was almost a breeze to merge the above mentioned concept with the container infrastructure. How does our MovieFinder look like using CodeConfig? Simple:
[Configuration] public class MovieFinderConfiguration { public virtual IMovieFinder MovieFinder() { return new MovieFinder(); } public virtual IMovieLister MovieLister() { return new MovieLister( MovieFinder() ) } }
Code Example: The CodeConfig configuration - not much difference!
Note the additional [Configuration] attribute and the "virtual" keyword added to the methods. Now feed this configuration into the Spring.NET application context and retrieve an instance of the IMovieLister:
var appContext = new GenericApplicationContext(); appContext.Configure() .FromConfiguration<MovieFinderConfiguration>(); ApplicationContext.Refresh(); var movieLister = appContext.GetObject<IMovieLister>(); var movies = movieLister.MoviesDirectedBy("Roberto Bergnini");
Code Example: retrieving an IMovieFinder from an CodeConfig-configured application context
Neat, isn't it? This means you can write your configuration in the simplest possible way and still can leverage the full power of the IoC container. Enabling logging on your services? No problem:
appContext.Configure()
.FromConfiguration<MovieFinderConfiguration>()
.EnableLogging(cfg => { cfg.TypeFilter = type => type.IsDefined(typeof(ServiceAttribute), true); cfg.Logger.LogExecutionTime = true; cfg.Logger.LogMethodArguments = true; cfg.Logger.LogReturnValue = true; cfg.Logger.LogLevel = LogLevel.Trace; });
Using Conventions for Configuration
You don't want to configure each object manually in a large application? Use component-scanning, a feature that I shamelessly stole from StructureMap:
appContext.Configure() .Scan(s => s .AssemblyOfType<MovieLister>() .AssemblyOfType<SimpleMovieFinder>() .Include(t => t.IsDefined(typeof (ComponentAttribute), true)) .With<TypeNamingConvention>() .With<AutowiringConvention>() );
Sources, Documentation and Next Steps
At this moment, you can find the sources in my public SVN repository at XP-Dev. Note, that the code will move to SpringSource's CodeConfig for Spring.NET repository here at a later stage.
For a quick overview of what is already possible, check out the various configuration examples for the MovieFinder example there. Documentation is missing, but reading the JavaConfig reference will give you a good overview of the features. For the component-scanning feature read Jeremy Miller's introduction on assembly scanning.
Beware that this is still a moving target, consider it not being more than a preview yet. But I'd love you to grab the sources, play around and let me know what you think - keen on hearing your feedback!
The first milestone of CodeConfig is scheduled for end of January - and don't forget to check, later this day, Spring.NET 1.3.0 GA will be released.
Merry XMLless!
14 comments:
The Spring 3.0 references docs on this functionality are also a good place to look for general info and probably will be closer to the direction of Spring.NET's CodeConfig.
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-java
Great!
What about an MarkerInterface instead of Attributes?
Simply and great, very good and original job.
Hey - I like it!
We are using Spring extensively for the SharePoint Action Framework (SAF) http://saf.codeplex.com.
We've been looking into developing a non-xml way to create our containers for a while now. This looks as though it could be the ticket.
Couple of questions ...
How do i give my object defs an "Id". We have a few methods that do a "GetObject(string name)" and I can't see how to assign one.
Do you think this will be rolled into the vNext of Spring ? If so, when ? just wondering whether to wait, or go with your source...
Many thanks and great work Mark
@Mark, for the moment it will stay as an independent project. We will release the first milstone within the next weeks.
Regarding your naming question: Checkout the TypeNamingConvention class - if you have specific needs how to name your objects this might be a good start!
Keep the feedback coming! I am very curious to hear experiences from the fields!
hth,
Erich
Hi Erich
Ok - I will look into the TypeNamingConvention class to see if it fits the bill.
I would be grateful if you could let me know if anything changes so that i can take the latest cut and place it into SAF.
Cheers
Mark
how about auto scan feature for all assembly in BaseDirectory.
Scan for all assembly and get type that marked with [Repository] and [Service] attribute?
Hi Erich. I downloaded your code (rev 45) and started playing with an ASP.NET MVC application. When configuring the Controllers I have to include them and in another phase Override them for SetSingleton(false). I think is too verbose because I have to write two times the same selector. There is a way to do the two things in the Include. Maybe an IncludeAndConfigure( t, cfg ) ?
Thanks in advance.
I'm Sorry
can you help me!
i have error:
No context registered. Use the 'RegisterContext' method or the 'spring/context' section from your configuration file.
Video:http://www.youtube.com/watch?v=LKBHVXFfETo
thanks
Right... I must be doing something stupid, using svn, I am trying to download the src from the url given above, but everytime I get "cannot connect to https://src.springframework.org"
the svn command I use is 'svn checkout https://src.springframework.org/svn/se-config-net/trunk -r HEAD'
help?
I seem to run into a bug...objects defined with the 'Prototype' scope are being treated by Spring as Singletons.
I think I've found the bug in the ProcessUtils.cs:
public static void CopyAttributes(string objectName, ConfigurationAttribute configurationAttribute, DefinitionAttribute definitionAttribute, RootObjectDefinition rod, IConfigurableListableObjectFactory objectFactory)
{
if (definitionAttribute.Scope == Scope.Singleton)
{
rod.IsSingleton = true;
} else
{
rod.IsAbstract = false;
}
After appending the follow piece of code to above the problem is gone:
if (definitionAttribute.Scope == Scope.Prototype)
{
rod.IsSingleton = false;
}
I'd like to propose 2 changes:
1. Allow overriding object defined in parent context (see TODO in ConfigurationProcessor line 153);
2. ServiceLocator GetObject without name should search for objects defined in parent context as the one with name does.
Here is the patch file for the 1st change if you need it:
Index: src/Spring/Spring.Config/Config/Process/ConfigurationProcessor.cs
===================================================================
--- src/Spring/Spring.Config/Config/Process/ConfigurationProcessor.cs (revision 8)
+++ src/Spring/Spring.Config/Config/Process/ConfigurationProcessor.cs (working copy)
@@ -144,23 +144,15 @@
DefinitionAttribute definitionAttribute = IsObjectDefinitionCandidate(cfgAttribute, method);
if (definitionAttribute != null)
{
- string objectName = string.Empty;
- objectName = definitionAttribute.Name;
+ string objectName = definitionAttribute.Name;
if (string.IsNullOrEmpty(objectName)) objectName = objectNamingStrategy.GetObjectName(method);
- if (!methodsSeen.Contains(objectName))
+ if (!methodsSeen.Contains(objectName)
+ && owningObjectFactory.GetObjectDefinition(objectName, false) != null)
{
- //TODO avoid check in parent object factories.
- if (owningObjectFactory.ContainsObjectDefinition(objectName))
- {
- if (!definitionAttribute.AllowOverridding)
- {
- throw new InvalidOperationException("An object named " + objectName +
- "already exists. Consider using [ObjectDefinition(AllowOverriding=true)]");
- }
- return;
- }
-
+ if (definitionAttribute.AllowOverridding) return;
+ throw new InvalidOperationException("An object named " + objectName +
+ "already exists. Consider using [ObjectDefinition(AllowOverriding=true)]");
}
methodsSeen.Add(objectName);
GenerateObjectDefinitionFromObjectCreationMethod(cfgAttribute, configurationObjectName, objectName, method, definitionAttribute);
I would like to introduce another solution to code base configuration for spring.net. This library takes slightly different approach than this one proposed.
https://github.com/thenapoleon/Fluent-API-for-Spring.Net/wiki/Fluent-Spring-Configuration-for-.Net
This library will soon contain convention based configuration too which will extend the existing auto wiring from spring.
Post a Comment