diff --git a/doc/reference/src/aop-quickstart.xml b/doc/reference/src/aop-quickstart.xml index dd7a6b11..794ed8d4 100644 --- a/doc/reference/src/aop-quickstart.xml +++ b/doc/reference/src/aop-quickstart.xml @@ -1,1116 +1,1124 @@ - - - - AOP Guide - - - Introduction - - This is an introductory guide to Aspect Oriented Programming (AOP) - with Spring.NET. - - This guide assumes little to no prior experience of having - used Spring.NET AOP on the part of the reader. - However, it does assume a certain familiarity with - the terminology of AOP in general. It is probably better if you have read - (or at least have skimmed through) the AOP section of the reference - documentation beforehand, so that you are familiar with a) just what AOP - is, b) what problems AOP is addressing, and c) what the AOP concepts of - advice, pointcut, and - joinpoint actually mean... this guide spends absolutely - zero time defining those terms. Having said all that, if you are the kind - of developer who learns best by example, then by all means follow along... - you can always consult the reference documentation as the need arises (see - ). - - The examples in this guide are intentionally simplistic. One of the core aims of - this guide is to get you up and running with Spring.NET's flavor of AOP in - as short a time as possible. Having to comprehend even a simple object - model in order to understand the AOP examples would not be conducive to - learning Spring.NET AOP. It is left as an exercise for the reader to take - the concepts learned from this guide and apply them to his or her own code - base. Again, having said all of that, this guide concludes with a number - of cookbook-style AOP 'recipes' that illustrate the application of - Spring.NET's AOP offering in a real world context; additionally, the - Spring.NET reference application contains a number of Spring.NET AOP - aspects particular to it's own domain model (see ). - - - - The basics - - This initial section introduces the basics of defining and then - applying some simple advice. - - - Applying advice - - Lets see (a very basic) example of using Spring.NET AOP. The - following example code simply applies advice that writes the details of - an advised method call to the system console. Admittedly, this is not a - particularly compelling or even useful application of AOP, but having - worked through the example, you will then hopefully be able to see how - to apply your own custom advice to perform useful work (transaction - management, auditing, security enforcement, thread safety, etc). - - Before looking at the AOP code proper lets quickly look at the - domain classes that are the target of the advice (in Spring.NET AOP - terminology, an instance of the following class is going to be the - advised object. - - public interface ICommand -{ - object Execute(object context); -} - -public class ServiceCommand : ICommand -{ - public object Execute(object context) - { - Console.Out.WriteLine("Service implementation : [{0}]", context); - return null; - } -} - - Find below the advice that is going to be applied to the - object Execute(object context) method of the - ServiceCommand class. As you can see, this is an - example of around advice (see ). - - public class ConsoleLoggingAroundAdvice : IMethodInterceptor - { - public object Invoke(IMethodInvocation invocation) - { - Console.Out.WriteLine("Advice executing; calling the advised method..."); - object returnValue = invocation.Proceed(); - Console.Out.WriteLine("Advice executed; advised method returned " + returnValue); - return returnValue; - } - } - - - - Some simple code that merely prints out the fact that the advice is executing. - - - - The advised method is invoked. - - - - The return value is captured in the - - returnValue - - variable. - - - - The value of the captured - - returnValue - - is printed out. - - - - The previously captured - - returnValue - - is returned. - - - - So thus far we have three artifacts: an interface - (ICommand); an implementation of said interface - (ServiceCommand); and some (trivial) advice - (encapsulated by the ConsoleLoggingAroundAdvice - class). All that remains is to actually apply the - ConsoleLoggingAroundAdvice advice to the - invocation of the Execute() method of the - ServiceCommand class. Lets look at how to effect - this programmatically... - - ProxyFactory factory = new ProxyFactory(new ServiceCommand()); - factory.AddAdvice(new ConsoleLoggingAroundAdvice()); - ICommand command = (ICommand) factory.GetProxy(); - command.Execute("This is the argument"); - - The result of executing the above snippet of code will look - something like this... - - Advice executing; calling the advised method... - Service implementation : [This is the argument] - Advice executed; advised method returned - - The output shows that the advice (the - Console.Out statements from the - ConsoleLoggingAroundAdvice was applied - around the invocation of the advised method. - - So what is happening here? The fact that the preceding code used a - class called ProxyFactory may have clued you in. - The constructor for the ProxyFactory class took - as an argument the object that we wanted to advise (in this case, an - instance of the ServiceCommand class). We then - added some advice (a ConsoleLoggingAroundAdvice - instance) using the AddAdvice() method of the - ProxyFactory instance. We then called the - GetProxy() method of the - ProxyFactory instance which gave us a proxy... an - (AOP) proxy that proxied the target object (the - ServiceCommand instance), and called the advice - (a single instance of the - ConsoleLoggingAroundAdvice in this case). When we - invoked the Execute(object context) method of the - proxy, the advice was 'applied' (executed), as can be - seen from the attendant output. - - The following image shows a graphical view of the flow of - execution through a Spring.NET AOP proxy. - - - - - - - - One thing to note here is that the AOP proxy that was returned - from the call to the GetProxy() method of the - ProxyFactory instance was cast to the - ICommand interface that the - ServiceCommand target object implemented. This is - very important... currently, Spring.NET's AOP implementation mandates - the use of an interface for advised objects. In short, this means that - in order for your classes to leverage Spring.NET's AOP support, those - classes that you wish to use with Spring.NET AOP must implement at least one interface. In - practice this restriction is not as onerous as it sounds... in any case, - it is generally good practice to program to - interfaces anyway (support for applying advice to classes that do not - implement any interfaces is planned for a future point release of - Spring.NET AOP). - - The remainder of this guide is concerned with fleshing out some of - the finer details of Spring.NET AOP, but basically speaking, that's - about it. - - As a first example of fleshing out one of those finer details, - find below some Spring.NET XML configuration that does - exactly the same thing as the previous example; it - should also be added that this declarative style approach to Spring.NET - AOP is preferred to the programmatic style. - - <object id="consoleLoggingAroundAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> - <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> - <property name="target"> - <object id="myServiceObjectTarget" - type="Spring.Examples.AopQuickStart.ServiceCommand"/> - </property> - <property name="interceptorNames"> - <list> - <value>consoleLoggingAroundAdvice</value> - </list> - </property> - </object> - - ICommand command = (ICommand) ctx["myServiceObject"]; - command.Execute(); - - Some comments are warranted concerning the above XML configuration - snippet. Firstly, note that the - ConsoleLoggingAroundAdvice is itself a plain - vanilla object, and is eligible for configuration just like any other - class... if the advice itself needed to be injected with any - dependencies, any such dependencies could be injected as normal. - - Secondly, notice that the object definition corresponding to the - object that is retrieved from the IoC container is a - ProxyFactoryObject. The - ProxyFactoryObject class is an implementation of - the IFactoryObject interface; - IFactoryObject implementations are treated - specially by the Spring.NET IoC container... in this specific case, it - is not a reference to the ProxyFactoryObject - instance itself that is returned, but rather the object that the - ProxyFactoryObject produces. In this case, it - will be an advised instance of the ServiceCommand - class. - - Thirdly, notice that the target of the - ProxyFactoryObject is an instance of the - ServiceCommand class; this is the object that is - going to be advised (i.e. invocations of its methods are going to be - intercepted). This object instance is defined as an inner object - definition... this is the preferred idiom for using the - ProxyFactoryObject, as it means that other - objects cannot acquire a reference to the raw object, but rather only - the advised object. - - Finally, notice that the advice that is to be applied to the - target object is referred to by its object name in the list of the names - of interceptors for the ProxyFactoryObject's - interceptorNames property. In this particular case, - there is only one instance of advice being applied... the - ConsoleLoggingAroundAdvice defined in an object - definition of the same name. The reason for using a list of object names - as opposed to references to the advice objects themselves is explained - in the reference documentation... - - '... if the ProxyFactoryObject's - singleton property is set to false, it must be able to return - independent proxy instances. If any of the advisors is itself a - prototype, an independent instance would need to be returned, so it is - necessary to be able to obtain an instance of the prototype from the - context; holding a reference isn't sufficient.' - - - - Using Pointcuts - the basics - - The advice that was applied in the previous section was rather - indiscriminate with regard to which methods on the advised object were - to be advised... the ConsoleLoggingAroundAdvice - simply intercepted all methods (that - were part of an interface implementation) on the target object. - - This is great for simple examples and suchlike, but not so great - when you only want certain methods of an object to be advised. For - example, you may only want those methods beginning with - 'Start' to be advised; or you may only want those - methods that are called with specific runtime argument values to be - advised; or you may only want those methods that are decorated with a - Lockable attribute to be advised. - - The mechanism that Spring.NET AOP uses to discriminate about where - advice is applied (i.e. which method invocations are intercepted) is - encapsulated by the IPointcut interface (see - ). Spring.NET provides many - out-of-the-box implementations of the IPointcut - interface... the implementation that is used if none is explicitly - supplied (as was the case with the first example) is the canonical - TruePointcut : as the name suggests, this - pointcut always matches, and hence all - methods that can be advised will be advised. - - So let's change the configuration of the advice such that it is - only applied to methods that contain the letters - 'Do'. We'll change the - ICommand interface (and it's attendant - implementation) to accommodate this... - - public interface ICommand - { - void Execute(); - - void DoExecute(); - } - - public class ServiceCommand : ICommand - { - public void Execute() - { - Console.Out.WriteLine("Service implementation : Execute()..."); - } - - public void DoExecute() - { - Console.Out.WriteLine("Service implementation : DoExecute()..."); - } - } - - Please note that the advice itself (encapsulated within the - ConsoleLoggingAroundAdvice class) does not need - to change; we are changing where this advice is - applied, and not the advice itself. - - Programmatic configuration of the advice, taking into account the - fact that we only want methods that contain the letters - 'Do' to be advised, looks like this... - - ProxyFactory factory = new ProxyFactory(new ServiceCommand()); - factory.AddAdvisor(new DefaultPointcutAdvisor( - new SdkRegularExpressionMethodPointcut("Do"), - new ConsoleLoggingAroundAdvice())); - ICommand command = (ICommand) factory.GetProxy(); - command.DoExecute(); - - The result of executing the above snippet of code will look - something like this... - - Intercepted call : about to invoke next item in chain... - Service implementation... - Intercepted call : returned - - The output indicates that the advice was applied around the - invocation of the advised method, because the name of the method that - was executed contained the letters 'Do'. Try changing - the pertinent code snippet to invoke the Execute() - method, like so... - - ProxyFactory factory = new ProxyFactory(new ServiceCommand()); - factory.AddAdvisor( - new DefaultPointcutAdvisor( - new SdkRegularExpressionMethodPointcut("Do"), - new ConsoleLoggingAroundAdvice())); - ICommand command = (ICommand) factory.GetProxy(); - - // note that there is no 'Do' in this method name - command.Execute(); - - Run the code snippet again; you will see that the advice will not - be applied : the pointcut is not matched (the method name does not - contain the letters 'Do'), resulting in the following - (unadvised) output... - - Service implementation... - - XML configuration that accomplishes exactly the same thing as the - previous programmatic configuration example can be seen below... - - <object id="consoleLoggingAroundAdvice" - type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor"> - <property name="pattern" value="Do"/> - <property name="advice"> - <object type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> - </property> - </object> - <object id="myServiceObject" - type="Spring.Aop.Framework.ProxyFactoryObject"> - <property name="target"> - <object id="myServiceObjectTarget" - type="Spring.Examples.AopQuickStart.ServiceCommand"/> - </property> - <property name="interceptorNames"> - <list> - <value>consoleLoggingAroundAdvice</value> - </list> - </property> - </object> - - You'll will perhaps have noticed that this treatment of pointcuts - introduced the concept of an advisor (see ). An advisor is nothing more the composition - of a pointcut (i.e. where advice is going to be - applied), and the advice itself (i.e. what is going - to happen at the interception point). The - consoleLoggingAroundAdvice object defines an advisor - that will apply the advice to all those methods of the advised object - that match the pattern 'Do' (the pointcut). The - pattern to match against is supplied as a simple string value to the - pattern property of the - RegularExpressionMethodPointcutAdvisor - class. - - - - - Going deeper - - The first section should (hopefully) have demonstrated the basics of - firstly defining advice, and secondly, of choosing where to apply that - advice using the notion of a pointcut. Of course, there is a great deal - more to Spring.NET AOP than the aforementioned single advice type and - pointcut. This section continues the exploration of Spring.NET AOP, and - describes the various advice and pointcuts that are available for you to - use (yes, there is more than one type of advice and pointcut). - - - Other types of Advice - - The advice that was demonstrated and explained in the preceding - section is what is termed 'around advice'. The name - 'around advice' is used because the advice is - applied around the target method invocation. In the - specific case of the ConsoleLoggingAroundAdvice - advice that was defined previously, the target was made available to the - advice as an IMethodInvocation object... a call - was made to the Console class before the target - was invoked, and a call was made to the Console - class after the target method invocation was invoked. The advice - surrounded the target, one could even say that the advice was totally - 'around' the target... hence the name, 'around - advice'. - - 'around advice' provides one with the - opportunity to do things both before - the target gets a chance to do anything, and after the target has returned: one even gets a - chance to inspect (and possibly even totally change) the return - value. - - Sometimes you don't need all that power though. If we stick with - the example of the ConsoleLoggingAroundAdvice - advice, what if one just wants to log the fact that a method was called? - In that case one doesn't need to do anything after - the target method invocation is to be invoked, nor do you need access to - the return value of the target method invocation. In fact, you only want - to do something before the target is to be invoked - (in this case, print out a message to the system - Console detailing the name of the method). In the - tradition of good programming that says one should use only what one - needs and no more, Spring.NET has another type of advice that one can - use... if one only wants to do something before the - target method invocation is invoked, why bother with having to manually - call the Proceed() method? The most expedient - solution simply is to use 'before advice'. - - - Before advice - - 'before advice' is just that... it is - advice that runs before the target method - invocation is invoked. One does not get access to the target method - invocation itself, and one cannot return a value... this is a good - thing, because it means that you cannot inadvertently forget to call - the Proceed() method on the target, and it also - means that you cannot inadvertently forget to return the return value - of the target method invocation. If you don't need to inspect or - change the return value, or even do anything after the successful - execution of the target method invocation, then 'before - advice' is just what you need. - - 'before advice' in Spring.NET is defined by - the IMethodBeforeAdvice interface in the - Spring.Aop namespace. Lets just dive in with an - example... we'll use the same scenario as before to keep things - simple. Let's define the 'before advice' - implementation first. - - public class ConsoleLoggingBeforeAdvice : IMethodBeforeAdvice - { - public void Before(MethodInfo method, object[] args, object target) - { - Console.Out.WriteLine("Intercepted call to this method : " + method.Name); - Console.Out.WriteLine(" The target is : " + target); - Console.Out.WriteLine(" The arguments are : "); - if(args != null) - { - foreach (object arg in args) - { - Console.Out.WriteLine("\t: " + arg); - } - } - } - } - - Let's apply a single instance of the - ConsoleLoggingBeforeAdvice advice to the - invocation of the Execute() method of the - ServiceCommand. What follows is programmatic - configuration; as you can see, its pretty much identical to the - previous version... the only difference is that we're using our new - 'before advice' (encapsulated as an instance of - the ConsoleLoggingBeforeAdvice class). - - ProxyFactory factory = new ProxyFactory(new ServiceCommand()); - factory.AddAdvice(new ConsoleLoggingBeforeAdvice()); - ICommand command = (ICommand) factory.GetProxy(); - command.Execute(); - - The result of executing the above snippet of code will look - something like this... - - Intercepted call to this method : Execute - The target is : Spring.Examples.AopQuickStart.ServiceCommand - The arguments are : - - The output clearly indicates that the advice was applied - before the invocation of the advised - method. Notice that in contrast to 'around - advice', with 'before advice' there is - no chance of forgetting to call the Proceed() - method on the target, because one does not have access to the - IMethodInvocation (as is the case with - 'around advice')... similarly, you cannot forget - to return the return value either. - - If you can use 'before advice', then do so. - The simpler programming model offered by 'before - advice' means that there is less to remember, and thus - potentially less things to get wrong. - - Here is the Spring.NET XML configuration for applying our - 'before advice' declaratively... - - <object id="beforeAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/> - - <object id="myServiceObject" - type="Spring.Aop.Framework.ProxyFactoryObject"> - <property name="target"> - <object id="myServiceObjectTarget" - type="Spring.Examples.AopQuickStart.ServiceCommand"/> - </property> - <property name="interceptorNames"> - <list> - <value>beforeAdvice</value> - </list> - </property> - </object> - - - - After advice - - Just as 'before advice' defines advice that - executes before an advised target, - 'after advice' is advice that executes after a target has been executed. - - 'after advice' in Spring.NET is defined by - the IAfterReturningAdvice interface in the - Spring.Aop namespace. Again, lets just fire on - ahead with an example... again, we'll use the same scenario as before - to keep things simple. - - public class ConsoleLoggingAfterAdvice : IAfterReturningAdvice - { - public void AfterReturning( - object returnValue, MethodInfo method, object[] args, object target) - { - Console.Out.WriteLine("This method call returned successfully : " + method.Name); - Console.Out.WriteLine(" The target was : " + target); - Console.Out.WriteLine(" The arguments were : "); - if(args != null) - { - foreach (object arg in args) - { - Console.Out.WriteLine("\t: " + arg); - } - } - Console.Out.WriteLine(" The return value is : " + returnValue); - } - } - - Let's apply a single instance of the - ConsoleLoggingAfterAdvice advice to the - invocation of the Execute() method of the - ServiceCommand. What follows is programmatic - configuration; as you can, its pretty much identical to the - 'before advice' version (which in turn was pretty - much identical to the original 'around advice' - version)... the only real difference is that we're using our new - 'after advice' (encapsulated as an instance of - the ConsoleLoggingAfterAdvice class). - - ProxyFactory factory = new ProxyFactory(new ServiceCommand()); - factory.AddAdvice(new ConsoleLoggingAfterAdvice()); - ICommand command = (ICommand) factory.GetProxy(); - command.Execute(); - - The result of executing the above snippet of code will look - something like this... - - This method call returned successfully : Execute - The target was : Spring.Examples.AopQuickStart.ServiceCommand - The arguments were : - The return value is : null - - The output clearly indicates that the advice was applied - after the invocation of the advised - method. Again, it bears repeating that your real world development - will actually have an advice implementation that does something useful - after the invocation of an advised method. Notice that in contrast to - 'around advice', with 'after - advice' there is no chance of forgetting to call the - Proceed() method on the target, because just like - 'before advice' you don't have access to the - IMethodInvocation... similarly, although you - get access to the return value of the target, you cannot forget to - return the return value either. You can however change the state of - the return value, typically by setting some of its properties, or by - calling methods on it. - - The best-practice rule for 'after advice' - is much the same as it is for 'before advice'; - namely that if you can use 'after advice', then - do so (in preference to using 'around advice'). - The simpler programming model offered by 'after - advice' means that there is less to remember, and thus less - things to get potentially wrong. - - A possible use case for 'after advice' - would include performing access control checks on the return value of - an advised method invocation; consider the case of a service that - returns a list of document URI's... depending on the identity of the - (Windows) user that is running the program that is calling this - service, one could strip out those URI's that contain sensitive data - for which the user does not have sufficient privileges to access. That - is just one (real world) scenario... I'm sure you can think of plenty - more that are a whole lot more relevant to your own development - needs. - - Here is the Spring.NET XML configuration for applying the - 'after advice' declaratively... - - <object id="afterAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/> - - <object id="myServiceObject" - type="Spring.Aop.Framework.ProxyFactoryObject"> - <property name="target"> - <object id="myServiceObjectTarget" - type="Spring.Examples.AopQuickStart.ServiceCommand"/> - </property> - <property name="interceptorNames"> - <list> - <value>afterAdvice</value> - </list> - </property> - </object> - - - - Throws advice - - So far we've covered 'around advice', - 'before advice', and 'after - advice'... these advice types will see you through most if - not all of your AOP needs. However, one of the remaining advice types - that Spring.NET has in its locker is 'throws - advice'. - - 'throws advice' is advice that executes - when an advised method invocation throws an - exception.. hence the name. One basically applies the - 'throws advice' to a target object in much the - same way as any of the previously mentioned advice types. If during - the execution of ones application none of any of the advised methods - throws an exception, then the 'throws advice' - will never execute. However, if during the execution of your - application an advised method does throw an - exception, then the 'throws advice' will kick in - and be executed. You can use 'throws advice' to - apply a common exception handling policy across the various objects in - your application, or to perform logging of every exception thown by an - advised method, or to alert (perhaps via email) the support team in - the case of particularly of critical exceptions... the list of - possible uses cases is of course endless. - - The 'throws advice' type in Spring.NET is - defined by the IThrowsAdvice interface in the - Spring.Aop namespace... basically, one defines on - one's 'throws advice' implementation class what - types of exception are going to be handled. Lets take a quick look at - the IThrowsAdvice interface... - - public interface IThrowsAdvice : IAdvice - { - } - - Yes, that is really it... it is a marker interface that has no - methods on it. You may be wondering how Spring.NET determines which - methods to call to effect the running of one's 'throws - advice'. An example would perhaps be illustrative at this - point, so here is some simple Spring.NET style 'throws - advice'... - - public class ConsoleLoggingThrowsAdvice : IThrowsAdvice - { - public void AfterThrowing(Exception ex) - { - Console.Out.WriteLine("Advised method threw this exception : " + ex); - } - } - - Lets also change the implementation of the - Execute() method of the - ServiceCommand class such that it throws an - exception. This will allow the advice encapsulated by the above - ConsoleLoggingThrowsAdvice to kick in. - - public class ServiceCommand : ICommand - { - public void Execute() - { - throw new UnauthorizedAccessException(); - } - } - - Let's programmatically apply the 'throws - advice' (an instance of our - ConsoleLoggingThrowsAdvice) to the invocation - of the Execute() method of the above - ServiceCommand class; to wit... - - ProxyFactory factory = new ProxyFactory(new ServiceCommand()); - factory.AddAdvice(new ConsoleLoggingThrowsAdvice()); - ICommand command = (ICommand) factory.GetProxy(); - command.Execute(); - - The result of executing the above snippet of code will look - something like this... - - Advised method threw this exception : System.UnauthorizedAccessException: - Attempted to perform an unauthorized operation. - - As can be seen from the output, the - ConsoleLoggingThrowsAdvice kicked in when the - advised method invocation threw an exception. There are a number of - things to note about the - ConsoleLoggingThrowsAdvice advice class, so - lets take them each in turn. - - In Spring.NET, 'throws advice' means that - you have to define a class that implements the - IThrowsAdvice interface. Then, for each type of - exception that your 'throws advice' is going to - handle, you have to define a method with this signature... - - void AfterThrowing(Exception ex) - - Basically, your exception handling method has to be named - AfterThrowing. This name is important... your - exception handling method(s) absolutely must be called - AfterThrowing. If your handler method is not called - AfterThrowing, then your 'throws - advice' will never be - called, it's as simple as that. Currently, this naming restriction is - not configurable (although it may well be opened up for configuration - in the future). - - Your exception handling method must (at the very least) declare - a parameter that is an Exception type... this - parameter can be the root Exception class (as - in the case of the above example), or it can be an - Exception subclass if you only want to handle - certain types of exception. It is good practice to always make your - exception handling methods have an Exception - parameter that is the most specialized - Exception type possible... i.e. if you are - applying 'throws advice' to a method that could - only ever throw ArgumentExceptions, then - declare the parameter of your exception handling method as... - - void AfterThrowing(ArgumentException ex) - - Note that your exception handling method can have any return - type, but returning any value from a Spring.NET 'throws - advice' method would be a waste of time... the Spring.NET - AOP infrastructure will simply ignore the return value, so always - define the return type of your exception handling methods to be - void. - - Finally, here is the Spring.NET XML configuration for applying - the 'throws advice' declaratively... - - <object id="throwsAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/> - - <object id="myServiceObject" - type="Spring.Aop.Framework.ProxyFactoryObject"> - <property name="target"> - <object id="myServiceObjectTarget" - type="Spring.Examples.AopQuickStart.ServiceCommand"/> - </property> - <property name="interceptorNames"> - <list> - <value>throwsAdvice</value> - </list> - </property> - </object> - - One thing that cannot be done using 'throws - advice' is exception swallowing. It is not possible to - define an exception handling method in a 'throws - advice' implementation that will swallow any exception and - prevent said exception from bubbling up the call stack. The nearest - thing that one can do is define an exception handling method in a - 'throws advice' implementation that will wrap the - handled exception in another exception; one would then throw the - wrapped exception in the body of one's exception handling method. One - can use this to implement some sort of exception translation or - exception scrubbing policy, in which implementation specific - exceptions (such as SqlException or - OracleException exceptions being thrown by an - advised data access object) get replaced with a business exception - that has meaning to the service objects in one's business layer. A toy - example of this type of 'throws advice' can be - seen below. - - public class DataAccessExceptionScrubbingThrowsAdvice : IThrowsAdvice - { - public void AfterThrowing (SqlException ex) - { - // business objects in higher level service layer need only deal with PersistenceException... - throw new PersistenceException ("Cannot access persistent storage.", ex.StackTrace); - } - } - - Spring.NET's data access library already has this - kind of functionality (and is a whole lot more sophisticated)... the - above example is merely being used for illustrative purposes. - - - This treatment of 'throws advice', and of - Spring.NET's implementation of it is rather simplistic. - 'throws advice' features that have been omitted - include the fact that one can define exception handling methods that - permit access to the original object, method, and method arguments of - the advised method invocation that threw the original exception. This - is a quickstart guide though, and is not meant to be exhaustive... do - consult the 'throws advice' section of the - reference documentation, which describes how to declare an exception - handling method that gives one access to the above extra objects, and - how to declare multiple exception handling methods on the same - IThrowsAdvice implementation class (see ). - - - - Introductions (mixins) - - In a nutshell, introductions are all about adding new state and - behaviour to arbitrary objects... transparently and at runtime. - Introductions (also called mixins) allow one to emulate multiple - inheritance, typically with an eye towards applying crosscutting state - and operations to a wide swathe of objects in your application that - don't share the same inheritance hierarchy. - - - - Layering advice - - The examples shown so far have all demonstrated the application - of a single advice instance to an advised object. Spring.NET's flavor - of AOP would be pretty poor if one could only apply a single advice - instance per advised object... it is perfectly valid to apply multiple - advice to an advised object. For example, one might apply - transactional advice to a service object, and also apply a security - access checking advice to that same advised service object. - - In the interests of keeping this section lean and tight, let's - simply apply all of the advice types that have - been previously described to a single advised object... in this first - instance we'll just use the default pointcut which means that every - possible joinpoint will be advised, and you'll be able to see that the - various advice instances are applied in order. - - Please do consult the class definitions for the following - previously defined advice types to see exactly what each advice type - implementation does... we're going to be using single instances of the - ConsoleLoggingAroundAdvice, - ConsoleLoggingBeforeAdvice, - ConsoleLoggingAfterAdvice, and - ConsoleLoggingThrowsAdvice advice to advise a - single instance of the ServiceCommand - class. - - You can find the following listing and executable application in - the AopQuickStart solution in the project - Spring.AopQuickStart.Step1. - - ProxyFactory factory = new ProxyFactory(new ServiceCommand()); - factory.AddAdvice(new ConsoleLoggingBeforeAdvice()); - factory.AddAdvice(new ConsoleLoggingAfterAdvice()); - factory.AddAdvice(new ConsoleLoggingThrowsAdvice()); - factory.AddAdvice(new ConsoleLoggingAroundAdvice()); - ICommand command = (ICommand) factory.GetProxy(); - command.Execute(); - - Here is the Spring.NET XML configuration for declaratively - applying multiple advice. - - You can find the following listing and executable application in - the AopQuickStart solution in the project - Spring.AopQuickStart.Step2. - - <object id="throwsAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/> - <object id="afterAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/> - <object id="beforeAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/> - <object id="aroundAdvice" - type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> - - <object id="myServiceObject" - type="Spring.Aop.Framework.ProxyFactoryObject"> - <property name="target"> - <object id="myServiceObjectTarget" - type="Spring.Examples.AopQuickStart.ServiceCommand"/> - </property> - <property name="interceptorNames"> - <list> - <value>throwsAdvice</value> - <value>afterAdvice</value> - <value>beforeAdvice</value> - <value>aroundAdvice</value> - </list> - </property> - </object> - - - - Configuring advice - - In case it is not immediately apparent, remember that advice is - just a plain old .NET object (a PONO); advice can have constructors - that can take any number of parameters, and like any other .NET class, - advice can have properties. What this means is that one can leverage - the power of the Spring.NET IoC container to apply the IoC principle - to one's advice, and in so doing reap all the benefits of Dependency - Injection. - - Consider the case of throws advice that needs to report (fatal) - exceptions to a first line support centre. The throws advice could - declare a dependency on a reporting service via a .NET property, and - the Spring.NET container could dependency inject the reporting service - dependency into the throws advice when it is being created; the - reporting dependency might be a simple Log4NET wrapper, or a Windows - EventLog wrapper, or a custom reporting exception reporting service - that sends detailed emails concerning the fatal exception. - - Also bear in mind the fact that Spring.NET's AOP implementation - is quite independent of Spring.NET's IoC container. As you have seen, - the various examples used in this have illustrated both programmatic - and declarative AOP configuration (the latter being illustrated via - Spring.NET's IoC XML configuration mechanism). - - - - - Using Attributes to define Pointcuts - - - - - - - The Spring.NET AOP Cookbook - - The preceding treatment of Spring.NET AOP has (quite intentionally) - been decidedly simple. The overarching aim was to convey the concepts of - Spring.NET AOP... this section of the Spring.NET AOP guide contains a - number of real world examples of the application of Spring.NET AOP. - - - Caching - - This example illustrates one of the more common usages of AOP... - caching. - - Lets consider the scenario where we have some static reference - data that needs to be kept around for the duration of an application. - The data will almost never change over the uptime of an application, and - it exists only in the database to satisfy referential integrity amongst - the various relations in the database schema. An example of such static - (and typically immutable) reference data would be a collection of - Country objects (comprising a country name and a - code). What we would like to do is suck in the collection of - Country objects and then pin them in a cache. - This saves us having to hit the back end database again and again every - time we need to reference a country in our application (for example, to - populate dropdown controls in a Windows Forms desktop - application). - - The Data Access Object (DAO) that will load the collection of - Country objects is called - AdoCountryDao (it is an implementation of the - data-access-technology agnostic DAO interface called - ICountryDao). The implementation of the - AdoCountryDao is quite simple, in that every time - the FindAllCountries instance method is called, an - instance will query the database for an - IDataReader and hydrate zero or more - Country objects using the returned data. - - public class AdoCountryDao : ICountryDao - { - public IList FindAllCountries () - { - // implementation elided for clarity... - return countries; - } - } - - Ideally, what we would like to have happen is for the results of - the first call to the - FindAllCountries instance method to be cached. We - would also like to do this in a non-invasive way, because caching is - something that we might want to apply at any number of points across the - codebase of our application. So, to address what we have identified as a - cross cutting concern, we can use Spring.NET AOP to - implement the caching. - - The mechanism that this example is going to use to identify (or - pick out) areas in our application that we would like to apply caching - to is a .NET Attribute. Spring.NET ships with a - number of useful custom .NET Attribute - implementations, one of which is the cunningly named - CacheAttribute. In the specific case of this - example, we are simply going to decorate the definition of the - FindAllCountries instance method with the - CacheAttribute. - - public class AdoCountryDao : ICountryDao - { - [Cache] - public IList FindAllCountries () - { - // implementation elided for clarity... - return countries; - } - } - - The SpringAir reference application that is packaged as part of - the Spring.NET distribution comes with a working example of caching - applied using Spring.NET AOP (see ). - - - - Performance Monitoring - - This recipe show how easy it is to instrument the classes and - objects in an application for performance monitoring. The performance - monitoring implementation uses one of the (many) Windows performance - counters to display and track the performance data. - - - - Retry Rules - - This final recipe describes a simple (but really quite useful) - aspect... retry logic. Using Spring.NET AOP, it is quite easy to - surround an operation such as a method that opens a connection to a - database with a (configurable) aspect that tries to obtain a database - connection any number of times in the event of a failure. - - - - - Spring.NET AOP Best Practices - - Spring.NET AOP is an 80% AOP solution, in that it only tries to - solve the 80% of those cases where AOP is a good fit in a typical - enterprise application. This final section of the Spring.NET AOP guide - describes where Spring.NET AOP is typically useful (the 80%), as well as - where Spring.NET AOP is not a good fit (the 20%). - - \ No newline at end of file + + + + AOP QuickStart + + + Introduction + + This is an introductory guide to Aspect Oriented Programming (AOP) + with Spring.NET. + + This guide assumes little to no prior experience of having + used Spring.NET AOP on the part of the reader. + However, it does assume a certain familiarity with + the terminology of AOP in general. It is probably better if you have read + (or at least have skimmed through) the AOP section of the reference + documentation beforehand, so that you are familiar with a) just what AOP + is, b) what problems AOP is addressing, and c) what the AOP concepts of + advice, pointcut, and + joinpoint actually mean... this guide spends absolutely + zero time defining those terms. Having said all that, if you are the kind + of developer who learns best by example, then by all means follow along... + you can always consult the reference documentation as the need arises (see + ). + + The examples in this guide are intentionally simplistic. One of the core aims of + this guide is to get you up and running with Spring.NET's flavor of AOP in + as short a time as possible. Having to comprehend even a simple object + model in order to understand the AOP examples would not be conducive to + learning Spring.NET AOP. It is left as an exercise for the reader to take + the concepts learned from this guide and apply them to his or her own code + base. Again, having said all of that, this guide concludes with a number + of cookbook-style AOP 'recipes' that illustrate the application of + Spring.NET's AOP offering in a real world context; additionally, the + Spring.NET reference application contains a number of Spring.NET AOP + aspects particular to it's own domain model (see ). + + + To follow this AOP QuickStart load the solution file found in the + directory + <spring-install-dir>\examples\Spring\Spring.AopQuickStart + + + + + The basics + + This initial section introduces the basics of defining and then + applying some simple advice. + + + Applying advice + + Lets see (a very basic) example of using Spring.NET AOP. The + following example code simply applies advice that writes the details of + an advised method call to the system console. Admittedly, this is not a + particularly compelling or even useful application of AOP, but having + worked through the example, you will then hopefully be able to see how + to apply your own custom advice to perform useful work (transaction + management, auditing, security enforcement, thread safety, etc). + + Before looking at the AOP code proper lets quickly look at the + domain classes that are the target of the advice (in Spring.NET AOP + terminology, an instance of the following class is going to be the + advised object. + + public interface ICommand +{ + object Execute(object context); +} + +public class ServiceCommand : ICommand +{ + public object Execute(object context) + { + Console.Out.WriteLine("Service implementation : [{0}]", context); + return null; + } +} + + Find below the advice that is going to be applied to the + object Execute(object context) method of the + ServiceCommand class. As you can see, this is an + example of around advice (see ). + + public class ConsoleLoggingAroundAdvice : IMethodInterceptor + { + public object Invoke(IMethodInvocation invocation) + { + Console.Out.WriteLine("Advice executing; calling the advised method..."); + object returnValue = invocation.Proceed(); + Console.Out.WriteLine("Advice executed; advised method returned " + returnValue); + return returnValue; + } + } + + + + Some simple code that merely prints out the fact that the advice is executing. + + + + The advised method is invoked. + + + + The return value is captured in the + + returnValue + + variable. + + + + The value of the captured + + returnValue + + is printed out. + + + + The previously captured + + returnValue + + is returned. + + + + So thus far we have three artifacts: an interface + (ICommand); an implementation of said interface + (ServiceCommand); and some (trivial) advice + (encapsulated by the ConsoleLoggingAroundAdvice + class). All that remains is to actually apply the + ConsoleLoggingAroundAdvice advice to the invocation + of the Execute() method of the + ServiceCommand class. Lets look at how to effect this + programmatically... + + ProxyFactory factory = new ProxyFactory(new ServiceCommand()); + factory.AddAdvice(new ConsoleLoggingAroundAdvice()); + ICommand command = (ICommand) factory.GetProxy(); + command.Execute("This is the argument"); + + The result of executing the above snippet of code will look + something like this... + + Advice executing; calling the advised method... + Service implementation : [This is the argument] + Advice executed; advised method returned + + The output shows that the advice (the + Console.Out statements from the + ConsoleLoggingAroundAdvice was applied + around the invocation of the advised method. + + So what is happening here? The fact that the preceding code used a + class called ProxyFactory may have clued you in. The + constructor for the ProxyFactory class took as an + argument the object that we wanted to advise (in this case, an instance + of the ServiceCommand class). We then added some + advice (a ConsoleLoggingAroundAdvice instance) using + the AddAdvice() method of the + ProxyFactory instance. We then called the + GetProxy() method of the + ProxyFactory instance which gave us a proxy... an + (AOP) proxy that proxied the target object (the + ServiceCommand instance), and called the advice (a + single instance of the ConsoleLoggingAroundAdvice in + this case). When we invoked the Execute(object + context) method of the proxy, the advice was + 'applied' (executed), as can be seen from the + attendant output. + + The following image shows a graphical view of the flow of + execution through a Spring.NET AOP proxy. + + + + + + + + One thing to note here is that the AOP proxy that was returned + from the call to the GetProxy() method of the + ProxyFactory instance was cast to the + ICommand interface that the + ServiceCommand target object implemented. This is + very important... currently, Spring.NET's AOP implementation mandates + the use of an interface for advised objects. In short, this means that + in order for your classes to leverage Spring.NET's AOP support, those + classes that you wish to use with Spring.NET AOP must implement at least one interface. In + practice this restriction is not as onerous as it sounds... in any case, + it is generally good practice to program to + interfaces anyway (support for applying advice to classes that do not + implement any interfaces is planned for a future point release of + Spring.NET AOP). + + The remainder of this guide is concerned with fleshing out some of + the finer details of Spring.NET AOP, but basically speaking, that's + about it. + + As a first example of fleshing out one of those finer details, + find below some Spring.NET XML configuration that does + exactly the same thing as the previous example; it + should also be added that this declarative style approach to Spring.NET + AOP is preferred to the programmatic style. + + <object id="consoleLoggingAroundAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> + <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> + <property name="target"> + <object id="myServiceObjectTarget" + type="Spring.Examples.AopQuickStart.ServiceCommand"/> + </property> + <property name="interceptorNames"> + <list> + <value>consoleLoggingAroundAdvice</value> + </list> + </property> + </object> + + ICommand command = (ICommand) ctx["myServiceObject"]; + command.Execute(); + + Some comments are warranted concerning the above XML configuration + snippet. Firstly, note that the + ConsoleLoggingAroundAdvice is itself a plain vanilla + object, and is eligible for configuration just like any other class... + if the advice itself needed to be injected with any dependencies, any + such dependencies could be injected as normal. + + Secondly, notice that the object definition corresponding to the + object that is retrieved from the IoC container is a + ProxyFactoryObject. The + ProxyFactoryObject class is an implementation of the + IFactoryObject interface; + IFactoryObject implementations are treated specially + by the Spring.NET IoC container... in this specific case, it is not a + reference to the ProxyFactoryObject instance itself + that is returned, but rather the object that the + ProxyFactoryObject produces. In this case, it will be + an advised instance of the ServiceCommand + class. + + Thirdly, notice that the target of the + ProxyFactoryObject is an instance of the + ServiceCommand class; this is the object that is + going to be advised (i.e. invocations of its methods are going to be + intercepted). This object instance is defined as an inner object + definition... this is the preferred idiom for using the + ProxyFactoryObject, as it means that other objects + cannot acquire a reference to the raw object, but rather only the + advised object. + + Finally, notice that the advice that is to be applied to the + target object is referred to by its object name in the list of the names + of interceptors for the ProxyFactoryObject's + interceptorNames property. In this particular case, + there is only one instance of advice being applied... the + ConsoleLoggingAroundAdvice defined in an object + definition of the same name. The reason for using a list of object names + as opposed to references to the advice objects themselves is explained + in the reference documentation... + + '... if the ProxyFactoryObject's + singleton property is set to false, it must be able to return + independent proxy instances. If any of the advisors is itself a + prototype, an independent instance would need to be returned, so it is + necessary to be able to obtain an instance of the prototype from the + context; holding a reference isn't sufficient.' + + + + Using Pointcuts - the basics + + The advice that was applied in the previous section was rather + indiscriminate with regard to which methods on the advised object were + to be advised... the ConsoleLoggingAroundAdvice + simply intercepted all methods (that + were part of an interface implementation) on the target object. + + This is great for simple examples and suchlike, but not so great + when you only want certain methods of an object to be advised. For + example, you may only want those methods beginning with + 'Start' to be advised; or you may only want those + methods that are called with specific runtime argument values to be + advised; or you may only want those methods that are decorated with a + Lockable attribute to be advised. + + The mechanism that Spring.NET AOP uses to discriminate about where + advice is applied (i.e. which method invocations are intercepted) is + encapsulated by the IPointcut interface (see ). Spring.NET provides many out-of-the-box + implementations of the IPointcut interface... the + implementation that is used if none is explicitly supplied (as was the + case with the first example) is the canonical + TruePointcut : as the name suggests, this pointcut + always matches, and hence all methods + that can be advised will be advised. + + So let's change the configuration of the advice such that it is + only applied to methods that contain the letters + 'Do'. We'll change the ICommand + interface (and it's attendant implementation) to accommodate + this... + + public interface ICommand + { + void Execute(); + + void DoExecute(); + } + + public class ServiceCommand : ICommand + { + public void Execute() + { + Console.Out.WriteLine("Service implementation : Execute()..."); + } + + public void DoExecute() + { + Console.Out.WriteLine("Service implementation : DoExecute()..."); + } + } + + Please note that the advice itself (encapsulated within the + ConsoleLoggingAroundAdvice class) does not need to + change; we are changing where this advice is + applied, and not the advice itself. + + Programmatic configuration of the advice, taking into account the + fact that we only want methods that contain the letters + 'Do' to be advised, looks like this... + + ProxyFactory factory = new ProxyFactory(new ServiceCommand()); + factory.AddAdvisor(new DefaultPointcutAdvisor( + new SdkRegularExpressionMethodPointcut("Do"), + new ConsoleLoggingAroundAdvice())); + ICommand command = (ICommand) factory.GetProxy(); + command.DoExecute(); + + The result of executing the above snippet of code will look + something like this... + + Intercepted call : about to invoke next item in chain... + Service implementation... + Intercepted call : returned + + The output indicates that the advice was applied around the + invocation of the advised method, because the name of the method that + was executed contained the letters 'Do'. Try changing + the pertinent code snippet to invoke the Execute() + method, like so... + + ProxyFactory factory = new ProxyFactory(new ServiceCommand()); + factory.AddAdvisor( + new DefaultPointcutAdvisor( + new SdkRegularExpressionMethodPointcut("Do"), + new ConsoleLoggingAroundAdvice())); + ICommand command = (ICommand) factory.GetProxy(); + + // note that there is no 'Do' in this method name + command.Execute(); + + Run the code snippet again; you will see that the advice will not + be applied : the pointcut is not matched (the method name does not + contain the letters 'Do'), resulting in the following + (unadvised) output... + + Service implementation... + + XML configuration that accomplishes exactly the same thing as the + previous programmatic configuration example can be seen below... + + <object id="consoleLoggingAroundAdvice" + type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor"> + <property name="pattern" value="Do"/> + <property name="advice"> + <object type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> + </property> + </object> + <object id="myServiceObject" + type="Spring.Aop.Framework.ProxyFactoryObject"> + <property name="target"> + <object id="myServiceObjectTarget" + type="Spring.Examples.AopQuickStart.ServiceCommand"/> + </property> + <property name="interceptorNames"> + <list> + <value>consoleLoggingAroundAdvice</value> + </list> + </property> + </object> + + You'll will perhaps have noticed that this treatment of pointcuts + introduced the concept of an advisor (see ). An advisor is nothing more the composition + of a pointcut (i.e. where advice is going to be + applied), and the advice itself (i.e. what is going + to happen at the interception point). The + consoleLoggingAroundAdvice object defines an advisor + that will apply the advice to all those methods of the advised object + that match the pattern 'Do' (the pointcut). The + pattern to match against is supplied as a simple string value to the + pattern property of the + RegularExpressionMethodPointcutAdvisor class. + + + + + Going deeper + + The first section should (hopefully) have demonstrated the basics of + firstly defining advice, and secondly, of choosing where to apply that + advice using the notion of a pointcut. Of course, there is a great deal + more to Spring.NET AOP than the aforementioned single advice type and + pointcut. This section continues the exploration of Spring.NET AOP, and + describes the various advice and pointcuts that are available for you to + use (yes, there is more than one type of advice and pointcut). + + + Other types of Advice + + The advice that was demonstrated and explained in the preceding + section is what is termed 'around advice'. The name + 'around advice' is used because the advice is + applied around the target method invocation. In the + specific case of the ConsoleLoggingAroundAdvice + advice that was defined previously, the target was made available to the + advice as an IMethodInvocation object... a call was + made to the Console class before the target was + invoked, and a call was made to the Console class + after the target method invocation was invoked. The advice surrounded + the target, one could even say that the advice was totally 'around' the + target... hence the name, 'around advice'. + + 'around advice' provides one with the + opportunity to do things both before + the target gets a chance to do anything, and after the target has returned: one even gets a + chance to inspect (and possibly even totally change) the return + value. + + Sometimes you don't need all that power though. If we stick with + the example of the ConsoleLoggingAroundAdvice advice, + what if one just wants to log the fact that a method was called? In that + case one doesn't need to do anything after the + target method invocation is to be invoked, nor do you need access to the + return value of the target method invocation. In fact, you only want to + do something before the target is to be invoked (in + this case, print out a message to the system Console + detailing the name of the method). In the tradition of good programming + that says one should use only what one needs and no more, Spring.NET has + another type of advice that one can use... if one only wants to do + something before the target method invocation is + invoked, why bother with having to manually call the + Proceed() method? The most expedient solution simply + is to use 'before advice'. + + + Before advice + + 'before advice' is just that... it is + advice that runs before the target method + invocation is invoked. One does not get access to the target method + invocation itself, and one cannot return a value... this is a good + thing, because it means that you cannot inadvertently forget to call + the Proceed() method on the target, and it also + means that you cannot inadvertently forget to return the return value + of the target method invocation. If you don't need to inspect or + change the return value, or even do anything after the successful + execution of the target method invocation, then 'before + advice' is just what you need. + + 'before advice' in Spring.NET is defined by + the IMethodBeforeAdvice interface in the + Spring.Aop namespace. Lets just dive in with an + example... we'll use the same scenario as before to keep things + simple. Let's define the 'before advice' + implementation first. + + public class ConsoleLoggingBeforeAdvice : IMethodBeforeAdvice + { + public void Before(MethodInfo method, object[] args, object target) + { + Console.Out.WriteLine("Intercepted call to this method : " + method.Name); + Console.Out.WriteLine(" The target is : " + target); + Console.Out.WriteLine(" The arguments are : "); + if(args != null) + { + foreach (object arg in args) + { + Console.Out.WriteLine("\t: " + arg); + } + } + } + } + + Let's apply a single instance of the + ConsoleLoggingBeforeAdvice advice to the invocation + of the Execute() method of the + ServiceCommand. What follows is programmatic + configuration; as you can see, its pretty much identical to the + previous version... the only difference is that we're using our new + 'before advice' (encapsulated as an instance of + the ConsoleLoggingBeforeAdvice class). + + ProxyFactory factory = new ProxyFactory(new ServiceCommand()); + factory.AddAdvice(new ConsoleLoggingBeforeAdvice()); + ICommand command = (ICommand) factory.GetProxy(); + command.Execute(); + + The result of executing the above snippet of code will look + something like this... + + Intercepted call to this method : Execute + The target is : Spring.Examples.AopQuickStart.ServiceCommand + The arguments are : + + The output clearly indicates that the advice was applied + before the invocation of the advised + method. Notice that in contrast to 'around + advice', with 'before advice' there is + no chance of forgetting to call the Proceed() + method on the target, because one does not have access to the + IMethodInvocation (as is the case with + 'around advice')... similarly, you cannot forget + to return the return value either. + + If you can use 'before advice', then do so. + The simpler programming model offered by 'before + advice' means that there is less to remember, and thus + potentially less things to get wrong. + + Here is the Spring.NET XML configuration for applying our + 'before advice' declaratively... + + <object id="beforeAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/> + + <object id="myServiceObject" + type="Spring.Aop.Framework.ProxyFactoryObject"> + <property name="target"> + <object id="myServiceObjectTarget" + type="Spring.Examples.AopQuickStart.ServiceCommand"/> + </property> + <property name="interceptorNames"> + <list> + <value>beforeAdvice</value> + </list> + </property> + </object> + + + + After advice + + Just as 'before advice' defines advice that + executes before an advised target, + 'after advice' is advice that executes after a target has been executed. + + 'after advice' in Spring.NET is defined by + the IAfterReturningAdvice interface in the + Spring.Aop namespace. Again, lets just fire on + ahead with an example... again, we'll use the same scenario as before + to keep things simple. + + public class ConsoleLoggingAfterAdvice : IAfterReturningAdvice + { + public void AfterReturning( + object returnValue, MethodInfo method, object[] args, object target) + { + Console.Out.WriteLine("This method call returned successfully : " + method.Name); + Console.Out.WriteLine(" The target was : " + target); + Console.Out.WriteLine(" The arguments were : "); + if(args != null) + { + foreach (object arg in args) + { + Console.Out.WriteLine("\t: " + arg); + } + } + Console.Out.WriteLine(" The return value is : " + returnValue); + } + } + + Let's apply a single instance of the + ConsoleLoggingAfterAdvice advice to the invocation + of the Execute() method of the + ServiceCommand. What follows is programmatic + configuration; as you can, its pretty much identical to the + 'before advice' version (which in turn was pretty + much identical to the original 'around advice' + version)... the only real difference is that we're using our new + 'after advice' (encapsulated as an instance of + the ConsoleLoggingAfterAdvice class). + + ProxyFactory factory = new ProxyFactory(new ServiceCommand()); + factory.AddAdvice(new ConsoleLoggingAfterAdvice()); + ICommand command = (ICommand) factory.GetProxy(); + command.Execute(); + + The result of executing the above snippet of code will look + something like this... + + This method call returned successfully : Execute + The target was : Spring.Examples.AopQuickStart.ServiceCommand + The arguments were : + The return value is : null + + The output clearly indicates that the advice was applied + after the invocation of the advised + method. Again, it bears repeating that your real world development + will actually have an advice implementation that does something useful + after the invocation of an advised method. Notice that in contrast to + 'around advice', with 'after + advice' there is no chance of forgetting to call the + Proceed() method on the target, because just like + 'before advice' you don't have access to the + IMethodInvocation... similarly, although you get + access to the return value of the target, you cannot forget to return + the return value either. You can however change the state of the + return value, typically by setting some of its properties, or by + calling methods on it. + + The best-practice rule for 'after advice' + is much the same as it is for 'before advice'; + namely that if you can use 'after advice', then + do so (in preference to using 'around advice'). + The simpler programming model offered by 'after + advice' means that there is less to remember, and thus less + things to get potentially wrong. + + A possible use case for 'after advice' + would include performing access control checks on the return value of + an advised method invocation; consider the case of a service that + returns a list of document URI's... depending on the identity of the + (Windows) user that is running the program that is calling this + service, one could strip out those URI's that contain sensitive data + for which the user does not have sufficient privileges to access. That + is just one (real world) scenario... I'm sure you can think of plenty + more that are a whole lot more relevant to your own development + needs. + + Here is the Spring.NET XML configuration for applying the + 'after advice' declaratively... + + <object id="afterAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/> + + <object id="myServiceObject" + type="Spring.Aop.Framework.ProxyFactoryObject"> + <property name="target"> + <object id="myServiceObjectTarget" + type="Spring.Examples.AopQuickStart.ServiceCommand"/> + </property> + <property name="interceptorNames"> + <list> + <value>afterAdvice</value> + </list> + </property> + </object> + + + + Throws advice + + So far we've covered 'around advice', + 'before advice', and 'after + advice'... these advice types will see you through most if + not all of your AOP needs. However, one of the remaining advice types + that Spring.NET has in its locker is 'throws + advice'. + + 'throws advice' is advice that executes + when an advised method invocation throws an + exception.. hence the name. One basically applies the + 'throws advice' to a target object in much the + same way as any of the previously mentioned advice types. If during + the execution of ones application none of any of the advised methods + throws an exception, then the 'throws advice' + will never execute. However, if during the execution of your + application an advised method does throw an + exception, then the 'throws advice' will kick in + and be executed. You can use 'throws advice' to + apply a common exception handling policy across the various objects in + your application, or to perform logging of every exception thown by an + advised method, or to alert (perhaps via email) the support team in + the case of particularly of critical exceptions... the list of + possible uses cases is of course endless. + + The 'throws advice' type in Spring.NET is + defined by the IThrowsAdvice interface in the + Spring.Aop namespace... basically, one defines on + one's 'throws advice' implementation class what + types of exception are going to be handled. Lets take a quick look at + the IThrowsAdvice interface... + + public interface IThrowsAdvice : IAdvice + { + } + + Yes, that is really it... it is a marker interface that has no + methods on it. You may be wondering how Spring.NET determines which + methods to call to effect the running of one's 'throws + advice'. An example would perhaps be illustrative at this + point, so here is some simple Spring.NET style 'throws + advice'... + + public class ConsoleLoggingThrowsAdvice : IThrowsAdvice + { + public void AfterThrowing(Exception ex) + { + Console.Out.WriteLine("Advised method threw this exception : " + ex); + } + } + + Lets also change the implementation of the + Execute() method of the + ServiceCommand class such that it throws an + exception. This will allow the advice encapsulated by the above + ConsoleLoggingThrowsAdvice to kick in. + + public class ServiceCommand : ICommand + { + public void Execute() + { + throw new UnauthorizedAccessException(); + } + } + + Let's programmatically apply the 'throws + advice' (an instance of our + ConsoleLoggingThrowsAdvice) to the invocation of + the Execute() method of the above + ServiceCommand class; to wit... + + ProxyFactory factory = new ProxyFactory(new ServiceCommand()); + factory.AddAdvice(new ConsoleLoggingThrowsAdvice()); + ICommand command = (ICommand) factory.GetProxy(); + command.Execute(); + + The result of executing the above snippet of code will look + something like this... + + Advised method threw this exception : System.UnauthorizedAccessException: + Attempted to perform an unauthorized operation. + + As can be seen from the output, the + ConsoleLoggingThrowsAdvice kicked in when the + advised method invocation threw an exception. There are a number of + things to note about the ConsoleLoggingThrowsAdvice + advice class, so lets take them each in turn. + + In Spring.NET, 'throws advice' means that + you have to define a class that implements the + IThrowsAdvice interface. Then, for each type of + exception that your 'throws advice' is going to + handle, you have to define a method with this signature... + + void AfterThrowing(Exception ex) + + Basically, your exception handling method has to be named + AfterThrowing. This name is important... your + exception handling method(s) absolutely must be called + AfterThrowing. If your handler method is not called + AfterThrowing, then your 'throws + advice' will never be + called, it's as simple as that. Currently, this naming restriction is + not configurable (although it may well be opened up for configuration + in the future). + + Your exception handling method must (at the very least) declare + a parameter that is an Exception type... this + parameter can be the root Exception class (as in + the case of the above example), or it can be an + Exception subclass if you only want to handle + certain types of exception. It is good practice to always make your + exception handling methods have an Exception + parameter that is the most specialized Exception + type possible... i.e. if you are applying 'throws + advice' to a method that could only ever throw + ArgumentExceptions, then declare the parameter of + your exception handling method as... + + void AfterThrowing(ArgumentException ex) + + Note that your exception handling method can have any return + type, but returning any value from a Spring.NET 'throws + advice' method would be a waste of time... the Spring.NET + AOP infrastructure will simply ignore the return value, so always + define the return type of your exception handling methods to be + void. + + Finally, here is the Spring.NET XML configuration for applying + the 'throws advice' declaratively... + + <object id="throwsAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/> + + <object id="myServiceObject" + type="Spring.Aop.Framework.ProxyFactoryObject"> + <property name="target"> + <object id="myServiceObjectTarget" + type="Spring.Examples.AopQuickStart.ServiceCommand"/> + </property> + <property name="interceptorNames"> + <list> + <value>throwsAdvice</value> + </list> + </property> + </object> + + One thing that cannot be done using 'throws + advice' is exception swallowing. It is not possible to + define an exception handling method in a 'throws + advice' implementation that will swallow any exception and + prevent said exception from bubbling up the call stack. The nearest + thing that one can do is define an exception handling method in a + 'throws advice' implementation that will wrap the + handled exception in another exception; one would then throw the + wrapped exception in the body of one's exception handling method. One + can use this to implement some sort of exception translation or + exception scrubbing policy, in which implementation specific + exceptions (such as SqlException or + OracleException exceptions being thrown by an + advised data access object) get replaced with a business exception + that has meaning to the service objects in one's business layer. A toy + example of this type of 'throws advice' can be + seen below. + + public class DataAccessExceptionScrubbingThrowsAdvice : IThrowsAdvice + { + public void AfterThrowing (SqlException ex) + { + // business objects in higher level service layer need only deal with PersistenceException... + throw new PersistenceException ("Cannot access persistent storage.", ex.StackTrace); + } + } + + Spring.NET's data access library already has this + kind of functionality (and is a whole lot more sophisticated)... the + above example is merely being used for illustrative purposes. + + + This treatment of 'throws advice', and of + Spring.NET's implementation of it is rather simplistic. + 'throws advice' features that have been omitted + include the fact that one can define exception handling methods that + permit access to the original object, method, and method arguments of + the advised method invocation that threw the original exception. This + is a quickstart guide though, and is not meant to be exhaustive... do + consult the 'throws advice' section of the + reference documentation, which describes how to declare an exception + handling method that gives one access to the above extra objects, and + how to declare multiple exception handling methods on the same + IThrowsAdvice implementation class (see ). + + + + Introductions (mixins) + + In a nutshell, introductions are all about adding new state and + behaviour to arbitrary objects... transparently and at runtime. + Introductions (also called mixins) allow one to emulate multiple + inheritance, typically with an eye towards applying crosscutting state + and operations to a wide swathe of objects in your application that + don't share the same inheritance hierarchy. + + + + Layering advice + + The examples shown so far have all demonstrated the application + of a single advice instance to an advised object. Spring.NET's flavor + of AOP would be pretty poor if one could only apply a single advice + instance per advised object... it is perfectly valid to apply multiple + advice to an advised object. For example, one might apply + transactional advice to a service object, and also apply a security + access checking advice to that same advised service object. + + In the interests of keeping this section lean and tight, let's + simply apply all of the advice types that have + been previously described to a single advised object... in this first + instance we'll just use the default pointcut which means that every + possible joinpoint will be advised, and you'll be able to see that the + various advice instances are applied in order. + + Please do consult the class definitions for the following + previously defined advice types to see exactly what each advice type + implementation does... we're going to be using single instances of the + ConsoleLoggingAroundAdvice, + ConsoleLoggingBeforeAdvice, + ConsoleLoggingAfterAdvice, and + ConsoleLoggingThrowsAdvice advice to advise a + single instance of the ServiceCommand class. + + You can find the following listing and executable application in + the AopQuickStart solution in the project + Spring.AopQuickStart.Step1. + + ProxyFactory factory = new ProxyFactory(new ServiceCommand()); + factory.AddAdvice(new ConsoleLoggingBeforeAdvice()); + factory.AddAdvice(new ConsoleLoggingAfterAdvice()); + factory.AddAdvice(new ConsoleLoggingThrowsAdvice()); + factory.AddAdvice(new ConsoleLoggingAroundAdvice()); + ICommand command = (ICommand) factory.GetProxy(); + command.Execute(); + + Here is the Spring.NET XML configuration for declaratively + applying multiple advice. + + You can find the following listing and executable application in + the AopQuickStart solution in the project + Spring.AopQuickStart.Step2. + + <object id="throwsAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/> + <object id="afterAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/> + <object id="beforeAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/> + <object id="aroundAdvice" + type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> + + <object id="myServiceObject" + type="Spring.Aop.Framework.ProxyFactoryObject"> + <property name="target"> + <object id="myServiceObjectTarget" + type="Spring.Examples.AopQuickStart.ServiceCommand"/> + </property> + <property name="interceptorNames"> + <list> + <value>throwsAdvice</value> + <value>afterAdvice</value> + <value>beforeAdvice</value> + <value>aroundAdvice</value> + </list> + </property> + </object> + + + + Configuring advice + + In case it is not immediately apparent, remember that advice is + just a plain old .NET object (a PONO); advice can have constructors + that can take any number of parameters, and like any other .NET class, + advice can have properties. What this means is that one can leverage + the power of the Spring.NET IoC container to apply the IoC principle + to one's advice, and in so doing reap all the benefits of Dependency + Injection. + + Consider the case of throws advice that needs to report (fatal) + exceptions to a first line support centre. The throws advice could + declare a dependency on a reporting service via a .NET property, and + the Spring.NET container could dependency inject the reporting service + dependency into the throws advice when it is being created; the + reporting dependency might be a simple Log4NET wrapper, or a Windows + EventLog wrapper, or a custom reporting exception reporting service + that sends detailed emails concerning the fatal exception. + + Also bear in mind the fact that Spring.NET's AOP implementation + is quite independent of Spring.NET's IoC container. As you have seen, + the various examples used in this have illustrated both programmatic + and declarative AOP configuration (the latter being illustrated via + Spring.NET's IoC XML configuration mechanism). + + + + + Using Attributes to define Pointcuts + + + + + + + The Spring.NET AOP Cookbook + + The preceding treatment of Spring.NET AOP has (quite intentionally) + been decidedly simple. The overarching aim was to convey the concepts of + Spring.NET AOP... this section of the Spring.NET AOP guide contains a + number of real world examples of the application of Spring.NET AOP. + + + Caching + + This example illustrates one of the more common usages of AOP... + caching. + + Lets consider the scenario where we have some static reference + data that needs to be kept around for the duration of an application. + The data will almost never change over the uptime of an application, and + it exists only in the database to satisfy referential integrity amongst + the various relations in the database schema. An example of such static + (and typically immutable) reference data would be a collection of + Country objects (comprising a country name and a + code). What we would like to do is suck in the collection of + Country objects and then pin them in a cache. This + saves us having to hit the back end database again and again every time + we need to reference a country in our application (for example, to + populate dropdown controls in a Windows Forms desktop + application). + + The Data Access Object (DAO) that will load the collection of + Country objects is called + AdoCountryDao (it is an implementation of the + data-access-technology agnostic DAO interface called + ICountryDao). The implementation of the + AdoCountryDao is quite simple, in that every time the + FindAllCountries instance method is called, an + instance will query the database for an IDataReader + and hydrate zero or more Country objects using the + returned data. + + public class AdoCountryDao : ICountryDao + { + public IList FindAllCountries () + { + // implementation elided for clarity... + return countries; + } + } + + Ideally, what we would like to have happen is for the results of + the first call to the + FindAllCountries instance method to be cached. We + would also like to do this in a non-invasive way, because caching is + something that we might want to apply at any number of points across the + codebase of our application. So, to address what we have identified as a + cross cutting concern, we can use Spring.NET AOP to + implement the caching. + + The mechanism that this example is going to use to identify (or + pick out) areas in our application that we would like to apply caching + to is a .NET Attribute. Spring.NET ships with a + number of useful custom .NET Attribute + implementations, one of which is the cunningly named + CacheAttribute. In the specific case of this example, + we are simply going to decorate the definition of the + FindAllCountries instance method with the + CacheAttribute. + + public class AdoCountryDao : ICountryDao + { + [Cache] + public IList FindAllCountries () + { + // implementation elided for clarity... + return countries; + } + } + + The SpringAir reference application that is packaged as part of + the Spring.NET distribution comes with a working example of caching + applied using Spring.NET AOP (see ). + + + + Performance Monitoring + + This recipe show how easy it is to instrument the classes and + objects in an application for performance monitoring. The performance + monitoring implementation uses one of the (many) Windows performance + counters to display and track the performance data. + + + + Retry Rules + + This final recipe describes a simple (but really quite useful) + aspect... retry logic. Using Spring.NET AOP, it is quite easy to + surround an operation such as a method that opens a connection to a + database with a (configurable) aspect that tries to obtain a database + connection any number of times in the event of a failure. + + + + + Spring.NET AOP Best Practices + + Spring.NET AOP is an 80% AOP solution, in that it only tries to + solve the 80% of those cases where AOP is a good fit in a typical + enterprise application. This final section of the Spring.NET AOP guide + describes where Spring.NET AOP is typically useful (the 80%), as well as + where Spring.NET AOP is not a good fit (the 20%). + + diff --git a/doc/reference/src/data-quickstart.xml b/doc/reference/src/data-quickstart.xml index be95892a..2741a452 100644 --- a/doc/reference/src/data-quickstart.xml +++ b/doc/reference/src/data-quickstart.xml @@ -16,7 +16,13 @@ * limitations under the License. */ --> - + Data Access QuickStart
@@ -72,6 +78,12 @@ namespace, collections of which are generally returned from the DAO methods. + + To follow this Data Access QuickStart load the solution file found + in the directory + <spring-install-dir>\examples\Spring\Spring.DataQuickStart + +
Database configuration @@ -185,4 +197,4 @@ installation.
-
\ No newline at end of file +
diff --git a/doc/reference/src/msmq-quickstart.xml b/doc/reference/src/msmq-quickstart.xml index 60b3a6d1..cf79bfd3 100644 --- a/doc/reference/src/msmq-quickstart.xml +++ b/doc/reference/src/msmq-quickstart.xml @@ -16,7 +16,13 @@ * limitations under the License. */ --> - + MSMQ QuickStart
@@ -27,11 +33,17 @@ follows the same basic approach as in the NMS QuickStart but is adapted as need for use with MSMQ. Please read the introduction in that chapter to get an - overview of the system. + overview of the system. When there is direct overlap in functionality between the MSMQ and NMS quickstart a reference to the appropriate section in the NMS QuickStart documentation is given. + + + To follow this MSMQ QuickStart load the solution file found in the + directory + <spring-install-dir>\examples\Spring\Spring.MsmqQuickStart +
@@ -39,20 +51,18 @@ To communicate between th client and server a pair of queues will be used. Messages sent from the client to the server will use the - transactional queue named - .\Private$\request.txqueue. Messages sent from the - server to the client will use the transactional queue - .\Private$\response.joe.txqueue. The queue for + transactional queue named .\Private$\request.txqueue. + Messages sent from the server to the client will use the transactional + queue .\Private$\response.joe.txqueue. The queue for messages that cannot be processed, so called 'poison messages' will be - sent to the queue .\Private$\dead.queue. You can - create these queues using the computer management administration console. - Private queues are used to simplify the application setup - requirements. + sent to the queue .\Private$\dead.queue. You can create + these queues using the computer management administration console. Private + queues are used to simplify the application setup requirements. Since MSMQ does not natively support the publish-subscribe messaging style as in other messaging systems, Apache MQ, IBM Websphere MQ, TIBCO EMS, the market data information is sent on the same queue as the - responses from the server to the client for trade requests.. + responses from the server to the client for trade requests..
@@ -112,10 +122,10 @@ Messaging Infrastructure The implementations of the gateway interfaces inherit from Spring's - helper class MessageQueueGatewaySupport in order to - get easy access to a MessageQueueTemplate for - sending. The implementation of the IStockService - interface is shown below + helper class MessageQueueGatewaySupport in order to get + easy access to a MessageQueueTemplate for sending. The + implementation of the IStockService interface is shown + below public class MsmqStockServiceGateway : MessageQueueGatewaySupport, IStockService { @@ -145,17 +155,16 @@ } - The Send method is using - MessageQueueTemplate's ConvertAndSend(object obj, - MessagePostProcessorDelegate messagePostProcessorDelegate) - method. The anonymous delegate allows you to modify the message - properties, such as ResponseQueue and AppSpecific after the message has - been converted from an object but before it has been sent. The use of an - anonymous delegate allows makes it very easy to apply any post processing - logic to the converted message. + The Send method is using MessageQueueTemplate's + ConvertAndSend(object obj, MessagePostProcessorDelegate + messagePostProcessorDelegate) method. The anonymous delegate + allows you to modify the message properties, such as ResponseQueue and + AppSpecific after the message has been converted from an object but before + it has been sent. The use of an anonymous delegate allows makes it very + easy to apply any post processing logic to the converted message. - The configuration for MsmqStockServiceGateway - and all its dependencies is shown below, highlighting important dependency + The configuration for MsmqStockServiceGateway and + all its dependencies is shown below, highlighting important dependency links. <object name="stockServiceGateway" type="Spring.MsmqQuickStart.Client.Gateways.MsmqStockServiceGateway, Spring.MsmqQuickStart.Client"> @@ -190,9 +199,9 @@ Since the client also needs to listen to incoming messages on the responseTxQueue, a - TransactionalMessageListenerContainer is - configured. The configuration for the message listener container and all - its dependencies is shown below, highlighting important dependency + TransactionalMessageListenerContainer is configured. + The configuration for the message listener container and all its + dependencies is shown below, highlighting important dependency links. <!-- MSMQ Transaction Manager --> @@ -203,7 +212,8 @@ <property name="MessageQueueObjectName" value="responseTxQueue"/> <property name="PlatformTransactionManager" ref="messageQueueTransactionManager"/> <property name="MessageListener" ref="messageListenerAdapter"/> - <property name="MessageTransactionExceptionHandler" ref="sendToQueueExceptionHandler"/> + <property name="MessageTransactionExceptionHandler" ref="sendToQueueExceptionHandler"/> </object> @@ -226,12 +236,12 @@ A similar configuration is used on the server to configure the class Spring.MsmqQuickStart.Server.Gateways.MarketDataServiceGateway that implements the IMarketDataService - interface and a - TransactionalMessageListenerContainer to process - messages on the requestTxQueue. You can increase the number of processing - thread in the TransactionalMessageListenerContainer - by setting the property MaxConcurrentListeners, the - default value is 1. + interface and a TransactionalMessageListenerContainer + to process messages on the requestTxQueue. You can increase the number of + processing thread in the + TransactionalMessageListenerContainer by setting the + property MaxConcurrentListeners, the default value is + 1.
@@ -247,10 +257,10 @@ - +
-
\ No newline at end of file +
diff --git a/doc/reference/src/nh-quickstart.xml b/doc/reference/src/nh-quickstart.xml index a1113167..761d8a0a 100644 --- a/doc/reference/src/nh-quickstart.xml +++ b/doc/reference/src/nh-quickstart.xml @@ -34,19 +34,17 @@ has a simple service layer that simulated a fullillment process. See the integration tests as well for insight into how it works. The application uses Spring's declarative transaction management features, - HibernateTemplate helper class, and Open Session In View module. The - example will be updated to not use HibernateTemplate and instead use the - standard NHibernate API in a future release. All functionality is still - present when using the standard NHibernate API, as Spring transaction - managment is integrated into NHibernate extension points and exception - translation is provided by AOP advice. See the section titled Implementing - DAOs based on plain Hibernate 1.2/2.0 API" in the hibernate orm section of - the reference docs for more information.To run the application - make the Web application - the project that starts and set Default.aspx as the start page. You will - see a list of customers. If you select 'edit' then you can edit some - customer info and save it by pressing the save button. Note that you will - need to explicitly navigate back to the Default.aspx page and reload it in - order to see the changes. + HibernateTemplate helper class, and Open Session In View module. + The example will be updated to not use HibernateTemplate and instead use the standard NHibernate API in a future release. All functionality is still present when using the standard NHibernate API, as Spring transaction managment is integrated into NHibernate extension points and exception translation is provided by AOP advice. See the section titled Implementing DAOs based on plain Hibernate 1.2/2.0 API" in the hibernate orm section of the reference docs for more information. + To run the application make the Web application the project that + starts and set Default.aspx as the start page. You will see a list of + customers. If you select 'edit' then you can edit some customer info and + save it by pressing the save button. Note that you will need to explicitly + navigate back to the Default.aspx page and reload it in order to see the + changes. + To follow this NHibernate QuickStart load the solution file + found in the directory + <spring-install-dir>\examples\Spring\Spring.Data.NHibernate.Northwind + diff --git a/doc/reference/src/nms-quickstart.xml b/doc/reference/src/nms-quickstart.xml index c8db8f1a..affa0a24 100644 --- a/doc/reference/src/nms-quickstart.xml +++ b/doc/reference/src/nms-quickstart.xml @@ -16,7 +16,13 @@ * limitations under the License. */ --> - + NMS QuickStart
@@ -39,12 +45,15 @@ - + - This example was developed with ActiveMQ 5.1 and the ActiveMQ NMS - library with subversion repository number 685750. + + To follow this NMS QuickStart load the solution file found in the + directory + <spring-install-dir>\examples\Spring\Spring.NmsQuickStart +
@@ -68,7 +77,7 @@ + role="" scale="75"> @@ -104,16 +113,16 @@ The use of interfaces allows for multiple implementations to be created. Implementations that use messaging to communicate will be based - on the Spring's NmsGateway class and will be - discussed later. stub or mock implementations can be used for testing + on the Spring's NmsGateway class and will be discussed + later. stub or mock implementations can be used for testing purposes.
Message Data - The TradeRequest object shown above contains - all the information required to process a stock order. To promote the + The TradeRequest object shown above contains all + the information required to process a stock order. To promote the interoperability of this data across different platforms the TradeRequest class is generated from an XML Schema using Microsoft's Schema Definition Tool (xsd.exe). The schema for trade @@ -169,9 +178,9 @@ } - The schema and the TradeRequest class are - located in the project Spring.NmsQuickStart.Common. - This common project will be shared between the server and client for + The schema and the TradeRequest class are located + in the project Spring.NmsQuickStart.Common. This common + project will be shared between the server and client for convenience. When sending a response back to the client the type @@ -232,8 +241,8 @@
Message Handlers - When the TradeRequest message is received by - the server, it will be handled by the class + When the TradeRequest message is received by the + server, it will be handled by the class Spring.NmsQuickStart.Server.Handlers.StockAppHandler shown below @@ -312,15 +321,14 @@ Spring.NmsQuickStart.Common.Converters.XmlMessageConverter. This converter adds the ability to marshal and unmarshal objects to and from XML strings. It also uses Spring's - SimpleMessageConverter to convert Hashtables, - strings, and byte arrays. In order to pass information about the - serialized type, type information is put in the message properties. The - type information can be either the class name or an integer value - identifying the type. In systems where the client and server are deployed - together and are tightly coupled, sharing the class name is a convenient - shortcut. The alternative is to register a type for a given integer value. - The XML configuration used to configure these objects is shown - below + SimpleMessageConverter to convert Hashtables, strings, + and byte arrays. In order to pass information about the serialized type, + type information is put in the message properties. The type information + can be either the class name or an integer value identifying the type. In + systems where the client and server are deployed together and are tightly + coupled, sharing the class name is a convenient shortcut. The alternative + is to register a type for a given integer value. The XML configuration + used to configure these objects is shown below <object name="XmlMessageConverter" type="Spring.NmsQuickStart.Common.Converters.XmlMessageConverter, Spring.NmsQuickStart.Common"> <property name="TypeMapper" ref="TypeMapper"/> @@ -374,12 +382,11 @@ logic to the converted message. The object definition for the - NmsStockServiceGateway is shown below along with - its dependent object definitions of NmsTemplate and the + NmsStockServiceGateway is shown below along with its + dependent object definitions of NmsTemplate and the ConnectionFactory. - - <object name="StockServiceGateway" type="Spring.NmsQuickStart.Client.Gateways.NmsStockServiceGateway, Spring.NmsQuickStart.Client"> + <object name="StockServiceGateway" type="Spring.NmsQuickStart.Client.Gateways.NmsStockServiceGateway, Spring.NmsQuickStart.Client"> <property name="NmsTemplate" ref="NmsTemplate"/> <property name="DefaultReplyToQueue"> <object type="Apache.NMS.ActiveMQ.Commands.ActiveMQQueue, Apache.NMS.ActiveMQ"> @@ -447,10 +454,10 @@ - +
- \ No newline at end of file + diff --git a/doc/reference/src/quartz-quickstart.xml b/doc/reference/src/quartz-quickstart.xml index dc3f0ad1..ae6aa086 100644 --- a/doc/reference/src/quartz-quickstart.xml +++ b/doc/reference/src/quartz-quickstart.xml @@ -16,7 +16,13 @@ * limitations under the License. */ --> - + Quartz QuickStart
@@ -38,16 +44,16 @@ The full details of Quartz are outside the scope of this quickstart but here is 'quick tour for the impatient' of the main classes and interfaces used in Quartz so you can get your sea legs. A Quartz - IJob interface represents the task you would like - to execute. You either directly implement Quartz's - IJob interface or a convenience base class. The - Quartz Trigger controls when a job is executed, for - example in the wee hours of the morning every weekday . This would be done - using Quartz's CronTrigger implementation. - Instances of your job are created every time the trigger fires. As such, - in order to pass information between different job instances you stash - data away in a hashtable that gets passed to the each Job instance upon - its creation. Quartz's JobDetail class combines the + IJob interface represents the task you would like to + execute. You either directly implement Quartz's IJob + interface or a convenience base class. The Quartz + Trigger controls when a job is executed, for example in + the wee hours of the morning every weekday . This would be done using + Quartz's CronTrigger implementation. Instances of your + job are created every time the trigger fires. As such, in order to pass + information between different job instances you stash data away in a + hashtable that gets passed to the each Job instance upon its creation. + Quartz's JobDetail class combines the IJob and this hashtable of data. Instead of the standard System.Collections.Hashtable the class JobDataMap is used. Triggers are registered with a @@ -55,6 +61,12 @@ overall execution of the triggers and jobs. The StdSchedulerFactory implementation is generally used. + + + To follow this Quarts QuickStart load the solution file found in + the directory + <spring-install-dir>\examples\Spring\Spring.Scheduling.Quartz.Example +
@@ -72,11 +84,11 @@
Standard job scheduling - The Spring base class QuartzJobObject - implements IJob and allows for your object's - properties to be set via values that are stored inside Quartz's - JobDataMap that is passed along each time your job - is instantiated due a trigger firing. This class is shown below + The Spring base class QuartzJobObject implements + IJob and allows for your object's properties to be set + via values that are stored inside Quartz's JobDataMap + that is passed along each time your job is instantiated due a trigger + firing. This class is shown below public class ExampleJob : QuartzJobObject { @@ -98,12 +110,12 @@ The method ExecuteInternal is called when the trigger fires and is where you would put your business logic. The - JobExecutionContext passed in lets you access - various pieces of information about the current job execution, such as the + JobExecutionContext passed in lets you access various + pieces of information about the current job execution, such as the JobDataMap or information on when the next time the trigger will fire. The ExampleJob is configured by creating a - JobDetail object as shown below in the following - XML snippet taken from spring-objects.xml + JobDetail object as shown below in the following XML + snippet taken from spring-objects.xml <object name="exampleJob" type="Spring.Scheduling.Quartz.JobDetailObject, Spring.Scheduling.Quartz"> <property name="JobType" value="Spring.Scheduling.Quartz.Example.ExampleJob, Spring.Scheduling.Quartz.Example" /> @@ -115,8 +127,7 @@ </property> </object> - The dictionary property of the - JobDetailObject, + The dictionary property of the JobDetailObject, JobDataAsMap, is used to set the values of the ExampleJob's properties. This will result in the ExampleJob being instantiated with it's UserName property value set to 'Alexandre' the @@ -174,8 +185,8 @@ } Note that it does not inherit from any base class. To instruct - Spring to create a JobDetail object for this method - we use Spring's factory object class + Spring to create a JobDetail object for this method we + use Spring's factory object class MethodInvokingJobDetailFactoryObject as shown below @@ -191,10 +202,10 @@ </object> - Note that AdminService object is configured - using Spring as you would do normally, without consideration for Quartz. - The trigger associated with the jobDetail object is listed below. Also - note that when using MethodInvokingJobDetailFactoryObject you can't use + Note that AdminService object is configured using + Spring as you would do normally, without consideration for Quartz. The + trigger associated with the jobDetail object is listed below. Also note + that when using MethodInvokingJobDetailFactoryObject you can't use database persistence for Jobs. See the class documentation for additional details. @@ -245,4 +256,4 @@ 8/8/2008 1:41:03 PM: DoAdminWork called, user name: Gabriel
- \ No newline at end of file + diff --git a/doc/reference/src/remoting-quickstart.xml b/doc/reference/src/remoting-quickstart.xml index 23931b61..0176b61f 100644 --- a/doc/reference/src/remoting-quickstart.xml +++ b/doc/reference/src/remoting-quickstart.xml @@ -1,834 +1,846 @@ - - - - Portable Service Abstraction Quick Start - - - Introduction - - This quickstart demonstrates the basic usage of Spring.NET's - portable service abstraction functionality. Sections 2-5 demonstrate the - use of .NET Remoting, Section 6 shows the use of the - ServicedComponentExporter for .NET Enterprise Services, and Section 7 - shows the use of the WebServiceExporter. - - - - .NET Remoting Example - - The infrastructure classes are located in the - Spring.Services assembly under the - Spring.Services.Remoting namespace. The overall - strategy is to export .NET objects on the server side as either CAO or SAO - objects using CaoExporter or - SaoExporter and obtain references to these objects - on the client side using CaoFactoryObject and - SaoFactoryObject. This quickstart does assume - familiarity with .NET Remoting on the part of the reader. If you are new - to .NET remoting you may find the links to introductory remoting material - presented at the conclusion of this quickstart of some help. - - As usual with quick start examples in Spring.NET, the classes used - in the quickstart are intentionally simple. In the specific case of this - remoting quickstart we are going to make a simple calculator that can be - accessed remotely. The same calculator class will be exported in multiple - ways reflecting the variety of .NET remoting options available (CAO, - SAO-SingleCall, SAO-Singleton) and also the use of adding AOP advice to - SAO hosted objects. - - The example solution is located in the - examples\Spring\Spring.Calculator directory and - contains multiple projects. - - - - - - - - The Spring.Calculator.Contract project contains - the interface ICalculator that defines the basic - operations of a calculator and another interface - IAdvancedCalculator that adds support for memory - storage for results. (woo hoo - big feature - HP-12C beware!) These - interfaces are shown below. The - Spring.Calculator.Services project contains an - implementation of the these interfaces, namely the classes - Calculator and - AdvancedCalculator. The purpose of the - AdvancedCalculator implementation is to demonstrate - the configuration of object state for SAO-singleton objects. Note that the - calculator implementations do not inherit from the - MarshalByRefObject class. The - Spring.Calculator.ClientApp project contains the client - application and the Spring.Calculator.RemoteApp project - contains a console application that will host a Remoted instance of the - AdvancedCalculator class. The - Spring.Aspects project contains some logging advice - that will be used to demonstrate the application of aspects to remoted - objects. Spring.Calculator.RegisterComponentServices is - related to enterprise service exporters and is not relevant for this - quickstart. Spring.Calculator.Web is related to web - services exporters and is not relevant for this quickstart. - - public interface ICalculator -{ - int Add(int n1, int n2); - - int Subtract(int n1, int n2); - - DivisionResult Divide(int n1, int n2); - - int Multiply(int n1, int n2); -} - -[Serializable] -public class DivisionResult -{ - private int _quotient = 0; - private int _rest = 0; - - public int Quotient - { - get { return _quotient; } - set { _quotient = value; } - } - - public int Rest - { - get { return _rest; } - set { _rest = value; } - } -} - - An extension of this interface that supports having a slot for - calculator memory is shown below - - public interface IAdvancedCalculator : ICalculator -{ - int GetMemory(); - - void SetMemory(int memoryValue); - - void MemoryClear(); - - void MemoryAdd(int num); -} - - The structure of the VS.NET solution is a consequence of following - the best practice of using interfaces to share type information between a - .NET remoting client and server. The benefits of this approach are that - the client does not need a reference to the assembly that contains the - implementation class. Having the client reference the implementation - assembly is undesirable for a variety of reasons. One reason being - security since an untrusted client could potentially obtain the source - code to the implementation since Intermediate Language (IL) code is easily - reverse engineered. Another, more compelling, reason is to provide a - greater decoupling between the client and server so the server can update - its implementation of the interface in a manner that is quite transparent - to the client; i.e. the client code need not change. Independent of .NET - remoting best practices, using an interface to provide a service contract - is just good object-oriented design. This lets the client choose another - implementation unrelated to .NET Remoting, for example a local, test-stub - or a web services implementation. One of the major benefits of using - Spring.NET is that it reduces the cost of doing 'interface based - programming' to almost nothing. As such, this best practice approach to - .NET remoting fits naturally into the general approach to application - development that Spring.NET encourages you to follow. Ok, with that - barrage of OO design ranting finished, on to the implementation! - - - - Implementation - - The implementation of the calculators contained in the - Spring.Calculator.Servies project is quite - straightforward. The only interesting methods are those that deal with the - memory storage, which is the state that we will be configuring explicitly - using constructor injection. A subset of the implementation is shown - below. - - public class Calculator : ICalculator -{ - - public int Add(int n1, int n2) - { - return n1 + n2; - } - - public int Substract(int n1, int n2) - { - return n1 - n2; - } - - public DivisionResult Divide(int n1, int n2) - { - DivisionResult result = new DivisionResult(); - result.Quotient = n1 / n2; - result.Rest = n1 % n2; - return result; - } - - public int Multiply(int n1, int n2) - { - return n1 * n2; - } - -} - -public class AdvancedCalculator : Calculator, IAdvancedCalculator -{ - - private int memoryStore = 0; - - public AdvancedCalculator() - {} - - public AdvancedCalculator(int initialMemory) - { - memoryStore = initialMemory; - } - - public int GetMemory() - { - return memoryStore; - } - - // other methods omitted in this listing... - -} - - The Spring.Calculator.RemotedApp project - hosts remoted objects inside a console application. The code is also quite - simple and shown below - - public static void Main(string[] args) -{ - try - { - // initialization of Spring.NET's IoC container - IApplicationContext ctx = ContextRegistry.GetContext(); - - Console.Out.WriteLine("Server listening..."); - } - catch (Exception e) - { - Console.Out.WriteLine(e); - } - finally - { - Console.Out.WriteLine("--- Press <return> to quit ---"); - Console.ReadLine(); - } -} - - The configuration of the .NET remoting channels is done using the - standard system.runtime.remoting configuration section - inside the .NET configuration file of the application - (App.config). In this case we are using the - tcp channel on port 8005. - - <system.runtime.remoting> - <application> - <channels> - <channel ref="tcp" port="8005" /> - </channels> - </application> -</system.runtime.remoting> - - The objects created in Spring's application context are shown below. - Multiple resource files are used to export these objects under various - remoting configurations. The AOP advice used in this example is a simple - Log4Net based around advice. - - <configSections> - <sectionGroup name="spring"> - <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" /> - <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> - <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" /> - </sectionGroup> - <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" /> - </configSections> - -<spring> - <parsers> - <parser type="Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services" /> - </parsers> - <context> - <resource uri="config://spring/objects" /> - <resource uri="assembly://RemoteServer/RemoteServer.Config/cao.xml" /> - <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall.xml" /> - <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall-aop.xml" /> - <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton.xml" /> - <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton-aop.xml" /> - </context> - <objects xmlns="http://www.springframework.net"> - <description>Definitions of objects to be exported.</description> - - <object type="Spring.Remoting.RemotingConfigurer, Spring.Services"> - <property name="Filename" value="Spring.Calculator.RemoteApp.exe.config" /> - </object> - - <object id="Log4NetLoggingAroundAdvice" type="Spring.Aspects.Logging.Log4NetLoggingAroundAdvice, Spring.Aspects"> - <property name="Level" value="Debug" /> - </object> - - <object id="singletonCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"> - <constructor-arg type="int" value="217"/> - </object> - - <object id="singletonCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> - <property name="target" ref="singletonCalculator"/> - <property name="interceptorNames"> - <list> - <value>Log4NetLoggingAroundAdvice</value> - </list> - </property> - </object> - - <object id="prototypeCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" singleton="false"> - <constructor-arg type="int" value="217"/> - </object> - - <object id="prototypeCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> - <property name="targetSource"> - <object type="Spring.Aop.Target.PrototypeTargetSource, Spring.Aop"> - <property name="TargetObjectName" value="prototypeCalculator"/> - </object> - </property> - <property name="interceptorNames"> - <list> - <value>Log4NetLoggingAroundAdvice</value> - </list> - </property> - </object> - - </objects> -</spring> - - The declaration of the calculator instance, - singletonCalculator for example, and the setting of any - property values and / or object references is done as you would normally - do for any object declared in the Spring.NET configuration file. To expose - the calculator objects as .NET remoted objects the exporter - Spring.Remoting.CaoExporter is used for CAO objects - and Spring.Remoting.SaoExporter is used for SAO - objects. Both exporters require the setting of a - TargetName property that refers to the name of the - object in Spring's IoC container that will be remoted. The semantics of - SAO-SingleCall and CAO behavior are achieved by exporting a target object - that is declared as a "prototype" (i.e. singleton=false). For SAO objects, - the ServiceName property defines the name of the - service as it will appear in the URL that clients use to locate the remote - object. To set the remoting lifetime of the objects to be infinite, the - property Infinite is set to true. - - The configuration for the exporting a SAO-Singleton is shown - below.<objects - xmlns="http://www.springframework.net" - xmlns:r="http://www.springframework.net/remoting"> - - <description>Registers the calculator service as a SAO in 'Singleton' mode.</description> - - <r:saoExporter - targetName="singletonCalculator" - serviceName="RemotedSaoSingletonCalculator" /> -</objects>The configuration shown above uses the Spring - Remoting schema but you can also choose to use the standard 'generic' XML - configuration shown below.<object name="saoSingletonCalculator" type="Spring.Remoting.SaoExporter, Spring.Services"> - <property name="TargetName" value="singletonCalculator" /> - <property name="ServiceName" value="RemotedSaoSingletonCalculator" /> -</object> This will result in the remote object being - identified by the URL - tcp://localhost:8005/RemotedSaoSingletonCalculator. The - use of SaoExporter and - CaoExporter for other configuration are similar, - look at the configuration files in the - Spring.Calculator.RemotedApp project files for more - information. - - On the client side, the client application will connect a specific - type of remote calculator service, object, ask it for it's current memory - value, which is pre-configured to 217, then perform a - simple addition. As in the case of the server, the channel configuration - is done using the standard .NET Remoting configuration section of the .NET - application configuration file (App.config), as can - been seen below. - - <system.runtime.remoting> - <application> - <channels> - <channel ref="tcp"/> - </channels> - </application> -</system.runtime.remoting> - - The client implementation code is shown below. - - public static void Main(string[] args) -{ - try - { - Pause(); - - IApplicationContext ctx = ContextRegistry.GetContext(); - - Console.Out.WriteLine("Get Calculator..."); - IAdvancedCalculator firstCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService"); - Console.WriteLine("Divide(11, 2) : " + firstCalc.Divide(11, 2)); - Console.Out.WriteLine("Memory = " + firstCalc.GetMemory()); - firstCalc.MemoryAdd(2); - Console.Out.WriteLine("Memory + 2 = " + firstCalc.GetMemory()); - - Console.Out.WriteLine("Get Calculator..."); - IAdvancedCalculator secondCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService"); - Console.Out.WriteLine("Memory = " + secondCalc.GetMemory()); - } - catch (Exception e) - { - Console.Out.WriteLine(e); - } - finally - { - Pause(); - } -} - - Note that the client application code is not aware that it is using - a remote object. The Pause() method simply waits until - the Return key is pressed on the console so that the - client doesn't make a request to the server before the server has had a - chance to start. The standard configuration and initialization of the .NET - remoting infrastructure is done before the creation of the Spring.NET IoC - container. The configuration of the client application is constructed in - such a way that one can easily switch implementations of the - calculatorService retrieved from the application - context. In more complex applications the calculator service would be a - dependency on another object in your application, say in a workflow - processing layer. The following listing shows a configuration for use of a - local implementation and then several remote implementations. The same - Exporter approach can be used to create Web Services and Serviced - Components (Enterprise Services) of the calculator object but are not - discussed in this QuickStart. - - -<spring> - <context> - <resource uri="config://spring/objects" /> - - <!-- Only one at a time ! --> - - <!-- ================================== --> - <!-- In process (local) implementations --> - <!-- ================================== --> - <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.InProcess/inProcess.xml" /> - - <!-- ======================== --> - <!-- Remoting implementations --> - <!-- ======================== --> - <!-- Make sure 'RemoteApp' console application is running and listening. --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao.xml" /> --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao-ctor.xml" /> --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton.xml" /> --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton-aop.xml" /> --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall.xml" /> --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall-aop.xml" /> --> - - <!-- =========================== --> - <!-- Web Service implementations --> - <!-- =========================== --> - <!-- Make sure 'http://localhost/SpringCalculator/' web application is running --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices.xml" /> --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices-aop.xml" /> --> - - <!-- ================================= --> - <!-- EnterpriseService implementations --> - <!-- ================================= --> - <!-- Make sure you register components with 'RegisterComponentServices' console application. --> - <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.EnterpriseServices/enterpriseServices.xml" /> --> - </context> -</spring> - - - The inProcess.xml configuration file creates an instance of - AdvancedCalculator directly -<objects xmlns="http://www.springframework.net"> - - <description>inProcess</description> - - <object id="calculatorService" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" /> - -</objects> - - - Factory classes are used to create a client side reference to the - .NET remoting implementations. For SAO objects use the - SaoFactoryObject class and for CAO objects use the - CaoFactoryObject class. The configuration for - obtaining a reference to the previously exported SAO singleton - implementation is shown below <objects xmlns="http://www.springframework.net"> - - <description>saoSingleton</description> - - <object id="calculatorService" type="Spring.Remoting.SaoFactoryObject, Spring.Services"> - <property name="ServiceInterface" value="Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract" /> - <property name="ServiceUrl" value="tcp://localhost:8005/RemotedSaoSingletonCalculator" /> - </object> - -</objects> - - - You must specify the property ServiceInterface as - well as the location of the remote object via the - ServiceUrl property. The property replacement - facilities of Spring.NET can be leveraged here to make it easy to - configure the URL value based on environment variable settings, a standard - .NET configuration section, or an external property file. This is useful - to easily switch between test, QA, and production (yea baby!) - environments. An example of how this would be expressed is... - - <property name="ServiceUrl" value="${protocol}://${host}:${port}/RemotedSaoSingletonCalculator" /> - - The property values in this example are defined elsewhere; refer to - for additional - information. As mentioned previously, more important in terms of - configuration flexibility is the fact that now you can swap out different - implementations (.NET remoting based or otherwise) of this interface by - making a simple change to the configuration file. - - The configuration for obtaining a reference to the previously - exported CAO implementation is shown below <objects xmlns="http://www.springframework.net"> - - <description>cao</description> - - <object id="calculatorService" type="Spring.Remoting.CaoFactoryObject, Spring.Services"> - <property name="RemoteTargetName" value="prototypeCalculator" /> - <property name="ServiceUrl" value="tcp://localhost:8005" /> - </object> - -</objects> - - - - - Running the application - - Now that we have had a walk though of the implementation and - configuration it is finally time to run the application (if you haven't - yet pulled the trigger). Be sure to set up VS.NET to run multiple - applications on startup as shown below. - - - - - - - - Running the solution yields the following output in the server and - client window - - SERVER WINDOW - -Server listening... ---- Press <return> to quit --- - - - CLIENT WINDOW - ---- Press <return> to continue --- (hit return...) -Get Calculator... -Divide(11, 2) : Quotient: '5'; Rest: '1' -Memory = 0 -Memory + 2 = 2 -Get Calculator... -Memory = 2 ---- Press <return> to continue --- - - - - Remoting Schema - - The spring-remoting.xsd file in the doc directory provides a short - syntax to configure Spring.NET remoting features. To install the schema in - the VS.NET environment run the install-schema NAnt script in the doc - directory. Refer to the Chapter on VS.NET integration for more - details. - - The various configuration files in the RemoteServer and Client - projects show the schema in action. Here is a condensed listing of those - definitions which should give you a good feel for how to use the - schema. - - <!-- Calculator definitions --> -<object id="singletonCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"> - <constructor-arg type="int" value="217" /> -</object> - -<object id="prototypeCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" singleton="false"> - <constructor-arg type="int" value="217" /> -</object> - -<!-- CAO object --> -<r:caoExporter targetName="prototypeCalculator" infinite="false"> - <r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m"/> -</r:caoExporter> - -<!-- SAO Single Call --> -<r:saoExporter - targetName="prototypeCalculator" - serviceName="RemotedSaoSingleCallCalculator"/> - -<!-- SAO Singleton --> -<r:saoExporter - targetName="singletonCalculator" - serviceName="RemotedSaoSingletonCalculator" /> - - Note that the singleton nature of the remoted object is based on the - Spring object definition. The "PrototypeCalculator" has its singleton - property set to false to that a new one will be created every time a - method on the remoted object is invoked for the SAO case. - - - - .NET Enterprise Services Example - - The .NET Enterprise Services example is located in the project - Spring.Calculator.RegisterComponentServices.2005.csproj or - Spring.Calculator.RegisterComponentServices.2003.csproj, depending on the - use of .NET 1.1 or 2.0. The example uses the previous AdvancedCalculator - implementation and then imports the embedded configuration file - 'enterpriseServices.xml' from the namespace - Spring.Calculator.RegisterComponentServices.Config. The top level - configuration is shown below - - <spring> - - <context> - <resource uri="config://spring/objects" /> - <resource uri="assembly://Spring.Calculator.RegisterComponentServices/Spring.Calculator.RegisterComponentServices.Config/enterpriseServices.xml" /> - </context> - - <objects xmlns="http://www.springframework.net"> - <description>Definitions of objects to be registered.</description> - - <object id="calculatorService" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" /> - - </objects> - - </spring> - - The exporter that adapts the AdvancedCalculator for use as an - Enterprise Service component is defined first in enterpriseServices.xml. - Second is defined an exporter that will host the exported Enterprise - Services component application by signing the assembly, registering it - with the specified COM+ application name. If application does not exist it - will create it and configure it using values specified for Description, - AccessControl and Roles properties. The configuration file for - enterpriseServices.xml is shown below - - <objects xmlns="http://www.springframework.net"> - - <description>enterpriseService</description> - - <object id="calculatorComponent" type="Spring.EnterpriseServices.ServicedComponentExporter, Spring.Services"> - <property name="TargetName" value="calculatorService" /> - <property name="TypeAttributes"> - <list> - <object type="System.EnterpriseServices.TransactionAttribute, System.EnterpriseServices" /> - </list> - </property> - <property name="MemberAttributes"> - <dictionary> - <entry key="*"> - <list> - <object type="System.EnterpriseServices.AutoCompleteAttribute, System.EnterpriseServices" /> - </list> - </entry> - </dictionary> - </property> - </object> - - <object type="Spring.EnterpriseServices.EnterpriseServicesExporter, Spring.Services"> - <property name="ApplicationName"> - <value>Spring Calculator Application</value> - </property> - <property name="Description"> - <value>Spring Calculator application.</value> - </property> - <property name="AccessControl"> - <object type="System.EnterpriseServices.ApplicationAccessControlAttribute, System.EnterpriseServices"> - <property name="AccessChecksLevel"> - <value>ApplicationComponent</value> - </property> - </object> - </property> - <property name="Roles"> - <list> - <value>Admin : Administrator role</value> - <value>User : User role</value> - <value>Manager : Administrator role</value> - </list> - </property> - <property name="Components"> - <list> - <ref object="calculatorComponent" /> - </list> - </property> - <property name="Assembly"> - <value>Spring.Calculator.EnterpriseServices</value> - </property> - </object> - -</objects> - - - - Web Services Example - - The WebServices example shows how to export the AdvancedCalculator - as a web service that is an AOP proxy of AdvancedCalculator that has - logging advice applied to it. The main configuration file, Web.config, - includes information from three locations as shown below - - <context> - <resource uri="config://spring/objects"/> - <resource uri="~/Config/webServices.xml"/> - <resource uri="~/Config/webServices-aop.xml"/> - </context> - - The config section 'spring/objects' in Web.config contains the - definition for the 'plain' Advanced calculator, as well as the definitions - to create an AOP proxy of an AdvancedCalculator that adds logging advice. - These definitions are shown below <objects xmlns="http://www.springframework.net"> - - <!-- Aspect --> - - <object id="CommonLoggingAroundAdvice" type="Spring.Aspects.Logging.CommonLoggingAroundAdvice, Spring.Aspects"> - <property name="Level" value="Debug"/> - </object> - - <!-- Service --> - - <!-- 'plain object' for AdvancedCalculator --> - <object id="calculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"/> - - <!-- AdvancedCalculator object with AOP logging advice applied. --> - <object id="calculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> - <property name="target" ref="calculator"/> - <property name="interceptorNames"> - <list> - <value>CommonLoggingAroundAdvice</value> - </list> - </property> - </object> - - </objects>The configuration file webService.xml - simply exports the named calculator object - - <object id="calculatorService" type="Spring.Web.Services.WebServiceExporter, Spring.Web"> - <property name="TargetName" value="calculator" /> - <property name="Namespace" value="http://SpringCalculator/WebServices" /> - <property name="Description" value="Spring Calculator Web Services" /> - </object> - - Whereas the webService-aop.xml exports the calculator instance that - has AOP advice applied to it. - - <object id="calculatorServiceWeaved" type="Spring.Web.Services.WebServiceExporter, Spring.Web"> - <property name="TargetName" value="calculatorWeaved" /> - <property name="Namespace" value="http://SpringCalculator/WebServices" /> - <property name="Description" value="Spring Calculator Web Services" /> - </object> - - - Setting the solution to run the web project as the startup, you will - be presented with a screen as shown below - - - - - - Selecting the CalculatorService and - CalculatorServiceWeaved links will bring you to the standard user - interface generated for browsing a web service, as shown below - - - - - - And similarly for the calculator service with AOP applied - - - - - - - - Invoking the Add method for calculatorServiceWeaved shows the - screen - - - - - - - - Invoking add will then show the result '4' in a new browser instance - and the log file log.txt will contain the following entires - - 2007-10-15 17:59:47,375 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : about to invoke method 'Add' -2007-10-15 17:59:47,421 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : returned '4' - - - - Additional Resources - - Some introductory articles on .NET remoting can be found online at - MSDN. Ingo Rammer is also a very good authority on .NET remoting, and the - .NET Remoting FAQ (link below) which is maintained by Ingo is chock full - of useful information. - - - - An - Introduction to Microsoft .NET Remoting Framework - - - - Microsoft - .NET Remoting: A Technical Overview - - - - Advanced - .NET Remoting (authored by Ingo Rammer) - - - - .NET - Remoting FAQ - - - - \ No newline at end of file + + + + Portable Service Abstraction Quick Start + + + Introduction + + This quickstart demonstrates the basic usage of Spring.NET's + portable service abstraction functionality. Sections 2-5 demonstrate the + use of .NET Remoting, Section 6 shows the use of the + ServicedComponentExporter for .NET Enterprise Services, and Section 7 + shows the use of the WebServiceExporter. + + + To follow this Quarts QuickStart load the solution file found in + the directory + <spring-install-dir>\examples\Spring\Spring.Calculator + + + + + .NET Remoting Example + + The infrastructure classes are located in the + Spring.Services assembly under the + Spring.Services.Remoting namespace. The overall + strategy is to export .NET objects on the server side as either CAO or SAO + objects using CaoExporter or + SaoExporter and obtain references to these objects on + the client side using CaoFactoryObject and + SaoFactoryObject. This quickstart does assume + familiarity with .NET Remoting on the part of the reader. If you are new + to .NET remoting you may find the links to introductory remoting material + presented at the conclusion of this quickstart of some help. + + As usual with quick start examples in Spring.NET, the classes used + in the quickstart are intentionally simple. In the specific case of this + remoting quickstart we are going to make a simple calculator that can be + accessed remotely. The same calculator class will be exported in multiple + ways reflecting the variety of .NET remoting options available (CAO, + SAO-SingleCall, SAO-Singleton) and also the use of adding AOP advice to + SAO hosted objects. + + The example solution is located in the + examples\Spring\Spring.Calculator directory and + contains multiple projects. + + + + + + + + The Spring.Calculator.Contract project contains + the interface ICalculator that defines the basic + operations of a calculator and another interface + IAdvancedCalculator that adds support for memory + storage for results. (woo hoo - big feature - HP-12C beware!) These + interfaces are shown below. The + Spring.Calculator.Services project contains an + implementation of the these interfaces, namely the classes + Calculator and AdvancedCalculator. + The purpose of the AdvancedCalculator implementation is + to demonstrate the configuration of object state for SAO-singleton + objects. Note that the calculator implementations do + not inherit from the MarshalByRefObject + class. The Spring.Calculator.ClientApp project contains + the client application and the + Spring.Calculator.RemoteApp project contains a console + application that will host a Remoted instance of the + AdvancedCalculator class. The + Spring.Aspects project contains some logging advice + that will be used to demonstrate the application of aspects to remoted + objects. Spring.Calculator.RegisterComponentServices is + related to enterprise service exporters and is not relevant for this + quickstart. Spring.Calculator.Web is related to web + services exporters and is not relevant for this quickstart. + + public interface ICalculator +{ + int Add(int n1, int n2); + + int Subtract(int n1, int n2); + + DivisionResult Divide(int n1, int n2); + + int Multiply(int n1, int n2); +} + +[Serializable] +public class DivisionResult +{ + private int _quotient = 0; + private int _rest = 0; + + public int Quotient + { + get { return _quotient; } + set { _quotient = value; } + } + + public int Rest + { + get { return _rest; } + set { _rest = value; } + } +} + + An extension of this interface that supports having a slot for + calculator memory is shown below + + public interface IAdvancedCalculator : ICalculator +{ + int GetMemory(); + + void SetMemory(int memoryValue); + + void MemoryClear(); + + void MemoryAdd(int num); +} + + The structure of the VS.NET solution is a consequence of following + the best practice of using interfaces to share type information between a + .NET remoting client and server. The benefits of this approach are that + the client does not need a reference to the assembly that contains the + implementation class. Having the client reference the implementation + assembly is undesirable for a variety of reasons. One reason being + security since an untrusted client could potentially obtain the source + code to the implementation since Intermediate Language (IL) code is easily + reverse engineered. Another, more compelling, reason is to provide a + greater decoupling between the client and server so the server can update + its implementation of the interface in a manner that is quite transparent + to the client; i.e. the client code need not change. Independent of .NET + remoting best practices, using an interface to provide a service contract + is just good object-oriented design. This lets the client choose another + implementation unrelated to .NET Remoting, for example a local, test-stub + or a web services implementation. One of the major benefits of using + Spring.NET is that it reduces the cost of doing 'interface based + programming' to almost nothing. As such, this best practice approach to + .NET remoting fits naturally into the general approach to application + development that Spring.NET encourages you to follow. Ok, with that + barrage of OO design ranting finished, on to the implementation! + + + + Implementation + + The implementation of the calculators contained in the + Spring.Calculator.Servies project is quite + straightforward. The only interesting methods are those that deal with the + memory storage, which is the state that we will be configuring explicitly + using constructor injection. A subset of the implementation is shown + below. + + public class Calculator : ICalculator +{ + + public int Add(int n1, int n2) + { + return n1 + n2; + } + + public int Substract(int n1, int n2) + { + return n1 - n2; + } + + public DivisionResult Divide(int n1, int n2) + { + DivisionResult result = new DivisionResult(); + result.Quotient = n1 / n2; + result.Rest = n1 % n2; + return result; + } + + public int Multiply(int n1, int n2) + { + return n1 * n2; + } + +} + +public class AdvancedCalculator : Calculator, IAdvancedCalculator +{ + + private int memoryStore = 0; + + public AdvancedCalculator() + {} + + public AdvancedCalculator(int initialMemory) + { + memoryStore = initialMemory; + } + + public int GetMemory() + { + return memoryStore; + } + + // other methods omitted in this listing... + +} + + The Spring.Calculator.RemotedApp project hosts + remoted objects inside a console application. The code is also quite + simple and shown below + + public static void Main(string[] args) +{ + try + { + // initialization of Spring.NET's IoC container + IApplicationContext ctx = ContextRegistry.GetContext(); + + Console.Out.WriteLine("Server listening..."); + } + catch (Exception e) + { + Console.Out.WriteLine(e); + } + finally + { + Console.Out.WriteLine("--- Press <return> to quit ---"); + Console.ReadLine(); + } +} + + The configuration of the .NET remoting channels is done using the + standard system.runtime.remoting configuration section + inside the .NET configuration file of the application + (App.config). In this case we are using the + tcp channel on port 8005. + + <system.runtime.remoting> + <application> + <channels> + <channel ref="tcp" port="8005" /> + </channels> + </application> +</system.runtime.remoting> + + The objects created in Spring's application context are shown below. + Multiple resource files are used to export these objects under various + remoting configurations. The AOP advice used in this example is a simple + Log4Net based around advice. + + <configSections> + <sectionGroup name="spring"> + <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" /> + <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> + <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" /> + </sectionGroup> + <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" /> + </configSections> + +<spring> + <parsers> + <parser type="Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services" /> + </parsers> + <context> + <resource uri="config://spring/objects" /> + <resource uri="assembly://RemoteServer/RemoteServer.Config/cao.xml" /> + <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall.xml" /> + <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall-aop.xml" /> + <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton.xml" /> + <resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton-aop.xml" /> + </context> + <objects xmlns="http://www.springframework.net"> + <description>Definitions of objects to be exported.</description> + + <object type="Spring.Remoting.RemotingConfigurer, Spring.Services"> + <property name="Filename" value="Spring.Calculator.RemoteApp.exe.config" /> + </object> + + <object id="Log4NetLoggingAroundAdvice" type="Spring.Aspects.Logging.Log4NetLoggingAroundAdvice, Spring.Aspects"> + <property name="Level" value="Debug" /> + </object> + + <object id="singletonCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"> + <constructor-arg type="int" value="217"/> + </object> + + <object id="singletonCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> + <property name="target" ref="singletonCalculator"/> + <property name="interceptorNames"> + <list> + <value>Log4NetLoggingAroundAdvice</value> + </list> + </property> + </object> + + <object id="prototypeCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" singleton="false"> + <constructor-arg type="int" value="217"/> + </object> + + <object id="prototypeCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> + <property name="targetSource"> + <object type="Spring.Aop.Target.PrototypeTargetSource, Spring.Aop"> + <property name="TargetObjectName" value="prototypeCalculator"/> + </object> + </property> + <property name="interceptorNames"> + <list> + <value>Log4NetLoggingAroundAdvice</value> + </list> + </property> + </object> + + </objects> +</spring> + + The declaration of the calculator instance, + singletonCalculator for example, and the setting of any + property values and / or object references is done as you would normally + do for any object declared in the Spring.NET configuration file. To expose + the calculator objects as .NET remoted objects the exporter + Spring.Remoting.CaoExporter is used for CAO objects and + Spring.Remoting.SaoExporter is used for SAO objects. + Both exporters require the setting of a TargetName + property that refers to the name of the object in Spring's IoC container + that will be remoted. The semantics of SAO-SingleCall and CAO behavior are + achieved by exporting a target object that is declared as a "prototype" + (i.e. singleton=false). For SAO objects, the + ServiceName property defines the name of the service as + it will appear in the URL that clients use to locate the remote object. To + set the remoting lifetime of the objects to be infinite, the property + Infinite is set to true. + + The configuration for the exporting a SAO-Singleton is shown + below.<objects + xmlns="http://www.springframework.net" + xmlns:r="http://www.springframework.net/remoting"> + + <description>Registers the calculator service as a SAO in 'Singleton' mode.</description> + + <r:saoExporter + targetName="singletonCalculator" + serviceName="RemotedSaoSingletonCalculator" /> +</objects>The configuration shown above uses the Spring + Remoting schema but you can also choose to use the standard 'generic' XML + configuration shown below.<object name="saoSingletonCalculator" type="Spring.Remoting.SaoExporter, Spring.Services"> + <property name="TargetName" value="singletonCalculator" /> + <property name="ServiceName" value="RemotedSaoSingletonCalculator" /> +</object> This will result in the remote object being + identified by the URL + tcp://localhost:8005/RemotedSaoSingletonCalculator. The + use of SaoExporter and CaoExporter + for other configuration are similar, look at the configuration files in + the Spring.Calculator.RemotedApp project files for more + information. + + On the client side, the client application will connect a specific + type of remote calculator service, object, ask it for it's current memory + value, which is pre-configured to 217, then perform a + simple addition. As in the case of the server, the channel configuration + is done using the standard .NET Remoting configuration section of the .NET + application configuration file (App.config), as can + been seen below. + + <system.runtime.remoting> + <application> + <channels> + <channel ref="tcp"/> + </channels> + </application> +</system.runtime.remoting> + + The client implementation code is shown below. + + public static void Main(string[] args) +{ + try + { + Pause(); + + IApplicationContext ctx = ContextRegistry.GetContext(); + + Console.Out.WriteLine("Get Calculator..."); + IAdvancedCalculator firstCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService"); + Console.WriteLine("Divide(11, 2) : " + firstCalc.Divide(11, 2)); + Console.Out.WriteLine("Memory = " + firstCalc.GetMemory()); + firstCalc.MemoryAdd(2); + Console.Out.WriteLine("Memory + 2 = " + firstCalc.GetMemory()); + + Console.Out.WriteLine("Get Calculator..."); + IAdvancedCalculator secondCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService"); + Console.Out.WriteLine("Memory = " + secondCalc.GetMemory()); + } + catch (Exception e) + { + Console.Out.WriteLine(e); + } + finally + { + Pause(); + } +} + + Note that the client application code is not aware that it is using + a remote object. The Pause() method simply waits until + the Return key is pressed on the console so that the + client doesn't make a request to the server before the server has had a + chance to start. The standard configuration and initialization of the .NET + remoting infrastructure is done before the creation of the Spring.NET IoC + container. The configuration of the client application is constructed in + such a way that one can easily switch implementations of the + calculatorService retrieved from the application + context. In more complex applications the calculator service would be a + dependency on another object in your application, say in a workflow + processing layer. The following listing shows a configuration for use of a + local implementation and then several remote implementations. The same + Exporter approach can be used to create Web Services and Serviced + Components (Enterprise Services) of the calculator object but are not + discussed in this QuickStart. + + +<spring> + <context> + <resource uri="config://spring/objects" /> + + <!-- Only one at a time ! --> + + <!-- ================================== --> + <!-- In process (local) implementations --> + <!-- ================================== --> + <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.InProcess/inProcess.xml" /> + + <!-- ======================== --> + <!-- Remoting implementations --> + <!-- ======================== --> + <!-- Make sure 'RemoteApp' console application is running and listening. --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao.xml" /> --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao-ctor.xml" /> --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton.xml" /> --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton-aop.xml" /> --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall.xml" /> --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall-aop.xml" /> --> + + <!-- =========================== --> + <!-- Web Service implementations --> + <!-- =========================== --> + <!-- Make sure 'http://localhost/SpringCalculator/' web application is running --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices.xml" /> --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices-aop.xml" /> --> + + <!-- ================================= --> + <!-- EnterpriseService implementations --> + <!-- ================================= --> + <!-- Make sure you register components with 'RegisterComponentServices' console application. --> + <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.EnterpriseServices/enterpriseServices.xml" /> --> + </context> +</spring> + + + The inProcess.xml configuration file creates an instance of + AdvancedCalculator directly +<objects xmlns="http://www.springframework.net"> + + <description>inProcess</description> + + <object id="calculatorService" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" /> + +</objects> + + + Factory classes are used to create a client side reference to the + .NET remoting implementations. For SAO objects use the + SaoFactoryObject class and for CAO objects use the + CaoFactoryObject class. The configuration for obtaining + a reference to the previously exported SAO singleton implementation is + shown below <objects xmlns="http://www.springframework.net"> + + <description>saoSingleton</description> + + <object id="calculatorService" type="Spring.Remoting.SaoFactoryObject, Spring.Services"> + <property name="ServiceInterface" value="Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract" /> + <property name="ServiceUrl" value="tcp://localhost:8005/RemotedSaoSingletonCalculator" /> + </object> + +</objects> + + + You must specify the property ServiceInterface as + well as the location of the remote object via the + ServiceUrl property. The property replacement + facilities of Spring.NET can be leveraged here to make it easy to + configure the URL value based on environment variable settings, a standard + .NET configuration section, or an external property file. This is useful + to easily switch between test, QA, and production (yea baby!) + environments. An example of how this would be expressed is... + + <property name="ServiceUrl" value="${protocol}://${host}:${port}/RemotedSaoSingletonCalculator" /> + + The property values in this example are defined elsewhere; refer to + for additional + information. As mentioned previously, more important in terms of + configuration flexibility is the fact that now you can swap out different + implementations (.NET remoting based or otherwise) of this interface by + making a simple change to the configuration file. + + The configuration for obtaining a reference to the previously + exported CAO implementation is shown below <objects xmlns="http://www.springframework.net"> + + <description>cao</description> + + <object id="calculatorService" type="Spring.Remoting.CaoFactoryObject, Spring.Services"> + <property name="RemoteTargetName" value="prototypeCalculator" /> + <property name="ServiceUrl" value="tcp://localhost:8005" /> + </object> + +</objects> + + + + + Running the application + + Now that we have had a walk though of the implementation and + configuration it is finally time to run the application (if you haven't + yet pulled the trigger). Be sure to set up VS.NET to run multiple + applications on startup as shown below. + + + + + + + + Running the solution yields the following output in the server and + client window + + SERVER WINDOW + +Server listening... +--- Press <return> to quit --- + + + CLIENT WINDOW + +--- Press <return> to continue --- (hit return...) +Get Calculator... +Divide(11, 2) : Quotient: '5'; Rest: '1' +Memory = 0 +Memory + 2 = 2 +Get Calculator... +Memory = 2 +--- Press <return> to continue --- + + + + Remoting Schema + + The spring-remoting.xsd file in the doc directory provides a short + syntax to configure Spring.NET remoting features. To install the schema in + the VS.NET environment run the install-schema NAnt script in the doc + directory. Refer to the Chapter on VS.NET integration for more + details. + + The various configuration files in the RemoteServer and Client + projects show the schema in action. Here is a condensed listing of those + definitions which should give you a good feel for how to use the + schema. + + <!-- Calculator definitions --> +<object id="singletonCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"> + <constructor-arg type="int" value="217" /> +</object> + +<object id="prototypeCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" singleton="false"> + <constructor-arg type="int" value="217" /> +</object> + +<!-- CAO object --> +<r:caoExporter targetName="prototypeCalculator" infinite="false"> + <r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m"/> +</r:caoExporter> + +<!-- SAO Single Call --> +<r:saoExporter + targetName="prototypeCalculator" + serviceName="RemotedSaoSingleCallCalculator"/> + +<!-- SAO Singleton --> +<r:saoExporter + targetName="singletonCalculator" + serviceName="RemotedSaoSingletonCalculator" /> + + Note that the singleton nature of the remoted object is based on the + Spring object definition. The "PrototypeCalculator" has its singleton + property set to false to that a new one will be created every time a + method on the remoted object is invoked for the SAO case. + + + + .NET Enterprise Services Example + + The .NET Enterprise Services example is located in the project + Spring.Calculator.RegisterComponentServices.2005.csproj or + Spring.Calculator.RegisterComponentServices.2003.csproj, depending on the + use of .NET 1.1 or 2.0. The example uses the previous AdvancedCalculator + implementation and then imports the embedded configuration file + 'enterpriseServices.xml' from the namespace + Spring.Calculator.RegisterComponentServices.Config. The top level + configuration is shown below + + <spring> + + <context> + <resource uri="config://spring/objects" /> + <resource uri="assembly://Spring.Calculator.RegisterComponentServices/Spring.Calculator.RegisterComponentServices.Config/enterpriseServices.xml" /> + </context> + + <objects xmlns="http://www.springframework.net"> + <description>Definitions of objects to be registered.</description> + + <object id="calculatorService" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" /> + + </objects> + + </spring> + + The exporter that adapts the AdvancedCalculator for use as an + Enterprise Service component is defined first in enterpriseServices.xml. + Second is defined an exporter that will host the exported Enterprise + Services component application by signing the assembly, registering it + with the specified COM+ application name. If application does not exist it + will create it and configure it using values specified for Description, + AccessControl and Roles properties. The configuration file for + enterpriseServices.xml is shown below + + <objects xmlns="http://www.springframework.net"> + + <description>enterpriseService</description> + + <object id="calculatorComponent" type="Spring.EnterpriseServices.ServicedComponentExporter, Spring.Services"> + <property name="TargetName" value="calculatorService" /> + <property name="TypeAttributes"> + <list> + <object type="System.EnterpriseServices.TransactionAttribute, System.EnterpriseServices" /> + </list> + </property> + <property name="MemberAttributes"> + <dictionary> + <entry key="*"> + <list> + <object type="System.EnterpriseServices.AutoCompleteAttribute, System.EnterpriseServices" /> + </list> + </entry> + </dictionary> + </property> + </object> + + <object type="Spring.EnterpriseServices.EnterpriseServicesExporter, Spring.Services"> + <property name="ApplicationName"> + <value>Spring Calculator Application</value> + </property> + <property name="Description"> + <value>Spring Calculator application.</value> + </property> + <property name="AccessControl"> + <object type="System.EnterpriseServices.ApplicationAccessControlAttribute, System.EnterpriseServices"> + <property name="AccessChecksLevel"> + <value>ApplicationComponent</value> + </property> + </object> + </property> + <property name="Roles"> + <list> + <value>Admin : Administrator role</value> + <value>User : User role</value> + <value>Manager : Administrator role</value> + </list> + </property> + <property name="Components"> + <list> + <ref object="calculatorComponent" /> + </list> + </property> + <property name="Assembly"> + <value>Spring.Calculator.EnterpriseServices</value> + </property> + </object> + +</objects> + + + + Web Services Example + + The WebServices example shows how to export the AdvancedCalculator + as a web service that is an AOP proxy of AdvancedCalculator that has + logging advice applied to it. The main configuration file, Web.config, + includes information from three locations as shown below + + <context> + <resource uri="config://spring/objects"/> + <resource uri="~/Config/webServices.xml"/> + <resource uri="~/Config/webServices-aop.xml"/> + </context> + + The config section 'spring/objects' in Web.config contains the + definition for the 'plain' Advanced calculator, as well as the definitions + to create an AOP proxy of an AdvancedCalculator that adds logging advice. + These definitions are shown below <objects xmlns="http://www.springframework.net"> + + <!-- Aspect --> + + <object id="CommonLoggingAroundAdvice" type="Spring.Aspects.Logging.CommonLoggingAroundAdvice, Spring.Aspects"> + <property name="Level" value="Debug"/> + </object> + + <!-- Service --> + + <!-- 'plain object' for AdvancedCalculator --> + <object id="calculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"/> + + <!-- AdvancedCalculator object with AOP logging advice applied. --> + <object id="calculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> + <property name="target" ref="calculator"/> + <property name="interceptorNames"> + <list> + <value>CommonLoggingAroundAdvice</value> + </list> + </property> + </object> + + </objects>The configuration file webService.xml + simply exports the named calculator object + + <object id="calculatorService" type="Spring.Web.Services.WebServiceExporter, Spring.Web"> + <property name="TargetName" value="calculator" /> + <property name="Namespace" value="http://SpringCalculator/WebServices" /> + <property name="Description" value="Spring Calculator Web Services" /> + </object> + + Whereas the webService-aop.xml exports the calculator instance that + has AOP advice applied to it. + + <object id="calculatorServiceWeaved" type="Spring.Web.Services.WebServiceExporter, Spring.Web"> + <property name="TargetName" value="calculatorWeaved" /> + <property name="Namespace" value="http://SpringCalculator/WebServices" /> + <property name="Description" value="Spring Calculator Web Services" /> + </object> + + + Setting the solution to run the web project as the startup, you will + be presented with a screen as shown below + + + + + + Selecting the CalculatorService and + CalculatorServiceWeaved links will bring you to the standard user + interface generated for browsing a web service, as shown below + + + + + + And similarly for the calculator service with AOP applied + + + + + + + + Invoking the Add method for calculatorServiceWeaved shows the + screen + + + + + + + + Invoking add will then show the result '4' in a new browser instance + and the log file log.txt will contain the following entires + + 2007-10-15 17:59:47,375 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : about to invoke method 'Add' +2007-10-15 17:59:47,421 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : returned '4' + + + + Additional Resources + + Some introductory articles on .NET remoting can be found online at + MSDN. Ingo Rammer is also a very good authority on .NET remoting, and the + .NET Remoting FAQ (link below) which is maintained by Ingo is chock full + of useful information. + + + + An + Introduction to Microsoft .NET Remoting Framework + + + + Microsoft + .NET Remoting: A Technical Overview + + + + Advanced + .NET Remoting (authored by Ingo Rammer) + + + + .NET + Remoting FAQ + + + + diff --git a/doc/reference/src/tx-quickstart.xml b/doc/reference/src/tx-quickstart.xml index a9def177..cf2a0e33 100644 --- a/doc/reference/src/tx-quickstart.xml +++ b/doc/reference/src/tx-quickstart.xml @@ -1,570 +1,580 @@ - - - - Transactions QuickStart - -
- Introduction - - The Transaction Quickstart demonstrates Spring's transaction - management features. The database schema are two simple tables, credit and - debit, which contain an Identifier and an Amount. The quick start shows - the use of declarative transactions using attributes and also the ability - to change the transaction manager (local or distributed) via changes to - only the configuration files - no code changes are required. It also - demonstrates some techniques for unit and integration testing an - application as well as separating Spring's configuration files so that one - is responsible for describing how the core business classes are configured - and others that are responsible for the database environment and - application of AOP. - - This quickstart assumes you have installed a way to run NUnit tests - within your IDE. Some excellent tools that let you do this are TestDriven.NET and ReSharper. -
- -
- Application Overview - - The design of the application is very simple and consists of two - logical layers, a business service layer in the namespace - Spring.TxQuickStart.Services and a DAO layer in the - namespace Spring.TxQuickStart.Dao. As this is just a - toy example the business service layer does nothing more than call two DAO - objects. The business service is to transfer money in a bank account and - is blatantly taken from the book Pro - ADO.NET by Sahil Malik. The transfer service is defined by the - interface IAccountManager with the - implementation AccountManager located in the - namespace Spring.TxQuickStart.Services. The money - is recorded in a credit and debit table in the database. The SQL Server - schema for the tables is located in the file CreditsDebitsSchema.sql. - Transferring the money requires an ACID operation on these two tables. The - credit operation is defined via a - IAccountCreditDao interface and the debit - operation via an IAccountDebitDao - interface. Implementations of these interfaces using - AdoTemplate are in the namespace - Spring.TxQuickStart.Dao.Ado. - -
- Interfaces - - The Manager and DAO interfaces are shown below - - public interface IAccountManager - { - void DoTransfer(float creditAmount, float debitAmount); - } - - - public interface IAccountCreditDao - { - void CreateCredit(float creditAmount); - } - - public interface IAccountDebitDao - { - void DebitAccount(float debitAmount); - } -
-
- -
- Implementation - - The implementation of the Account Credit DAO is shown below - - public class AccountCreditDao : AdoDaoSupport, IAccountCreditDao - { - public void CreateCredit(float creditAmount) - { - AdoTemplate.ExecuteNonQuery(CommandType.Text, - "insert into Credits (CreditAmount) VALUES (@amount)", "amount", DbType.Decimal, 0, - creditAmount); - } - } - - and for the Debit DAO - - public class AccountDebitDao : AdoDaoSupport, IAccountDebitDao - { - public void DebitAccount(float debitAmount) - { - AdoTemplate.ExecuteNonQuery(CommandType.Text, - "insert into dbo.Debits (DebitAmount) VALUES (@amount)", "amount", DbType.Decimal, 0, - debitAmount); - } - } - - Both of these DAO implementations inherit from Spring's - AdoDaoSupport class that provides convenient access - to an AdoTemplate for performing data access - operations. With no other properties that can be configured in these - implementations, the only configuration required is setting of - AdoDaoSupport's DbProvider property representing - the connection to the database. - - The implementation of the service layer interface, - IAccountManager, is shown below. - - public class AccountManager : IAccountManager - { - - private IAccountCreditDao accountCreditDao; - private IAccountDebitDao accountDebitDao; - - private float maxTransferAmount = 1000000; - - public AccountManager(IAccountCreditDao accountCreditDao, IAccountDebitDao accountDebitDao) - { - this.accountCreditDao = accountCreditDao; - this.accountDebitDao = accountDebitDao; - } - - public float MaxTransferAmount - { - get { return maxTransferAmount; } - set { maxTransferAmount = value; } - } - - - [Transaction] - public void DoTransfer(float creditAmount, float debitAmount) - { - accountCreditDao.CreateCredit(creditAmount); - - if (creditAmount > maxTransferAmount || debitAmount > maxTransferAmount) - { - throw new ArithmeticException("see a teller big spender..."); - } - - accountDebitDao.DebitAccount(debitAmount); - } - - } - - The if statement is a poor-mans representation of business logic, - namely that there is a policy that does not allow the use of this service - for amounts larger than $1,000,000. If the credit or debit amount is - larger than 1,000,000 then and exception will be thrown. We can write a - unit test that will test for this business logic and provide stub - implementations of the DAO objects so that our tests are not only - independent of the database but will also execute very quickly. - Notice the Transaction attribute on the - DoTransfer method. This attribute can be read by - Spring and used to create a transactional proxy to AccountManager in - order to perform declarative transaction management. - - - The NUnit unit test for AccountManager is shown below - - public class AccountManagerUnitTests - { - private IAccountManager accountManager; - - [SetUp] - public void Setup() - { - IAccountCreditDao stubCreditDao = new StubAccountCreditDao(); - IAccountDebitDao stubDebitDao = new StubAccountDebitDao(); - accountManager = new AccountManager(stubCreditDao, stubDebitDao); - } - - [Test] - public void TransferBelowMaxAmount() - { - accountManager.DoTransfer(217, 217); - } - - [Test] - [ExpectedException(typeof(ArithmeticException))] - public void TransferAboveMaxAmount() - { - accountManager.DoTransfer(2000000, 200000); - } - } - - Running these tests we exercise both code pathways through the - method DoTransfer. Nothing we have done so far is - Spring specific (aside from the presence of the [Transaction] attribute. - Now that we know the class works in isolation, we can now 'wire' up the - application for use in production by specifying how the service and DAO - layers are related. This configuration file is shown below and can loosely - be referred to as your 'application blueprint'. This configuration file is - named application-config.xml and is an embedded resource inside the 'main' - project, Spring.TxQuickStart. - - <objects xmlns='http://www.springframework.net'> - - <!-- DAO Implementations --> - <object id="accountCreditDao" type="Spring.TxQuickStart.Dao.Ado.AccountCreditDao, Spring.TxQuickStart"> - <property name="DbProvider" ref="CreditDbProvider"/> - </object> - - <object id="accountDebitDao" type="Spring.TxQuickStart.Dao.Ado.AccountDebitDao, Spring.TxQuickStart"> - <property name="DbProvider" ref="DebitDbProvider"/> - </object> - - - <!-- The service that performs multiple data access operations --> - <object id="accountManager" - type="Spring.TxQuickStart.Services.AccountManager, Spring.TxQuickStart"> - <constructor-arg name="accountCreditDao" ref="accountCreditDao"/> - <constructor-arg name="accountDebitDao" ref="accountDebitDao"/> - </object> - -</objects> - - This configuration is selecting the real ADO.NET implementations - that will insert records into the database. We can now write a NUnit - integration test that will test the service and DAO layers. To do this we - add on configuration information specific to our test environment. This - extra configuration information will determine what databases we speak to - and what transaction manager (local or distribute) to use. The code for - this integration style NUnit test is shown below - - [TestFixture] - public class AccountManagerTests - { - private AdoTemplate adoTemplateCredit; - private AdoTemplate adoTemplateDebit; - - private IAccountManager accountManager; - - [SetUp] - public void SetUp() - { - // Configure Spring programmatically - NamespaceParserRegistry.RegisterParser(typeof(DatabaseNamespaceParser)); - NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser)); - NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); - IApplicationContext context = new XmlApplicationContext( - "assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/system-test-local-config.xml" - ); - accountManager = context["accountManager"] as IAccountManager; - CleanDb(context); - } - - [Test] - public void TransferBelowMaxAmount() - { - accountManager.DoTransfer(217, 217); - - int numCreditRecords = (int)adoTemplateCredit.ExecuteScalar(CommandType.Text, "select count(*) from Credits"); - int numDebitRecords = (int)adoTemplateDebit.ExecuteScalar(CommandType.Text, "select count(*) from Debits"); - Assert.AreEqual(1, numCreditRecords); - Assert.AreEqual(1, numDebitRecords); - } - - [Test] - [ExpectedException(typeof(ArithmeticException))] - public void TransferAboveMaxAmount() - { - accountManager.DoTransfer(2000000, 200000); - } - - - private void CleanDb(IApplicationContext context) - { - IDbProvider dbProvider = (IDbProvider)context["DebitDbProvider"]; - adoTemplateDebit = new AdoTemplate(dbProvider); - adoTemplateDebit.ExecuteNonQuery(CommandType.Text, "truncate table Debits"); - - dbProvider = (IDbProvider)context["CreditDbProvider"]; - adoTemplateCredit = new AdoTemplate(dbProvider); - adoTemplateCredit.ExecuteNonQuery(CommandType.Text, "truncate table Credits"); - - } - } - - The essential element is to create an instance of Spring's - application context where the relevant layers of the application are - 'wired' together. The IAccountManager - implementation is retrieved from the IoC container and stored as a field - of the test class. The basic logic of the test is the same as in the unit - test but in addition there is the verification of actions performed in the - database. The set up method puts the database tables into a known state - before running the tests. Other techniques for performing integration - testing that can alleviate the need to do extensive database state - management for integration tests is described in the testing section. -
- -
- Configuration - - The configuration file system-test-local-config.xml shown in the - previous program listing includes application-config.xml and specifies the - database to use and the local (not distributed) transaction manager - AdoPlatformTransactionManager. This configuration file is shown - below - - <objects xmlns="http://www.springframework.net" - xmlns:db="http://www.springframework.net/database" - xmlns:tx="http://www.springframework.net/tx"> - - - <!-- Imports application configuration --> - <import resource="assembly://Spring.TxQuickStart/Spring.TxQuickStart/application-config.xml"/> - - <!-- Imports additional aspects --> - <!-- - <import resource="assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/aspects-config.xml"/> - --> - - - <!-- Database Providers --> - - <db:provider id="DebitDbProvider" - provider="System.Data.SqlClient" - connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa"/> - - <db:provider id="CreditDbProvider" - provider="System.Data.SqlClient" - connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa"/> - - <alias name="DebitDbProvider" alias="CreditDbProvider"/> - - <!-- Transaction Manager if using a single database that contain both credit and debit tables --> - <object id="transactionManager" - type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> - <property name="DbProvider" ref="DebitDbProvider"/> - </object> - - <!-- Transaction aspect --> - - <tx:attribute-driven/> - -</objects> - - Moving from top to bottom in the configuration file, the - 'application-blueprint' configuration file is included. Then the database - type and connection parameters are specified for the two databases. The - names of these providers must match those specific in - application-config.xml. Since the two names point to the same database, an - alias configuration element is used to have them point to the same - dbProvider under different names. The type of transaction manager is then - selected, in this case we are showing the use of local transactions with - AdoPlatformTransactionManager. Running the tests will result in 217 being - entered into the Credits and Debits table of each database. You can fire - up SQL Server Management Studio or equivalent to verify this. - - To switch to a distributed transaction you can refer to the - configuration file system-test-dtc-config.xml, which is shown below - - <objects xmlns='http://www.springframework.net' - xmlns:db="http://www.springframework.net/database" - xmlns:tx="http://www.springframework.net/tx"> - - - <!-- Imports application configuration --> - <import resource="assembly://Spring.TxQuickStart/Spring.TxQuickStart/application-config.xml"/> - - <!-- Imports additional aspects --> - <!-- - <import resource="assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/aspects-config.xml"/> - --> - - <db:provider id="DebitDbProvider" - provider="System.Data.SqlClient" - connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Debits;User ID=springqa; Password=springqa"/> - - - <db:provider id="CreditDbProvider" - provider="System.Data.SqlClient" - connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Credits;User ID=springqa; Password=springqa"/> - - - <!-- Transaction Manager if using two databases, one containing the credit table and the other a debit table --> - - <object id="transactionManager" - type="Spring.Data.Core.TxScopeTransactionManager, Spring.Data"> - </object> - - - <!-- Transaction aspect --> - <tx:attribute-driven/> - -</objects> - - TxScopeTransactionManager uses .NET 2.0 System.Transactions as the - implementation, allowing for distributed transactions between the two - different databases listed. In a larger application the different layers - would typically be broken up into individual configuration files and - imported into the main configuration file. This allows your configuration - to mirror your architecture. - - You can also use the configuration file - system-test-dtc-es-config.xml that will use EnterpriseServices to perform - transaction management. - -
- Rollback Rules - - Using Rollback rules allows you to specify which exceptions will - not cause a rollback and instead only stop execution flow, committing - the work done up to the exception. An alternative implementation of - AccountManager's DoTransfer method (included in the sample code) is - shown below. - - [Transaction(NoRollbackFor = new Type[] { typeof(ArithmeticException) })] - public void DoTransfer(float creditAmount, float debitAmount) - { - accountCreditDao.CreateCredit(creditAmount); - - if (creditAmount > maxTransferAmount || debitAmount > maxTransferAmount) - { - throw new ArithmeticException("see a teller big spender..."); - } - - accountDebitDao.DebitAccount(debitAmount); - } - - All that has changed is the use of the NoRollbackFor property on - the transaction attribute. - - The expected behavior is that the credit table will be updated - even though the exception is thrown. This is due to specifying that - exceptions of the type ArithmethicException should not rollback the - database transaction. Running the test code below verifies that the - exception still propagates out of the method. - - [Test] - public void DeclarativeWithAttributesNoRollbackFor() - { - try - { - accountManager.DoTransfer(2000000, 2000000); - Assert.Fail("Should have thrown Arithmetic Exception"); - } catch (ArithmeticException) { - int numCreditRecords = (int)adoTemplateCredit.ExecuteScalar(CommandType.Text, "select count(*) from Credits"); - int numDebitRecords = (int)adoTemplateDebit.ExecuteScalar(CommandType.Text, "select count(*) from Debits"); - Assert.AreEqual(1, numCreditRecords); - Assert.AreEqual(0, numDebitRecords); - } - } -
-
- -
- Adding additional Aspects - - Transactional advice is just one type of advice that can be applied - to the service layer. You can also configure other pieces of advice to be - executed as part of the general advice chain that is associated with - methods that have the Transaction attribute applied. In this example we - will add logging of thrown exceptions using Spring's - ExceptionHandlerAdvice as well as logging of the service layer method - invocation. No code is required to be changed in order to have this - additional functionality. Instead all you have to do is uncomment the - line - - <import resource="assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/aspects-config.xml"/> - - in either system-test-dtc-config.xml or system-test-local-config.xml - The aspect configuration file is shown below - - <objects xmlns='http://www.springframework.net' - xmlns:aop="http://www.springframework.net/aop"> - - - - <object name="exceptionAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"> - <property name="exceptionHandlers"> - <list> - <value>on exception name ArithmeticException log 'Logging an exception thrown from method ' + #method.Name </value> - </list> - </property> - </object> - - <object name="loggingAdvice" type="Spring.Aspects.Logging.SimpleLoggingAdvice, Spring.Aop"> - <property name="logUniqueIdentifier" value="true"/> - <property name="logExecutionTime" value="true"/> - <property name="logMethodArguments" value="true"/> - <property name="Separator" value=";"/> - - <property name="HideProxyTypeNames" value="true"/> - <property name="UseDynamicLogger" value="true"/> - - <property name="LogLevel" value="Info"/> - </object> - - - <object id="txAttributePointcut" type="Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop"> - <property name="Attribute" value="Spring.Transaction.Interceptor.TransactionAttribute, Spring.Data"/> - </object> - - <aop:config> - - <aop:advisor id="exceptionProcessAdvisor" order="1" - advice-ref="exceptionAdvice" - pointcut-ref="txAttributePointcut"/> - - <aop:advisor id="loggingAdvisor" order="2" - advice-ref="loggingAdvice" - pointcut-ref="txAttributePointcut"/> - - </aop:config> - -</objects> - - The transaction aspect is now additionally configured with an order - value of "10", which will place it after the execution of the exception - aspect, which is configured to use an order value of 1. The behavior for - logging the exception is specified by creating and configuring an instance - of - Spring.Aspects.Exceptions.ExceptionHandlerAdvice. - The location where that behavior is applied, the pointcut, is the - Transaction attribute. The logging of method arguments and execution time - is specified by configuring an instance of - Spring.Aspects.Logging.SimpleLoggingAdvice. - - The AOP configuration section on the bottom is what ties together - the behavior and where it will take place in the program flow. Under the - covers the transaction configuration, <tx:attribute-driven/> creates - similar advice and pointcut definitions. Running the test - TransferBelowMaxAmount will then log the following messages - - INFO - Entering DoTransfer;45b6af04-b736-4efa-a489-45462726ddf2;creditAmount=217; debitAmount=217 -INFO - Exiting DoTransfer;45b6af04-b736-4efa-a489-45462726ddf2;1328.125 ms;return= - - - When the test case of the test TransferAboveMaxAmount is run the - following messages are logged - - INFO - Entering DoTransfer;d94bc81b-a4ff-4ca1-9aaa-f2834f262307;creditAmount=2000000; debitAmount=200000 -INFO - Exception thrown in DoTransferDoTransfer;d94bc81b-a4ff-4ca1-9aaa-f2834f262307;1140.625 -System.ArithmeticException: see a teller big spender... - at Spring.TxQuickStart.Services.AccountManager.DoTransfer(Single creditAmount, Single debitAmount) in L:\projects\Spring.Net\examples\Spring\Spring.TxQuickStart\src\Spring\Spring.TxQuickStart\TxQuickStart\Services\AccountManager.cs:line 36 - at Spring.DynamicReflection.Method_DoTransfer_ec48557f22b149958fd2243413136600.Invoke(Object target, Object[] args) - at Spring.Reflection.Dynamic.SafeMethod.Invoke(Object target, Object[] arguments) in l:\projects\Spring.Net\src\Spring\Spring.Core\Reflection\Dynamic\DynamicMethod.cs:line 108 - at Spring.Aop.Framework.DynamicMethodInvocation.InvokeJoinpoint() in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aop\Framework\DynamicMethodInvocation.cs:line 89 - at Spring.Aop.Framework.AbstractMethodInvocation.Proceed() in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aop\Framework\AbstractMethodInvocation.cs:line 257 - at Spring.Transaction.Interceptor.TransactionInterceptor.Invoke(IMethodInvocation invocation) in l:\projects\Spring.Net\src\Spring\Spring.Data\Transaction\Interceptor\TransactionInterceptor.cs:line 80 - at Spring.Aop.Framework.AbstractMethodInvocation.Proceed() in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aop\Framework\AbstractMethodInvocation.cs:line 282 - at Spring.Aspects.Logging.SimpleLoggingAdvice.InvokeUnderLog(IMethodInvocation invocation, ILog log) in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aspects\Logging\SimpleLoggingAdvice.cs:line 185 -TRACE - Logging an exception thrown from method DoTransfer - - - -
-
\ No newline at end of file + + + + Transactions QuickStart + +
+ Introduction + + The Transaction Quickstart demonstrates Spring's transaction + management features. The database schema are two simple tables, credit and + debit, which contain an Identifier and an Amount. The quick start shows + the use of declarative transactions using attributes and also the ability + to change the transaction manager (local or distributed) via changes to + only the configuration files - no code changes are required. It also + demonstrates some techniques for unit and integration testing an + application as well as separating Spring's configuration files so that one + is responsible for describing how the core business classes are configured + and others that are responsible for the database environment and + application of AOP. + + This quickstart assumes you have installed a way to run NUnit tests + within your IDE. Some excellent tools that let you do this are TestDriven.NET and ReSharper. + + + To follow this Quarts QuickStart load the solution file found in + the directory + <spring-install-dir>\examples\Spring\Spring.TxQuickStart + +
+ +
+ Application Overview + + The design of the application is very simple and consists of two + logical layers, a business service layer in the namespace + Spring.TxQuickStart.Services and a DAO layer in the + namespace Spring.TxQuickStart.Dao. As this is just a + toy example the business service layer does nothing more than call two DAO + objects. The business service is to transfer money in a bank account and + is blatantly taken from the book Pro + ADO.NET by Sahil Malik. The transfer service is defined by the + interface IAccountManager with the implementation + AccountManager located in the namespace + Spring.TxQuickStart.Services. The money is recorded in + a credit and debit table in the database. The SQL Server schema for the + tables is located in the file CreditsDebitsSchema.sql. Transferring the + money requires an ACID operation on these two tables. The credit operation + is defined via a IAccountCreditDao interface and the + debit operation via an IAccountDebitDao interface. + Implementations of these interfaces using AdoTemplate + are in the namespace + Spring.TxQuickStart.Dao.Ado. + +
+ Interfaces + + The Manager and DAO interfaces are shown below + + public interface IAccountManager + { + void DoTransfer(float creditAmount, float debitAmount); + } + + + public interface IAccountCreditDao + { + void CreateCredit(float creditAmount); + } + + public interface IAccountDebitDao + { + void DebitAccount(float debitAmount); + } +
+
+ +
+ Implementation + + The implementation of the Account Credit DAO is shown below + + public class AccountCreditDao : AdoDaoSupport, IAccountCreditDao + { + public void CreateCredit(float creditAmount) + { + AdoTemplate.ExecuteNonQuery(CommandType.Text, + "insert into Credits (CreditAmount) VALUES (@amount)", "amount", DbType.Decimal, 0, + creditAmount); + } + } + + and for the Debit DAO + + public class AccountDebitDao : AdoDaoSupport, IAccountDebitDao + { + public void DebitAccount(float debitAmount) + { + AdoTemplate.ExecuteNonQuery(CommandType.Text, + "insert into dbo.Debits (DebitAmount) VALUES (@amount)", "amount", DbType.Decimal, 0, + debitAmount); + } + } + + Both of these DAO implementations inherit from Spring's + AdoDaoSupport class that provides convenient access to + an AdoTemplate for performing data access operations. + With no other properties that can be configured in these implementations, + the only configuration required is setting of AdoDaoSupport's + DbProvider property representing the connection to the + database. + + The implementation of the service layer interface, + IAccountManager, is shown below. + + public class AccountManager : IAccountManager + { + + private IAccountCreditDao accountCreditDao; + private IAccountDebitDao accountDebitDao; + + private float maxTransferAmount = 1000000; + + public AccountManager(IAccountCreditDao accountCreditDao, IAccountDebitDao accountDebitDao) + { + this.accountCreditDao = accountCreditDao; + this.accountDebitDao = accountDebitDao; + } + + public float MaxTransferAmount + { + get { return maxTransferAmount; } + set { maxTransferAmount = value; } + } + + + [Transaction] + public void DoTransfer(float creditAmount, float debitAmount) + { + accountCreditDao.CreateCredit(creditAmount); + + if (creditAmount > maxTransferAmount || debitAmount > maxTransferAmount) + { + throw new ArithmeticException("see a teller big spender..."); + } + + accountDebitDao.DebitAccount(debitAmount); + } + + } + + The if statement is a poor-mans representation of business logic, + namely that there is a policy that does not allow the use of this service + for amounts larger than $1,000,000. If the credit or debit amount is + larger than 1,000,000 then and exception will be thrown. We can write a + unit test that will test for this business logic and provide stub + implementations of the DAO objects so that our tests are not only + independent of the database but will also execute very quickly. + Notice the Transaction attribute on the + DoTransfer method. This attribute can be read by + Spring and used to create a transactional proxy to AccountManager in + order to perform declarative transaction management. + + + The NUnit unit test for AccountManager is shown below + + public class AccountManagerUnitTests + { + private IAccountManager accountManager; + + [SetUp] + public void Setup() + { + IAccountCreditDao stubCreditDao = new StubAccountCreditDao(); + IAccountDebitDao stubDebitDao = new StubAccountDebitDao(); + accountManager = new AccountManager(stubCreditDao, stubDebitDao); + } + + [Test] + public void TransferBelowMaxAmount() + { + accountManager.DoTransfer(217, 217); + } + + [Test] + [ExpectedException(typeof(ArithmeticException))] + public void TransferAboveMaxAmount() + { + accountManager.DoTransfer(2000000, 200000); + } + } + + Running these tests we exercise both code pathways through the + method DoTransfer. Nothing we have done so far is + Spring specific (aside from the presence of the [Transaction] attribute. + Now that we know the class works in isolation, we can now 'wire' up the + application for use in production by specifying how the service and DAO + layers are related. This configuration file is shown below and can loosely + be referred to as your 'application blueprint'. This configuration file is + named application-config.xml and is an embedded resource inside the 'main' + project, Spring.TxQuickStart. + + <objects xmlns='http://www.springframework.net'> + + <!-- DAO Implementations --> + <object id="accountCreditDao" type="Spring.TxQuickStart.Dao.Ado.AccountCreditDao, Spring.TxQuickStart"> + <property name="DbProvider" ref="CreditDbProvider"/> + </object> + + <object id="accountDebitDao" type="Spring.TxQuickStart.Dao.Ado.AccountDebitDao, Spring.TxQuickStart"> + <property name="DbProvider" ref="DebitDbProvider"/> + </object> + + + <!-- The service that performs multiple data access operations --> + <object id="accountManager" + type="Spring.TxQuickStart.Services.AccountManager, Spring.TxQuickStart"> + <constructor-arg name="accountCreditDao" ref="accountCreditDao"/> + <constructor-arg name="accountDebitDao" ref="accountDebitDao"/> + </object> + +</objects> + + This configuration is selecting the real ADO.NET implementations + that will insert records into the database. We can now write a NUnit + integration test that will test the service and DAO layers. To do this we + add on configuration information specific to our test environment. This + extra configuration information will determine what databases we speak to + and what transaction manager (local or distribute) to use. The code for + this integration style NUnit test is shown below + + [TestFixture] + public class AccountManagerTests + { + private AdoTemplate adoTemplateCredit; + private AdoTemplate adoTemplateDebit; + + private IAccountManager accountManager; + + [SetUp] + public void SetUp() + { + // Configure Spring programmatically + NamespaceParserRegistry.RegisterParser(typeof(DatabaseNamespaceParser)); + NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser)); + NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); + IApplicationContext context = new XmlApplicationContext( + "assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/system-test-local-config.xml" + ); + accountManager = context["accountManager"] as IAccountManager; + CleanDb(context); + } + + [Test] + public void TransferBelowMaxAmount() + { + accountManager.DoTransfer(217, 217); + + int numCreditRecords = (int)adoTemplateCredit.ExecuteScalar(CommandType.Text, "select count(*) from Credits"); + int numDebitRecords = (int)adoTemplateDebit.ExecuteScalar(CommandType.Text, "select count(*) from Debits"); + Assert.AreEqual(1, numCreditRecords); + Assert.AreEqual(1, numDebitRecords); + } + + [Test] + [ExpectedException(typeof(ArithmeticException))] + public void TransferAboveMaxAmount() + { + accountManager.DoTransfer(2000000, 200000); + } + + + private void CleanDb(IApplicationContext context) + { + IDbProvider dbProvider = (IDbProvider)context["DebitDbProvider"]; + adoTemplateDebit = new AdoTemplate(dbProvider); + adoTemplateDebit.ExecuteNonQuery(CommandType.Text, "truncate table Debits"); + + dbProvider = (IDbProvider)context["CreditDbProvider"]; + adoTemplateCredit = new AdoTemplate(dbProvider); + adoTemplateCredit.ExecuteNonQuery(CommandType.Text, "truncate table Credits"); + + } + } + + The essential element is to create an instance of Spring's + application context where the relevant layers of the application are + 'wired' together. The IAccountManager implementation is + retrieved from the IoC container and stored as a field of the test class. + The basic logic of the test is the same as in the unit test but in + addition there is the verification of actions performed in the database. + The set up method puts the database tables into a known state before + running the tests. Other techniques for performing integration testing + that can alleviate the need to do extensive database state management for + integration tests is described in the testing section. +
+ +
+ Configuration + + The configuration file system-test-local-config.xml shown in the + previous program listing includes application-config.xml and specifies the + database to use and the local (not distributed) transaction manager + AdoPlatformTransactionManager. This configuration file is shown + below + + <objects xmlns="http://www.springframework.net" + xmlns:db="http://www.springframework.net/database" + xmlns:tx="http://www.springframework.net/tx"> + + + <!-- Imports application configuration --> + <import resource="assembly://Spring.TxQuickStart/Spring.TxQuickStart/application-config.xml"/> + + <!-- Imports additional aspects --> + <!-- + <import resource="assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/aspects-config.xml"/> + --> + + + <!-- Database Providers --> + + <db:provider id="DebitDbProvider" + provider="System.Data.SqlClient" + connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa"/> + + <db:provider id="CreditDbProvider" + provider="System.Data.SqlClient" + connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa"/> + + <alias name="DebitDbProvider" alias="CreditDbProvider"/> + + <!-- Transaction Manager if using a single database that contain both credit and debit tables --> + <object id="transactionManager" + type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> + <property name="DbProvider" ref="DebitDbProvider"/> + </object> + + <!-- Transaction aspect --> + + <tx:attribute-driven/> + +</objects> + + Moving from top to bottom in the configuration file, the + 'application-blueprint' configuration file is included. Then the database + type and connection parameters are specified for the two databases. The + names of these providers must match those specific in + application-config.xml. Since the two names point to the same database, an + alias configuration element is used to have them point to the same + dbProvider under different names. The type of transaction manager is then + selected, in this case we are showing the use of local transactions with + AdoPlatformTransactionManager. Running the tests will result in 217 being + entered into the Credits and Debits table of each database. You can fire + up SQL Server Management Studio or equivalent to verify this. + + To switch to a distributed transaction you can refer to the + configuration file system-test-dtc-config.xml, which is shown below + + <objects xmlns='http://www.springframework.net' + xmlns:db="http://www.springframework.net/database" + xmlns:tx="http://www.springframework.net/tx"> + + + <!-- Imports application configuration --> + <import resource="assembly://Spring.TxQuickStart/Spring.TxQuickStart/application-config.xml"/> + + <!-- Imports additional aspects --> + <!-- + <import resource="assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/aspects-config.xml"/> + --> + + <db:provider id="DebitDbProvider" + provider="System.Data.SqlClient" + connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Debits;User ID=springqa; Password=springqa"/> + + + <db:provider id="CreditDbProvider" + provider="System.Data.SqlClient" + connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Credits;User ID=springqa; Password=springqa"/> + + + <!-- Transaction Manager if using two databases, one containing the credit table and the other a debit table --> + + <object id="transactionManager" + type="Spring.Data.Core.TxScopeTransactionManager, Spring.Data"> + </object> + + + <!-- Transaction aspect --> + <tx:attribute-driven/> + +</objects> + + TxScopeTransactionManager uses .NET 2.0 System.Transactions as the + implementation, allowing for distributed transactions between the two + different databases listed. In a larger application the different layers + would typically be broken up into individual configuration files and + imported into the main configuration file. This allows your configuration + to mirror your architecture. + + You can also use the configuration file + system-test-dtc-es-config.xml that will use EnterpriseServices to perform + transaction management. + +
+ Rollback Rules + + Using Rollback rules allows you to specify which exceptions will + not cause a rollback and instead only stop execution flow, committing + the work done up to the exception. An alternative implementation of + AccountManager's DoTransfer method (included in the sample code) is + shown below. + + [Transaction(NoRollbackFor = new Type[] { typeof(ArithmeticException) })] + public void DoTransfer(float creditAmount, float debitAmount) + { + accountCreditDao.CreateCredit(creditAmount); + + if (creditAmount > maxTransferAmount || debitAmount > maxTransferAmount) + { + throw new ArithmeticException("see a teller big spender..."); + } + + accountDebitDao.DebitAccount(debitAmount); + } + + All that has changed is the use of the NoRollbackFor property on + the transaction attribute. + + The expected behavior is that the credit table will be updated + even though the exception is thrown. This is due to specifying that + exceptions of the type ArithmethicException should not rollback the + database transaction. Running the test code below verifies that the + exception still propagates out of the method. + + [Test] + public void DeclarativeWithAttributesNoRollbackFor() + { + try + { + accountManager.DoTransfer(2000000, 2000000); + Assert.Fail("Should have thrown Arithmetic Exception"); + } catch (ArithmeticException) { + int numCreditRecords = (int)adoTemplateCredit.ExecuteScalar(CommandType.Text, "select count(*) from Credits"); + int numDebitRecords = (int)adoTemplateDebit.ExecuteScalar(CommandType.Text, "select count(*) from Debits"); + Assert.AreEqual(1, numCreditRecords); + Assert.AreEqual(0, numDebitRecords); + } + } +
+
+ +
+ Adding additional Aspects + + Transactional advice is just one type of advice that can be applied + to the service layer. You can also configure other pieces of advice to be + executed as part of the general advice chain that is associated with + methods that have the Transaction attribute applied. In this example we + will add logging of thrown exceptions using Spring's + ExceptionHandlerAdvice as well as logging of the service layer method + invocation. No code is required to be changed in order to have this + additional functionality. Instead all you have to do is uncomment the + line + + <import resource="assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/aspects-config.xml"/> + + in either system-test-dtc-config.xml or system-test-local-config.xml + The aspect configuration file is shown below + + <objects xmlns='http://www.springframework.net' + xmlns:aop="http://www.springframework.net/aop"> + + + + <object name="exceptionAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"> + <property name="exceptionHandlers"> + <list> + <value>on exception name ArithmeticException log 'Logging an exception thrown from method ' + #method.Name </value> + </list> + </property> + </object> + + <object name="loggingAdvice" type="Spring.Aspects.Logging.SimpleLoggingAdvice, Spring.Aop"> + <property name="logUniqueIdentifier" value="true"/> + <property name="logExecutionTime" value="true"/> + <property name="logMethodArguments" value="true"/> + <property name="Separator" value=";"/> + + <property name="HideProxyTypeNames" value="true"/> + <property name="UseDynamicLogger" value="true"/> + + <property name="LogLevel" value="Info"/> + </object> + + + <object id="txAttributePointcut" type="Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop"> + <property name="Attribute" value="Spring.Transaction.Interceptor.TransactionAttribute, Spring.Data"/> + </object> + + <aop:config> + + <aop:advisor id="exceptionProcessAdvisor" order="1" + advice-ref="exceptionAdvice" + pointcut-ref="txAttributePointcut"/> + + <aop:advisor id="loggingAdvisor" order="2" + advice-ref="loggingAdvice" + pointcut-ref="txAttributePointcut"/> + + </aop:config> + +</objects> + + The transaction aspect is now additionally configured with an order + value of "10", which will place it after the execution of the exception + aspect, which is configured to use an order value of 1. The behavior for + logging the exception is specified by creating and configuring an instance + of Spring.Aspects.Exceptions.ExceptionHandlerAdvice. + The location where that behavior is applied, the pointcut, is the + Transaction attribute. The logging of method arguments and execution time + is specified by configuring an instance of + Spring.Aspects.Logging.SimpleLoggingAdvice. + + The AOP configuration section on the bottom is what ties together + the behavior and where it will take place in the program flow. Under the + covers the transaction configuration, <tx:attribute-driven/> creates + similar advice and pointcut definitions. Running the test + TransferBelowMaxAmount will then log the following messages + + INFO - Entering DoTransfer;45b6af04-b736-4efa-a489-45462726ddf2;creditAmount=217; debitAmount=217 +INFO - Exiting DoTransfer;45b6af04-b736-4efa-a489-45462726ddf2;1328.125 ms;return= + + + When the test case of the test TransferAboveMaxAmount is run the + following messages are logged + + INFO - Entering DoTransfer;d94bc81b-a4ff-4ca1-9aaa-f2834f262307;creditAmount=2000000; debitAmount=200000 +INFO - Exception thrown in DoTransferDoTransfer;d94bc81b-a4ff-4ca1-9aaa-f2834f262307;1140.625 +System.ArithmeticException: see a teller big spender... + at Spring.TxQuickStart.Services.AccountManager.DoTransfer(Single creditAmount, Single debitAmount) in L:\projects\Spring.Net\examples\Spring\Spring.TxQuickStart\src\Spring\Spring.TxQuickStart\TxQuickStart\Services\AccountManager.cs:line 36 + at Spring.DynamicReflection.Method_DoTransfer_ec48557f22b149958fd2243413136600.Invoke(Object target, Object[] args) + at Spring.Reflection.Dynamic.SafeMethod.Invoke(Object target, Object[] arguments) in l:\projects\Spring.Net\src\Spring\Spring.Core\Reflection\Dynamic\DynamicMethod.cs:line 108 + at Spring.Aop.Framework.DynamicMethodInvocation.InvokeJoinpoint() in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aop\Framework\DynamicMethodInvocation.cs:line 89 + at Spring.Aop.Framework.AbstractMethodInvocation.Proceed() in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aop\Framework\AbstractMethodInvocation.cs:line 257 + at Spring.Transaction.Interceptor.TransactionInterceptor.Invoke(IMethodInvocation invocation) in l:\projects\Spring.Net\src\Spring\Spring.Data\Transaction\Interceptor\TransactionInterceptor.cs:line 80 + at Spring.Aop.Framework.AbstractMethodInvocation.Proceed() in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aop\Framework\AbstractMethodInvocation.cs:line 282 + at Spring.Aspects.Logging.SimpleLoggingAdvice.InvokeUnderLog(IMethodInvocation invocation, ILog log) in l:\projects\Spring.Net\src\Spring\Spring.Aop\Aspects\Logging\SimpleLoggingAdvice.cs:line 185 +TRACE - Logging an exception thrown from method DoTransfer + + + +
+
diff --git a/doc/reference/src/wcf-quickstart.xml b/doc/reference/src/wcf-quickstart.xml index 4c1aff99..a25dc769 100644 --- a/doc/reference/src/wcf-quickstart.xml +++ b/doc/reference/src/wcf-quickstart.xml @@ -42,6 +42,12 @@ client application is located in Sprng.WcfQuickStart.ClientApp.2008. To run the solution make sure that all three projects are set to startup. + + + To follow this Quarts QuickStart load the solution file found in + the directory + <spring-install-dir>\examples\Spring\Spring.WcfQuickStart +
diff --git a/doc/reference/src/web-quickstart.xml b/doc/reference/src/web-quickstart.xml index 03fd49b4..2dfd70f8 100644 --- a/doc/reference/src/web-quickstart.xml +++ b/doc/reference/src/web-quickstart.xml @@ -1,30 +1,43 @@ - - - - Web Quickstarts - -
- Introduction - - The Web Quickstart solution provides basic 'Hello World' examples - for using Spring.Web features. You can use this solution as a starting - point and then move on to the SpringAir application that uses a wider - range of Spring.Web features. -
-
\ No newline at end of file + + + + Web Quickstarts + +
+ Introduction + + The Web Quickstart solution provides basic 'Hello World' examples + for using Spring.Web features. You can use this solution as a starting + point and then move on to the SpringAir application that uses a wider + range of Spring.Web features. The documention inside the solution and web + application itself can help guide you through the functionality. + + + To follow this Quarts QuickStart load the solution file found in + the directory + <spring-install-dir>\examples\Spring\Spring.WebQuickStart + +
+