Spring Net Reference
Spring Net Reference
Spring Net Reference
Version 1.3.2
Copyright 2004-2008 Mark Pollack, Rick Evans, Aleksandar Seovic, Bruno Baia, Erich Eichinger, Federico Spinazzi, Rob Harrop, Griffin Caprio, Ruben Bartelink, Choy Rim, Erez Mazor, Stephen Bohlen, The Spring Java Team
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
1. Preface ................................................................................................................................................ 1 2. Introduction ......................................................................................................................................... 2 2.1. Overview .................................................................................................................................... 2 2.2. Background ................................................................................................................................. 2 2.3. Modules ...................................................................................................................................... 3 2.4. Usage Scenarios .......................................................................................................................... 4 2.5. Quickstart applications ................................................................................................................. 5 2.6. Associated Spring.NET Projects ................................................................................................... 6 2.7. License Information ..................................................................................................................... 6 2.8. Support ....................................................................................................................................... 6 3. Background information ....................................................................................................................... 7 3.1. Inversion of Control .................................................................................................................... 7 4. Migrating from 1.1 M2 ........................................................................................................................ 8 4.1. Introduction ................................................................................................................................. 8 4.2. Important Changes ...................................................................................................................... 8 4.2.1. Namespaces .................................................................................................................. 8 4.2.2. Core ............................................................................................................................. 9 4.2.3. Web ............................................................................................................................. 9 4.2.4. Data ............................................................................................................................. 9 4.3. Support for .NET 4 ...................................................................................................................... 9 I. Core Technologies .............................................................................................................................. 10 5. The IoC container ........................................................................................................................ 11 5.1. Introduction ................................................................................................................... 11 5.2. Container overview ........................................................................................................ 11 5.2.1. Configuration metadata ............................................................................. 12 5.2.2. Instantiating a container ............................................................................ 13 5.2.3. Using the container ................................................................................... 17 5.2.4. Object definition overview ........................................................................ 17 5.2.5. Instantiating objects .................................................................................. 19 5.2.6. Object creation of generic types ................................................................. 21 5.3. Dependencies ................................................................................................................ 23 5.3.1. Dependency injection ................................................................................ 23 5.3.2. Dependencies and configuration in detail .................................................... 30 5.3.3. Declarative Event Listener Registration ...................................................... 39 5.3.4. Using depends-on ..................................................................................... 41 5.3.5. Lazily-initialized objects ............................................................................ 41 5.3.6. Autowiring collaborators ........................................................................... 42 5.3.7. Checking for dependencies ........................................................................ 43 5.3.8. Method injection ....................................................................................... 44 5.3.9. Setting a reference using the members of other objects and classes. ............... 47 5.3.10. Provided IFactoryObject implementations ................................................. 51 5.4. Object Scopes ............................................................................................................... 51 5.4.1. The singleton scope .................................................................................. 52 5.4.2. The prototype scope .................................................................................. 52 5.4.3. Singleton objects with prototype-object dependencies .................................. 53 5.4.4. Request, session and web application scopes ............................................... 53 5.5. Type conversion ............................................................................................................ 53 5.5.1. Type Conversion for Enumerations ............................................................ 54 5.5.2. Built-in TypeConverters ............................................................................ 54
ii
The Spring.NET Framework 5.5.3. Custom Type Conversion .......................................................................... 55 5.6. Customizing the nature of an object ................................................................................ 57 5.6.1. Lifecycle interfaces ................................................................................... 57 5.6.2. IApplicationContextAware and IObjectNameAware .................................... 58 5.7. Object definition inheritance .......................................................................................... 59 5.8. Container extension points ............................................................................................. 60 5.8.1. Obtaining an IFactoryObject, not its product ............................................... 61 5.9. Container extension points ............................................................................................. 61 5.9.1. Customizing objects with IObjectPostProcessors ......................................... 61 5.9.2. Customizing configuration metadata with ObjectFactoryPostProcessors ........ 66 5.9.3. Customizing instantiation logic using IFactoryObjects ................................. 71 5.10. The IApplicationContext .............................................................................................. 72 5.10.1. IObjectFactory or IApplicationContext? .................................................... 73 5.11. Configuration of IApplicationContext ........................................................................... 73 5.11.1. Registering custom parsers ...................................................................... 74 5.11.2. Registering custom resource handlers ....................................................... 75 5.11.3. Registering Type Aliases ......................................................................... 76 5.11.4. Registering Type Converters .................................................................... 77 5.12. Added functionality of the IApplicationContext ............................................................. 77 5.12.1. Context Hierarchies ................................................................................. 77 5.12.2. Using IMessageSource ............................................................................ 78 5.12.3. Using resources within Spring.NET .......................................................... 80 5.12.4. Loosely coupled events ........................................................................... 80 5.12.5. Event notification from IApplicationContext ............................................. 81 5.13. Customized behavior in the ApplicationContext ............................................................. 83 5.13.1. The IApplicationContextAware marker interface ....................................... 83 5.13.2. The IObjectPostProcessor ........................................................................ 83 5.13.3. The IObjectFactoryPostProcessor ............................................................. 83 5.13.4. The PropertyPlaceholderConfigurer .......................................................... 83 5.14. Configuration of ApplicationContext without using XML ............................................... 83 5.15. Service Locator access ................................................................................................. 84 5.16. Stereotype attributes ..................................................................................................... 85 6. The IObjectWrapper and Type conversion ..................................................................................... 86 6.1. Introduction ................................................................................................................... 86 6.2. Manipulating objects using the IObjectWrapper ............................................................... 86 6.2.1. Setting and getting basic and nested properties ............................................ 86 6.2.2. Other features worth mentioning ................................................................ 88 6.3. Type conversion ............................................................................................................ 88 6.3.1. Type Conversion for Enumerations ............................................................ 89 6.4. Built-in TypeConverters ................................................................................................. 89 6.4.1. Custom type converters ............................................................................. 90 7. Resources .................................................................................................................................... 91 7.1. Introduction ................................................................................................................... 91 7.2. The IResource interface ................................................................................................. 91 7.3. Built-in IResource implementations ................................................................................ 92 7.3.1. Registering custom IResource implementations ........................................... 92 7.4. The IResourceLoader ..................................................................................................... 93 7.5. The IResourceLoaderAware interface ............................................................................. 93 7.6. Application contexts and IResource paths ....................................................................... 94
iii
The Spring.NET Framework 8. Threading and Concurrency Support ............................................................................................. 95 8.1. Introduction ................................................................................................................... 95 8.2. Thread Local Storage ..................................................................................................... 95 8.3. Synchronization Primitives ............................................................................................. 96 8.3.1. ISync ........................................................................................................ 96 8.3.2. SyncHolder ............................................................................................... 96 8.3.3. Latch ........................................................................................................ 97 8.3.4. Semaphore ................................................................................................ 97 9. Object Pooling ............................................................................................................................. 99 9.1. Introduction ................................................................................................................... 99 9.2. Interfaces and Implementations ...................................................................................... 99 10. Spring.NET miscellanea ............................................................................................................ 100 10.1. Introduction ............................................................................................................... 100 10.2. PathMatcher ............................................................................................................... 100 10.2.1. General rules ......................................................................................... 100 10.2.2. Matching filenames ............................................................................... 100 10.2.3. Matching subdirectories ......................................................................... 101 10.2.4. Case does matter, slashes don't ............................................................... 101 11. Expression Evaluation ............................................................................................................... 103 11.1. Introduction ............................................................................................................... 103 11.2. Evaluating Expressions ............................................................................................... 103 11.3. Language Reference ................................................................................................... 104 11.3.1. Literal expressions ................................................................................. 104 11.3.2. Properties, Arrays, Lists, Dictionaries, Indexers ....................................... 105 11.3.3. Methods ................................................................................................ 106 11.3.4. Operators .............................................................................................. 106 11.3.5. Assignment ........................................................................................... 109 11.3.6. Expression lists ..................................................................................... 109 11.3.7. Types .................................................................................................... 109 11.3.8. Type Registration .................................................................................. 110 11.3.9. Constructors .......................................................................................... 110 11.3.10. Variables ............................................................................................. 110 11.3.11. Ternary Operator (If-Then-Else) ........................................................... 111 11.3.12. List Projection and Selection ................................................................ 111 11.3.13. Collection Processors and Aggregators .................................................. 112 11.3.14. Spring Object References ..................................................................... 115 11.3.15. Lambda Expressions ............................................................................ 116 11.3.16. Delegate Expressions ........................................................................... 117 11.3.17. Null Context ....................................................................................... 117 11.4. Classes used in the examples ...................................................................................... 117 12. Validation Framework .............................................................................................................. 119 12.1. Introduction ............................................................................................................... 119 12.2. Example Usage .......................................................................................................... 119 12.3. Validator Groups ....................................................................................................... 121 12.4. Validators .................................................................................................................. 121 12.4.1. Condition Validator ............................................................................... 121 12.4.2. Required Validator ................................................................................ 122 12.4.3. Regular Expression Validator ................................................................. 122 12.4.4. Generic Validator .................................................................................. 123
iv
The Spring.NET Framework 12.4.5. Conditional Validator Execution ............................................................. 12.5. Validator Actions ....................................................................................................... 12.5.1. Error Message Action ............................................................................ 12.5.2. Exception Action ................................................................................... 12.5.3. Generic Actions .................................................................................... 12.6. Validator References .................................................................................................. 12.7. Progammatic usage .................................................................................................... 12.8. Usage tips within ASP.NET ....................................................................................... 12.8.1. Rendering Validation Errors ................................................................... 12.8.2. How Validate() and Validation Controls play together ............................. 13. Aspect Oriented Programming with Spring.NET ........................................................................ 13.1. Introduction ............................................................................................................... 13.1.1. AOP concepts ....................................................................................... 13.1.2. Spring.NET AOP capabilities ................................................................. 13.1.3. AOP Proxies in Spring.NET .................................................................. 13.2. Pointcut API in Spring.NET ....................................................................................... 13.2.1. Concepts ............................................................................................... 13.2.2. Operations on pointcuts ......................................................................... 13.2.3. Convenience pointcut implementations ................................................... 13.2.4. Custom pointcuts ................................................................................... 13.3. Advice API in Spring.NET ......................................................................................... 13.3.1. Advice Lifecycle ................................................................................... 13.3.2. Advice types ......................................................................................... 13.4. Advisor API in Spring.NET ....................................................................................... 13.5. Using the ProxyFactoryObject to create AOP proxies ................................................... 13.5.1. Basics ................................................................................................... 13.5.2. ProxyFactoryObject Properties ............................................................... 13.5.3. Proxying Interfaces ................................................................................ 13.5.4. Proxying Classes ................................................................................... 13.5.5. Concise proxy definitions ...................................................................... 13.6. Proxying mechanisms ................................................................................................. 13.6.1. InheritanceBasedAopConfigurer ............................................................. 13.7. Creating AOP Proxies Programatically with the ProxyFactory ...................................... 13.8. Manipulating Advised Objects .................................................................................... 13.9. Using the "autoproxy" facility .................................................................................... 13.9.1. Autoproxy object definitions .................................................................. 13.9.2. Using attribute-driven auto-proxying ...................................................... 13.10. Using AOP Namespace ............................................................................................ 13.11. Using TargetSources ................................................................................................ 13.11.1. Hot swappable target sources ............................................................... 13.11.2. Pooling target sources .......................................................................... 13.11.3. Prototype target sources ....................................................................... 13.11.4. ThreadLocal target sources ................................................................... 13.12. Defining new Advice types ....................................................................................... 13.13. Further reading and resources ................................................................................... 14. Aspect Library .......................................................................................................................... 14.1. Introduction ............................................................................................................... 14.2. Caching ..................................................................................................................... 14.3. Exception Handling .................................................................................................... 123 124 124 124 125 125 126 126 127 128 130 130 130 131 132 132 133 133 134 136 137 137 137 142 142 143 143 144 146 146 147 148 148 149 150 150 155 155 156 157 157 158 159 159 159 160 160 160 162
The Spring.NET Framework 14.3.1. Language Reference .............................................................................. 14.4. Logging ..................................................................................................................... 14.5. Retry ......................................................................................................................... 14.5.1. Language Reference .............................................................................. 14.6. Transactions ............................................................................................................... 14.7. Parameter Validation .................................................................................................. 15. Common Logging ..................................................................................................................... 15.1. Introduction ............................................................................................................... 16. Testing ..................................................................................................................................... 16.1. Introduction ............................................................................................................... 16.2. Unit testing ................................................................................................................ 16.3. Integration testing ...................................................................................................... 16.3.1. Context management and caching ........................................................... 16.3.2. Dependency Injection of test fixtures ...................................................... 16.3.3. Transaction management ........................................................................ 16.3.4. Convenience variables ........................................................................... II. Middle Tier Data Access .................................................................................................................. 17. Transaction management ........................................................................................................... 17.1. Introduction ............................................................................................................... 17.2. Motivations ................................................................................................................ 17.3. Key Abstractions ....................................................................................................... 17.4. Resource synchronization with transactions ................................................................. 17.4.1. High-level approach .............................................................................. 17.4.2. Low-level approach ............................................................................... 17.5. Declarative transaction management ............................................................................ 17.5.1. Understanding Spring's declarative transaction implementation ................. 17.5.2. Example of declarative transaction implementation .................................. 17.5.3. Declarative transactions using the transaction namespace ......................... 17.5.4. Transaction attribute settings .................................................................. 17.5.5. Declarative Transactions using AutoProxy .............................................. 17.6. Programmatic transaction management ........................................................................ 17.6.1. Using the TransactionTemplate .............................................................. 17.6.2. Using the PlatformTransactionManager .................................................. 17.7. Choosing between programmatic and declarative transaction management ..................... 17.8. Transaction lifecycle and status information ................................................................ 18. DAO support ............................................................................................................................ 18.1. Introduction ............................................................................................................... 18.2. Consistent exception hierarchy .................................................................................... 18.3. Consistent abstract classes for DAO support ................................................................ 19. DbProvider ............................................................................................................................... 19.1. Introduction ............................................................................................................... 19.2. IDbProvider and DbProviderFactory ........................................................................... 19.3. XML based configuration ........................................................................................... 19.4. Connection String management ................................................................................... 19.5. Additional IDbProvider implementations ..................................................................... 19.5.1. UserCredentialsDbProvider .................................................................... 19.5.2. MultiDelegatingDbProvider ................................................................... 20. Data access using ADO.NET .................................................................................................... 20.1. Introduction ............................................................................................................... 165 165 167 168 168 168 170 170 171 171 171 171 172 172 174 175 176 177 177 177 179 180 181 181 181 182 183 185 190 191 192 192 194 195 195 196 196 196 199 201 201 201 204 205 206 206 207 209 209
vi
The Spring.NET Framework 20.2. Motivations ................................................................................................................ 20.3. Provider Abstraction .................................................................................................. 20.3.1. Creating an instance of IDbProvider ....................................................... 20.4. Namespaces ............................................................................................................... 20.5. Approaches to Data Access ........................................................................................ 20.6. Introduction to AdoTemplate ...................................................................................... 20.6.1. Execute Callback ................................................................................... 20.6.2. Execute Callback in .NET 2.0 ................................................................ 20.6.3. Execute Callback in .NET 1.1 ................................................................ 20.6.4. Quick Guide to AdoTemplate Methods ................................................... 20.6.5. Quick Guide to AdoTemplate Properties ................................................. 20.7. Transaction Management ............................................................................................ 20.8. Exception Translation ................................................................................................. 20.9. Parameter Management .............................................................................................. 20.9.1. IDbParametersBuilder ............................................................................ 20.9.2. IDbParameters ....................................................................................... 20.9.3. Parameter names in SQL text ................................................................. 20.10. Custom IDataReader implementations ....................................................................... 20.11. Basic data access operations ..................................................................................... 20.11.1. ExecuteNonQuery ................................................................................ 20.11.2. ExecuteScalar ...................................................................................... 20.12. Queries and Lightweight Object Mapping .................................................................. 20.12.1. ResultSetExtractor ............................................................................... 20.12.2. RowCallback ....................................................................................... 20.12.3. RowMapper ........................................................................................ 20.12.4. Query for a single object ...................................................................... 20.12.5. Query using a CommandCreator ........................................................... 20.13. DataTable and DataSet ............................................................................................. 20.13.1. DataTables .......................................................................................... 20.13.2. DataSets .............................................................................................. 20.14. TableAdapters and participation in transactional context ............................................. 20.15. Database operations as Objects ................................................................................. 20.15.1. AdoQuery ........................................................................................... 20.15.2. MappingAdoQuery .............................................................................. 20.15.3. AdoNonQuery ..................................................................................... 20.15.4. Stored Procedure ................................................................................. 21. Object Relational Mapping (ORM) data access .......................................................................... 21.1. Introduction ............................................................................................................... 21.2. NHibernate ................................................................................................................ 21.2.1. Resource management ........................................................................... 21.2.2. Transaction Management ....................................................................... 21.2.3. SessionFactory set up in a Spring container ............................................. 21.2.4. Implementing DAOs based on plain Hibernate 1.2/2.x API ....................... 21.2.5. Declarative transaction demarcation ........................................................ 21.2.6. Programmatic transaction demarcation .................................................... 21.2.7. Transaction management strategies ......................................................... 21.2.8. Web Session Management ..................................................................... 21.2.9. Session Scope ....................................................................................... 21.2.10. Integration Testing ............................................................................... 210 211 212 212 212 213 213 213 215 216 218 219 219 219 220 220 221 221 221 222 222 222 222 223 224 225 225 227 228 228 230 231 231 232 232 233 235 235 236 236 237 238 240 242 244 245 245 246 246
vii
The Spring.NET Framework III. The Web ........................................................................................................................................ 248 22. Spring.NET Web Framework .................................................................................................... 249 22.1. Introduction to Spring.NET Web Framework ............................................................... 249 22.2. Comparing Spring.NET and ASP.NET ........................................................................ 250 22.3. Automatic context loading and hierarchical contexts .................................................... 251 22.3.1. Configuration of a web application ......................................................... 251 22.3.2. Context hierarchy .................................................................................. 253 22.4. Dependency injection for ASP.NET pages ................................................................... 254 22.4.1. Injecting dependencies into controls ....................................................... 255 22.4.2. Injecting dependencies into custom HTTP modules ................................. 256 22.4.3. Injecting dependencies into HTTP handlers and handler factories .............. 256 22.4.4. Injecting dependencies in custom ASP.NET providers ............................. 257 22.4.5. Customizing control dependency injection .............................................. 258 22.5. Web object scopes ..................................................................................................... 259 22.6. Support for ASP.NET 1.1 master pages in Spring.Web ................................................. 259 22.6.1. Linking child pages to their master page file ........................................... 261 22.7. Bidirectional data binding and data model management ................................................ 261 22.7.1. Data binding under the hood .................................................................. 265 22.7.2. Using DataBindingPanel ........................................................................ 270 22.7.3. Customizing model persistence .............................................................. 271 22.8. Localization and message sources ............................................................................... 271 22.8.1. Working with localizers ......................................................................... 272 22.8.2. Automatic localization with localizers ("push" localization) ...................... 273 22.8.3. Global message sources ......................................................................... 274 22.8.4. Applying resources manually ("pull" localization) ................................... 275 22.8.5. Localizing images within a web application ............................................ 276 22.8.6. User culture management ....................................................................... 276 22.8.7. Changing cultures .................................................................................. 277 22.9. Result mapping .......................................................................................................... 278 22.9.1. Registering user defined transfer modes .................................................. 280 22.10. Client-side scripting ................................................................................................. 281 22.10.1. Registering scripts within the head HTML section ................................. 281 22.10.2. Adding CSS definitions to the head section ........................................... 282 22.10.3. Well-known directories ........................................................................ 282 22.11. Spring user controls ................................................................................................. 282 22.11.1. Validation controls .............................................................................. 282 22.11.2. Databinding controls ............................................................................ 283 22.11.3. Calendar control .................................................................................. 283 22.11.4. Panel control ....................................................................................... 283 23. ASP.NET AJAX ....................................................................................................................... 284 23.1. Introduction ............................................................................................................... 284 23.2. Web Services ............................................................................................................. 284 23.2.1. Exposing Web Services ......................................................................... 284 23.2.2. Calling Web Services by using JavaScript ............................................... 285 24. Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 2.0 ............................................. 286 24.1. Introduction to Spring.NET ASP.NET MVC Infrastructure ........................................... 286 24.2. Automatic context loading and hierarchical contexts .................................................... 286 24.2.1. Configuration of a ASP.NET MVC Application ...................................... 286 24.2.2. Customizing the Behavior of the ASP.NET MVC Application Class ......... 287
viii
The Spring.NET Framework 24.3. Web object scopes ..................................................................................................... 289 25. Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 3.0 ............................................. 290 25.1. Introduction to Spring.NET ASP.NET MVC Infrastructure ........................................... 290 25.2. Automatic context loading and hierarchical contexts .................................................... 290 25.2.1. Configuration of a ASP.NET MVC Application ...................................... 290 25.2.2. Customizing the Behavior of the ASP.NET MVC Application Class ......... 291 25.3. Web object scopes ..................................................................................................... 292 IV. Services ......................................................................................................................................... 294 26. Introduction to Spring Services ................................................................................................. 295 26.1. Introduction ............................................................................................................... 295 27. .NET Remoting ......................................................................................................................... 297 27.1. Introduction ............................................................................................................... 297 27.2. Publishing SAOs on the Server ................................................................................... 297 27.2.1. SAO Singleton ...................................................................................... 297 27.2.2. SAO SingleCall ..................................................................................... 298 27.2.3. IIS Application Configuration ................................................................ 299 27.3. Accessing a SAO on the Client .................................................................................. 300 27.4. CAO best practices .................................................................................................... 301 27.5. Registering a CAO object on the Server ...................................................................... 301 27.5.1. Applying AOP advice to exported CAO objects ...................................... 302 27.6. Accessing a CAO on the Client .................................................................................. 302 27.6.1. Applying AOP advice to client side CAO objects. ................................... 302 27.7. XML Schema for configuration .................................................................................. 303 27.8. Additional Resources ................................................................................................. 303 28. .NET Enterprise Services .......................................................................................................... 304 28.1. Introduction ............................................................................................................... 304 28.2. Serviced Components ................................................................................................. 304 28.3. Server Side ................................................................................................................ 304 28.4. Client Side ................................................................................................................ 306 29. Web Services ........................................................................................................................... 307 29.1. Introduction ............................................................................................................... 307 29.2. Server-side ................................................................................................................. 307 29.2.1. Removing the need for .asmx files .......................................................... 307 29.2.2. Injecting dependencies into web services ................................................ 308 29.2.3. Exposing POCOs as Web Services ......................................................... 310 29.2.4. Exporting an AOP Proxy as a Web Service ............................................. 311 29.3. Client-side ................................................................................................................. 312 29.3.1. Using VS.NET generated proxy ............................................................. 312 29.3.2. Generating proxies dynamically ............................................................. 312 29.3.3. Configuring the proxy instance .............................................................. 313 30. Windows Communication Foundation (WCF) ............................................................................ 314 30.1. Introduction ............................................................................................................... 314 30.2. Configuring WCF services via Dependency Injection ................................................... 314 30.2.1. Dependency Injection ............................................................................ 314 30.3. Apply AOP advice to WCF services ........................................................................... 316 30.4. Creating client side proxies declaratively ..................................................................... 316 30.5. Exporting POCOs as WCF Services ............................................................................ 317 V. Integration ....................................................................................................................................... 318 31. Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS ......................................... 319
ix
The Spring.NET Framework 31.1. Introduction ............................................................................................................... 31.1.1. Multiple Vendor Support ....................................................................... 31.1.2. Separation of Concerns .......................................................................... 31.1.3. Interoperability and provider portability .................................................. 31.1.4. The role of Messaging API in a 'WCF world' .......................................... 31.2. Using Spring Messaging ............................................................................................. 31.2.1. Messaging Template overview ............................................................... 31.2.2. Connections .......................................................................................... 31.2.3. Caching Messaging Resources ............................................................... 31.2.4. Dynamic Destination Management ......................................................... 31.2.5. Message Listener Containers .................................................................. 31.2.6. Transaction Management ....................................................................... 31.3. Sending a Message .................................................................................................... 31.3.1. Using MessageConverters ...................................................................... 31.. Session and Producer Callback ..................................................................................... 31.5. Receiving a message .................................................................................................. 31.5.1. Synchronous Reception .......................................................................... 31.5.2. Asynchronous Reception ........................................................................ 31.5.3. The ISessionAwareMessageListener interface ......................................... 31.5.4. MessageListenerAdapater ....................................................................... 31.5.5. Processing messages within a messaging transaction ................................ 31.5.6. Messaging Namespace support ............................................................... 32. Message Oriented Middleware - TIBCO EMS ............................................................................ 32.1. Introduction ............................................................................................................... 32.2. Interface based APIs .................................................................................................. 32.3. Using Spring's EMS based Messaging ......................................................................... 32.3.1. Overivew .............................................................................................. 32.3.2. Connections .......................................................................................... 32.3.3. Caching Messaging Resources ............................................................... 32.3.4. Dynamic Destination Management ......................................................... 32.3.5. Accessing Admistrated objects via JNDI ................................................. 32.3.6. MessageListenerContainers .................................................................... 32.3.7. Transaction Management ....................................................................... 32.3.8. Sending a Message ................................................................................ 32.4. Using MessageConverters ........................................................................................... 32.5. Session and Producer Callback ................................................................................... 32.6. Receiving a messages ................................................................................................. 32.6.1. Synchronous Reception .......................................................................... 32.6.2. Asynchronous Reception ........................................................................ 32.6.3. The ISessionAwareMessageListener interface ......................................... 32.6.4. MessageListenerAdapter ........................................................................ 32.6.5. Processing messages within a messaging transaction ................................ 32.6.6. Messaging Namespace support ............................................................... 33. Message Oriented Middleware - MSMQ .................................................................................... 33.1. Introduction ............................................................................................................... 33.2. A quick tour for the impatient .................................................................................... 33.3. Using Spring MSMQ ................................................................................................. 33.3.1. MessageQueueTemplate ......................................................................... 33.3.2. MessageQueueFactoryObject ................................................................. 319 319 320 321 321 321 321 322 322 323 324 324 324 325 327 327 327 328 330 330 332 332 335 335 335 336 336 336 336 337 337 339 339 339 340 340 340 340 341 341 342 342 342 343 343 344 347 347 348
The Spring.NET Framework 33.3.3. MessageQueue and IMessageConverter resource management .................. 33.3.4. Message Listener Containers .................................................................. 33.4. MessageConverters .................................................................................................... 33.4.1. Using MessageConverters ...................................................................... 33.5. Interface based message processing ............................................................................. 33.5.1. ............................................................................................................ 33.6. Comparison with using WCF ...................................................................................... 34. Scheduling and Thread Pooling ................................................................................................. 34.1. Introduction ............................................................................................................... 34.2. Using the Quartz.NET Scheduler ................................................................................ 34.2.1. Using the JobDetailObject ..................................................................... 34.2.2. Using the MethodInvokingJobDetailFactoryObject .................................. 34.2.3. Wiring up jobs using triggers and the SchedulerFactoryObject .................. 35. Template Engine Support .......................................................................................................... 35.1. Introduction ............................................................................................................... 35.2. Dependencies ............................................................................................................. 35.3. Configuring a VelocityEngine ..................................................................................... 35.3.1. Simple file based template engine definition ........................................... 35.3.2. Configuration Options ........................................................................... 35.3.3. Assembly based template loading ........................................................... 35.3.4. Using Spring's IResourceLoader to load templates ................................... 35.3.5. Defining a custom resource loader .......................................................... 35.3.6. Resource Loader configuration options ................................................... 35.3.7. Using a custom configuration file ........................................................... 35.3.8. Logging ................................................................................................ 35.4. Merging a template .................................................................................................... 35.5. Configuring a VelocityEngine without a custom namespace .......................................... VI. VS.NET Integration ........................................................................................................................ 36. Visual Studio.NET Integration .................................................................................................. 36.1. XML Editing and Validation ...................................................................................... 36.2. Enhancing the XML Editing and Validation Experience using the Spring.NET Visual Studio 2010 Extension ........................................................................................................ 36.3. Solution Templates .................................................................................................... 36.3.1. Class Library ........................................................................................ 36.3.2. ADO.NET based application library ....................................................... 36.3.3. NHibernate based application library ...................................................... 36.3.4. Spring based web application ................................................................. 36.4. Resharper Type Completion ....................................................................................... 36.5. Resharper templates ................................................................................................... 36.6. Versions of XML Schema .......................................................................................... 36.7. API documentation .................................................................................................... VII. Quickstart applications .................................................................................................................. 37. IoC Quickstarts ......................................................................................................................... 37.1. Introduction ............................................................................................................... 37.2. Movie Finder ............................................................................................................. 37.2.1. Getting Started - Movie Finder ............................................................... 37.2.2. First Object Definition ........................................................................... 37.2.3. Setter Injection ...................................................................................... 37.2.4. Constructor Injection ............................................................................. 349 349 354 354 355 355 356 358 358 358 358 359 360 361 361 361 361 361 361 362 362 363 363 363 364 364 364 366 367 367 369 369 370 371 372 373 374 375 376 376 377 378 378 378 378 379 380 380
xi
The Spring.NET Framework 37.2.5. Summary .............................................................................................. 37.2.6. Logging ................................................................................................ 37.3. ApplicationContext and IMessageSource ..................................................................... 37.3.1. Introduction .......................................................................................... 37.4. ApplicationContext and IEventRegistry ....................................................................... 37.4.1. Introduction .......................................................................................... 37.5. Pooling example ........................................................................................................ 37.5.1. Implementing Spring.Pool.IPoolableObjectFactory .................................. 37.5.2. Being smart using pooled objects ........................................................... 37.5.3. Using the executor to do a parallel grep .................................................. 37.6. AOP .......................................................................................................................... AOP QuickStart ........................................................................................................................ 38.1. Introduction ............................................................................................................... 38.2. The basics ................................................................................................................. 38.2.1. Applying advice .................................................................................... 38.2.2. Using Pointcuts - the basics ................................................................... 38.3. Going deeper ............................................................................................................. 38.3.1. Other types of Advice ........................................................................... 38.3.2. Using Attributes to define Pointcuts ....................................................... 38.4. The Spring.NET AOP Cookbook ................................................................................ 38.4.1. Caching ................................................................................................ 38.4.2. Performance Monitoring ........................................................................ 38.4.3. Retry Rules ........................................................................................... 38.5. Spring.NET AOP Best Practices ................................................................................. Portable Service Abstraction Quick Start ................................................................................... 39.1. Introduction ............................................................................................................... 39.2. .NET Remoting Example ............................................................................................ 39.3. Implementation .......................................................................................................... 39.4. Running the application .............................................................................................. 39.5. Remoting Schema ...................................................................................................... 39.6. .NET Enterprise Services Example .............................................................................. 39.7. Web Services Example ............................................................................................... 39.8. Additional Resources ................................................................................................. Web Quickstarts ....................................................................................................................... 40.1. Introduction ............................................................................................................... SpringAir - Reference Application ............................................................................................. 41.1. Introduction ............................................................................................................... 41.2. Getting Started ........................................................................................................... 41.3. Container configuration .............................................................................................. 41.4. Bi-directional data binding ......................................................................................... 41.5. Declarative Validation ................................................................................................ 41.6. Internationalization ..................................................................................................... 41.7. Web Services ............................................................................................................. ADO.NET Data Access QuickStart ............................................................................................ 42.1. Introduction ............................................................................................................... 42.1.1. Database configuration .......................................................................... 42.1.2. CommandCallback ................................................................................ Transactions QuickStart ............................................................................................................ 43.1. Introduction ............................................................................................................... 381 382 383 383 385 385 386 386 388 389 389 390 390 390 390 393 395 395 401 402 402 403 403 403 404 404 404 406 411 412 413 414 418 419 419 420 420 420 420 422 422 423 423 425 425 425 426 428 428
38.
39.
40. 41.
42.
43.
xii
The Spring.NET Framework 43.2. Application Overview ................................................................................................ 428 43.2.1. Interfaces .............................................................................................. 428 43.3. Implementation .......................................................................................................... 429 43.4. Configuration ............................................................................................................. 432 43.4.1. Rollback Rules ...................................................................................... 433 43.5. Adding additional Aspects .......................................................................................... 434 NHibernate QuickStart .............................................................................................................. 436 44.1. Introduction ............................................................................................................... 436 44.2. Getting Started ........................................................................................................... 436 44.3. Implementation ......................................................................................................... 439 44.3.1. The Data Access Layer .......................................................................... 439 44.3.2. The domain objects ............................................................................... 440 44.3.3. NHibernate based DAO implementation ................................................. 441 44.3.4. The Service layer .................................................................................. 444 44.3.5. Integration testing .................................................................................. 445 44.3.6. Web Application ................................................................................... 447 Quartz QuickStart ..................................................................................................................... 449 45.1. Introduction ............................................................................................................... 449 45.2. Application Overview ................................................................................................ 449 45.3. Standard job scheduling ............................................................................................. 449 45.4. Scheduling arbitrary methods as jobs .......................................................................... 450 NMS QuickStart ....................................................................................................................... 452 46.1. Introduction ............................................................................................................... 452 46.2. Message Destinations ................................................................................................. 452 46.3. Gateways ................................................................................................................... 453 46.4. Message Data ............................................................................................................ 453 46.5. Message Handlers ...................................................................................................... 455 46.6. Message Converters ................................................................................................... 456 46.7. Messaging Infrastructure ............................................................................................ 456 46.8. Running the application .............................................................................................. 457 TIBCO EMS QuickStart ........................................................................................................... 459 47.1. Introduction ............................................................................................................... 459 47.2. Message Destinations ................................................................................................. 459 47.3. Messaging Infrastructure ............................................................................................ 460 47.4. Running the application .............................................................................................. 461 MSMQ QuickStart .................................................................................................................... 463 48.1. Introduction ............................................................................................................... 463 48.2. Message Destinations ................................................................................................. 463 48.3. Gateways ................................................................................................................... 463 48.4. Message Data ............................................................................................................ 463 48.5. Message Handlers ...................................................................................................... 464 48.6. MessageConverters .................................................................................................... 464 48.7. Messaging Infrastructure ............................................................................................ 464 48.8. Running the application .............................................................................................. 466 WCF QuickStart ....................................................................................................................... 467 49.1. Introduction ............................................................................................................... 467 49.2. The server side .......................................................................................................... 467 49.2.1. WCF Dependency Injection and AOP in self-hosted application ................ 468 49.2.2. WCF Dependency Injection and AOP in IIS web application .................... 468
44.
45.
46.
47.
48.
49.
xiii
The Spring.NET Framework 49.3. Client access .............................................................................................................. VIII. Spring.NET for Java developers ................................................................................................... 50. Spring.NET for Java Developers ............................................................................................... 50.1. Introduction ............................................................................................................... 50.2. Beans to Objects ........................................................................................................ 50.3. PropertyEditors to TypeConverters .............................................................................. 50.4. ResourceBundle-ResourceManager ............................................................................. 50.5. Exceptions ................................................................................................................. 50.6. Application Configuration .......................................................................................... 50.7. AOP Framework ........................................................................................................ 50.7.1. Cannot specify target name at the end of interceptorNames for ProxyFactoryObject .......................................................................................... IX. Appendices .................................................................................................................................... A. Classic Spring Usage ................................................................................................................. A.1. Classic Hibernate Usage .............................................................................................. A.1.1. The HibernateTemplate ........................................................................... A.1.2. Implementing Spring-based DAOs without callbacks ................................ A.2. Classic Declarative Transaction Configurations ............................................................. A.2.1. Declarative Transaction Configuration using DefaultAdvisorAutoProxyCreator ...................................................................... A.2.2. Declarative Transactions using TransactionProxyFactoryObject ................. A.2.3. Concise proxy definitions ....................................................................... A.2.4. Declarative Transactions using ProxyFactoryObject .................................. B. XML Schema-based configuration .............................................................................................. B.1. Introduction ................................................................................................................ B.2. XML Schema-based configuration ............................................................................... B.2.1. Referencing the schemas ......................................................................... B.2.2. The tx (transaction) schema .................................................................... B.2.3. The aop schema ..................................................................................... B.2.4. The db schema ....................................................................................... B.2.5. The wcf schema ..................................................................................... B.2.6. The remoting schema .............................................................................. B.2.7. The nms messaging schema .................................................................... B.2.8. The validation schema ............................................................................ B.2.9. The objects schema ................................................................................ B.3. Setting up your IDE .................................................................................................... C. Extensible XML authoring ......................................................................................................... C.1. Introduction ................................................................................................................ C.2. Authoring the schema .................................................................................................. C.3. Coding a INamespaceParser ......................................................................................... C.4. Coding an IObjectDefinitionParser ............................................................................... C.5. Registering the handler and the schema ........................................................................ C.5.1. NamespaceParsersSectionHandler ............................................................ C.6. Using a custom extension in your Spring XML configuration ......................................... C.7. Further Resources ....................................................................................................... D. Spring.NET's spring-objects.xsd ................................................................................................. 468 470 471 471 471 472 472 472 472 473 473 475 476 476 476 477 478 478 479 480 481 483 483 483 483 484 485 486 486 487 488 488 489 489 490 490 490 491 492 493 493 493 494 495
xiv
Chapter 1. Preface
Developing software applications is hard enough even with good tools and technologies. Spring provides a lightweight solution for building enterprise-ready applications. Spring provides a consistent and transparent means to configure your application and integrate AOP into your software. Highlights of Spring's functionality are providing declarative transaction management for your middle tier as well as a full-featured ASP.NET framework. Spring could potentially be a one-stop-shop for many areas of enterprise application development; however, Spring is modular, allowing you to use just those parts of it that you need, without having to bring in the rest. You can use just the IoC container to configure your application and use traditional ADO.NET based data access code, but you could also choose to use just the Hibernate integration code or the ADO.NET abstraction layer. Spring has been (and continues to be) designed to be non-intrusive, meaning dependencies on the framework itself are generally none (or absolutely minimal, depending on the area of use). This document provides a reference guide to Spring's features. Since this document is still to be considered very much work-in-progress, if you have any requests or comments, please post them on the user mailing list or on the support forums at forum.springframework.net. Before we go on, a few words of gratitude are due to Christian Bauer (of the Hibernate team), who prepared and adapted the DocBook-XSL software in order to be able to create Hibernate's reference guide, thus also allowing us to create this one. Also thanks to Russell Healy for doing an extensive and valuable review of some of the material.
Chapter 2. Introduction
2.1. Overview
Spring.NET is an application framework that provides comprehensive infrastructural support for developing enterprise .NET applications. It allows you to remove incidental complexity when using the base class libraries makes best practices, such as test driven development, easy practices. Spring.NET is created, supported and sustained by SpringSource. The design of Spring.NET is based on the Java version of the Spring Framework, which has shown real-world benefits and is used in thousands of enterprise applications world wide. Spring .NET is not a quick port from the Java version, but rather a 'spiritual port' based on following proven architectural and design patterns in that are not tied to a particular platform. The breadth of functionality in Spring .NET spans application tiers which allows you to treat it as a one stop shop but that is not required. Spring .NET is not an all-or-nothing solution. You can use the functionality in its modules independently. These modules are described below. Enterprise applications typically are composed of a number of a variety of physical tiers and within each tier functionality is often split into functional layers. The business service layer for example typically uses a objects in the data access layer to fulfill a use-case. No matter how your application is architected, at the end of the day there are a variety of objects that collaborate with one another to form the application proper. The objects in an application can thus be said to have dependencies between themselves. The .NET platform provides a wealth of functionality for architecting and building applications, ranging all the way from the very basic building blocks of primitive types and classes (and the means to define new classes), to rich full-featured application servers and web frameworks. One area that is decidedly conspicuous by its absence is any means of taking the basic building blocks and composing them into a coherent whole; this area has typically been left to the purvey of the architects and developers tasked with building an application (or applications). Now to be fair, there are a number of design patterns devoted to the business of composing the various classes and object instances that makeup an all-singing, all-dancing application. Design patterns such as Factory, Abstract Factory, Builder, Decorator, and Service Locator (to name but a few) have widespread recognition and acceptance within the software development industry (presumably that is why these patterns have been formalized as patterns in the first place). This is all very well, but these patterns are just that: best practices given a name, typically together with a description of what the pattern does, where the pattern is typically best applied, the problems that the application of the pattern addresses, and so forth. Notice that the last paragraph used the phrase ... a description of what the pattern does...; pattern books and wikis are typically listings of such formalized best practice that you can certainly take away, mull over, and then implement yourself in your application. The Spring Framework takes best practices that have been proven over the years in numerous applications and formalized as design patterns, and actually codifies these patterns as first class objects that you as an architect and developer can take away and integrate into your own application(s). This is a Very Good Thing Indeed as attested to by the numerous organizations and institutions that have used the Spring Framework to engineer robust, maintainable applications. For example, the IoC component of the Spring Framework addresses the enterprise concern of taking the classes, objects, and services that are to compose an application, by providing a formalized means of composing these various disparate components into a fully working application ready for use
2.2. Background
In early 2004, Martin Fowler asked the readers of his site: when talking about Inversion of Control: the question is, what aspect of control are [they] inverting?. Fowler then suggested renaming the principle (or at least giving
Introduction it a more self-explanatory name), and started to use the term Dependency Injection. His article then continued to explain the ideas underpinning the Inversion of Control (IoC) and Dependency Injection (DI) principle. If you need a decent insight into IoC and DI, please do refer to the article : http://martinfowler.com/articles/ injection.html.
2.3. Modules
The Spring Framework contains a lot of features, which are well-organized into modules shown in the diagram below. The diagram below shows the various core modules of Spring.NET.
Click on the module name for more information. Spring.Core is the most fundamental part of the framework allowing you to configure your application using Dependency Injection. Other supporting functionality, listed below, is located in Spring.Core Spring.Aop - Use this module to perform Aspect-Oriented Programming (AOP). AOP centralizes common functionality that can then be declaratively applied across your application in a targeted manner. Spring's aspect library provides predefined easy to use aspects for transactions, logging, performance monitoring, caching, method retry, and exception handling. Spring.Data - Use this module to achieve greater efficiency and consistency in writing data access functionality in ADO.NET and to perform declarative transaction management. Spring.Data.NHibernate - Use this module to integrate NHibernate with Springs declarative transaction management functionality allowing easy mixing of ADO.NET and NHibernate operations within the same transaction. NHibernate 1.0 users will benefit from ease of use APIs to perform data access operations. Spring.Messaging - Use this module to raise the level of abstraction when interacting with the Microsoft MSMQ message queing middleware Spring.Messaging.NMS - Use this module to raise the level of abstraction when interacting with the Apache ActiveMQ (NMS) message queing middleware Spring.Messaging.EMS - Use this module to raise the level of abstraction when interacting with the Tibco Enterprise Message Service (EMS) message queing middleware
Introduction Spring.Web - Use this module to raise the level of abstraction when writing ASP.NET web applications allowing you to effectively address common pain-points in ASP.NET such as data binding, validation, and ASP.NET page/ control/module/provider configuration. Spring.Web.Mvc - Use this module to integrate the functionality of the Spring.Core and Spring.Aop modules into your ASP.NET MVC 2 projects. Spring.Web.Mvc3 - Use this module to integrate the functionality of the Spring.Core and Spring.Aop modules into your ASP.NET MVC 3 projects. Spring.Web.Extensions - Use this module to raise the level of abstraction when writing ASP.NET web applications allowing you to effectively address common pain-points in ASP.NET such as data binding, validation, and ASP.NET page/control/module/provider configuration. Spring.Services - Use this module to adapt plain CLR objects so they can be used with a specific distributed communication technology, such as .NET Remoting, Enterprise Services, and ASMX Web Services. These services can be configured via dependency injection and decorated by applying AOP. Spring.Testing.NUnit - Use this module to perform integration testing with NUnit. Spring.Testing.MSTest - Use this module to perform integration testing with MSTest Spring.Scheduling.Quartz - Use this module to support interacting with the Quartz.NET job scheduler infrastructure. The Spring.Core module also includes the following additional features Expression Language - provides efficient querying and manipulation of an object graphs at runtime. Validation Framework - a robust UI agnostic framework for creating complex validation rules for business objects either programatically or declaratively. Data binding Framework - a UI agnostic framework for performing data binding. Dynamic Reflection - provides a high performance reflection API Threading - provides additional concurrency abstractions such as Latch, Semaphore and Thread Local Storage. Resource abstraction - provides a common interface to treat the InputStream from a file and from a URL in a polymorphic and protocol-independent manner.
Introduction While the Spring framework does not force any particular application architecure it encourages the use of a well layered application architecture with distinct tiers for the presentation, service, data access, and database.
for resources,
Adopters of Spring.NET are encouraged to explore these projects (and more!) that help to round out the comprehensive Spring.NET experience and provide additional capabilities beyond those of the core Spring.NET Framework to increase developer efficiency and effectiveness.
2.8. Support
Training and support are available through SpringSource in addition to the mailing lists and forums you can find on the main Spring.NET website.
4.2.1. Namespaces
Note: If you previously installed Spring .xsd files to your VS.NET installation directory, remove them manually, and copy over the new ones, which have the -1.1.xsd suffix. The names of the section handlers to register custom schemas has changed, from ConfigParsersSectionHandler to NamespaceParsersSectionHandler. The target namespaces have changed, the 'directory' named /schema/ has been removed. For example, the target schema changed from http://www.springframework.net/schema/tx to http://www.springframework.net/tx. A typical declaration to use custom schemas within your configuration file looks like this
<objects xmlns='http://www.springframework.net' xmlns:db="http://www.springframework.net/database" xmlns:tx="http://www.springframework.net/tx" xmlns:aop="http://www.springframework.net/aop">
to
Spring.Validation.Config.ValidationNamespaceParser
Renamed from DatabaseConfigParser to DatabaseNamespaceParser Renamed/Moved Remoting.RemotingConfigParser to Remoting.Config.RemotingNamespaceParser A typical registration of custom parsers within your configuration file looks like this
<configuration> <configSections> <sectionGroup name="spring"> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections>
4.2.2. Core
Moved Spring.Util.DynamicReflection to Spring.Reflection.Dynamic Moved TypeRegistry and related classes from Spring.Context.Support to Spring.Core.TypeResolution Moved Spring.Objects.TypeConverters to Spring.Core.TypeConvesion
4.2.3. Web
Moved Spring.Web.Validation to Spring.Web.UI.Validation
4.2.4. Data
Changed schema to use 'provider' instead of 'dbProvider' element, usage is now <db:provider ... /> and not <db:dbProvider .../> Moved TransactionTemplate, Spring.Data.Support TransactionDelegate and ITransactionCallback from Spring.Data to
Moved AdoTemplate, AdoAccessor, AdoDaoSupport, RowMapperResultSetExtractor from Spring.Data to Spring.Data.Core Moved AdoPlatformTransactionManager, ServiceDomainPlatformTransactionManager, TxScopeTransactionManager from Spring.Data to Spring.Data.Core and
Note
For more information on the In-Process Side-by-Side support introduced into the .NET 4 Framework, see the MSDN Magazine article located here: http://msdn.microsoft.com/en-us/magazine/
ee819091.aspx
Beginning with Spring.NET 1.3.2, this approach is no longer necessary and fuill native support of .NET is now provided.
10
assembly
is
the
basis
for
Spring.NET's
IoC
container.
IObjectFactory
[http://www.springframework.net/doc-latest/api/net-2.0/html/
Spring.Core~Spring.Objects.Factory.IObjectFactory.html]
mechanism
capable
of
managing
any
type
of
www.springframework.net/doc-latest/api/net-2.0/html/
is a sub-interface of IObjectFactory. It adds easier integration with Spring.NET's Aspect Oriented Programming (AOP) features, message resource handling (for use in internationalization), event propagation and application layer-specific context such as WebApplicationContext for use in web applications.
Spring.Core~Spring.Context.IApplicationContext.html]
In short, the IObjectFactory provides the configuration framework and basic functionality, and the IApplicationContext adds more enterprise-specific functionality. The IApplicationContext is a complete superset of the IObjectFactory and is used exclusively in this chapter in descriptions of Spring's IoC container. If you are new to Spring.NET or IoC containers in general, you may want to consider starting with Chapter 37, IoC Quickstarts, which contains a number of introductory level examples that actually demonstrate a lot of what is described in detail below. Don't worry if you don't absorb everything at once... those examples serve only to paint a picture of how Spring.NET hangs together in really broad brushstrokes. Once you have finished with those examples, you can come back to this section which will fill in all the fine detail.
Note
Note that other ways to specify the metadata, such as attributes and .NET code, are planned for future releases, the core IoC container does not assume any specific metadata format. The Java version of Spring already supports such functionality. Several implementations of the IApplicationContext interface are supplied out-of-the-box with Spring. In standalone applications it is common to create an instance of an XmlApplicationContext either programmatically or declaratively in your applications App.config file. In web applications Spring provides a WebApplicationContext implementation which is configured by adding a custom HTTP module and HTTP handler to your Web.config file. See the section on Web Configuration for more details.
11
The IoC container The following diagram is a high-level view of how Spring works. Your application classes are combined with configuration metadata so that after the ApplicationContext is created and initialized, you have a fully configured and executable system or application.
Note
XML-based metadata is by far the most commonly used form of configuration metadata. It is not however the only form of configuration metadata that is allowed. The Spring IoC container itself is totally decoupled from the format in which this configuration metadata is actually written. Attribute based and code based metadata will be part of an upcoming release and it is already part of the Spring Java framework. Spring configuration consists of at least one and typically more than one object definition that the container must manage. XML- based configuration shows these objects as <object/> elements inside a top-level <objects/> element.
12
The IoC container These object definitions correspond to the actual objects that make up your application. Typically you define service layer objects, data access objects (DAOs), presentation objects such as ASP.NET page instances, infrastructure objects such as NHibernate SessionFactories, and so forth. Typically one does not configure finegrained domain objects in the container, because it is usually the responsibility of DAOs and business logic to create/load domain objects. The following example shows the basic structure of XML-based configuration metadata:
<objects xmlns="http://www.springframework.net"> <object id="..." type="..."> <!-- collaborators and configuration for this object go here --> </object> <object id="...." type="..."> <!-- collaborators and configuration for this object go here --> </object> <!-- more object definitions go here --> </objects>
The id attribute is a string that you use to identify the individual object definition. The type attribute defines the type of the object and uses the fully qualified type name, including the assembly name. The value of the id attribute refers to collaborating objects. The XML for referring to collaborating objects is not shown in this example; Dependencies for more information. Spring.NET comes with an XSD schema to make the validation of the XML object definitions a whole lot easier. The XSD document is thoroughly documented so feel free to take a peek inside (see Appendix D, Spring.NET's spring-objects.xsd). The XSD is currently used in the implementation code to validate the XML document. The XSD schema serves a dual purpose in that it also facilitates the editing of XML object definitions inside an XSD aware editor (typically Visual Studio) by providing validation (and Intellisense support in the case of Visual Studio). You may wish to refer to Chapter 36, Visual Studio.NET Integration for more information regarding such integration.
The following example shows the service layer objects (services.xml) configuration file.
<objects xmlns="http://www.springframework.net"> <object id="PetStore" type="PetStore.Services.PetStoreService, PetStore"> <property name="AccountDao" ref="AccountDao"/> <property name="ItemDao" ref="ItemDao"/> <!-- additional collaborators and configuration for this object go here --> </object> <!-- more object definitions for services go here --> </objects>
The following example shows the data access objects (daos.xml) configuration file:
13
In the preceeding example, the service layer consists of the class PetStoreService, and two data access objects of the type HibernateAccountDao and HibernateItemDao are based on the NHibernate Object/Relational mapping framework. The property name element refers to the name of the class's property, and the ref element refers to the name of another object definition. This linkage between id and ref elements expresses the dependency between collaborating objects. For details of configuring an object's dependencies, see Dependencies. 5.2.2.1. Loading configuration metadata from non-default resource locations In the previous example the configuration resources are assumed to be located in the bin\Debug directory. You can use Spring's IResource [http://www.springframework.net/doc-latest/api/net-2.0/html/ Spring.Core~Spring.Core.IO.IResource.html] abstraction to load resources from other locations. The following example shows how to create an IoC container referring to resources located in the root directory of the filesystem an as an embedded assembly resource.
IApplicationContext context = new XmlApplicationContext( "file:///services.xml", "assembly://MyAssembly/MyDataAccess/data-access.xml");
The above example uses Spring.NET's IResource [http://www.springframework.net/doc-latest/api/net-2.0/html/ Spring.Core~Spring.Core.IO.IResource.html] abstraction. The IResource interface provides a simple and uniform interface to a wide array of IO resources that can represent themselves as System.IO.Stream.
Note
After you learn about Spring's IoC container, you may want to know more about Spring's IResource [http://www.springframework.net/doc-latest/api/net-2.0/html/ Spring.Core~Spring.Core.IO.IResource.html] abstraction to load metadata from other locations as desribed below and alsoin the chapter Chapter 7, Resources These resources are most frequently files or URLs but can also be resources that have been embedded inside a .NET assembly. A simple URI syntax is used to describe the location of the resource, which follows the standard conventions for files, i.e. file:///services.xml and other well known protocols such as http. The following snippet shows the use of the URI syntax for referring to a resource that has been embedded inside a .NET assembly, assembly://<AssemblyName>/<NameSpace>/<ResourceName>. The IResource abstraction is explained further in Section 7.1, Introduction.
Note
To create an embedded resource using Visual Studio you must set the Build Action of the .xml configuration file to Embedded Resource in the file property editor. Also, you will need to explicitly rebuild the project containing the configuration file if it is the only change you make between
14
The IoC container successive builds. If using NAnt to build, add a <resources> section to the csc task. For example usage, look at the Spring.Core.Tests.build file included the distribution. 5.2.2.2. Declarative configuration of the container in App.config/Web.config You can also create a container by using a custom configuration section in the standard .NET application configuration file (one of App.config or Web.config). A custom configuration section that creates the same IApplicationContext as the previous example is
<spring> <context> <resource uri="file://services.xml"/> <resource uri="assembly://MyAssembly/MyDataAccess/data-access.xml"/> </context> </spring>
The context type (specified as the value of the type attribute of the context element) is optional. In a standalone application the context type defaults to the Spring.Context.Support.XmlApplicationContext class and in a Web application defaults to WebApplicationContext. An example of explicitly configuring the context type The following example shows explicit use of the context type attribute:
<spring> <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core"> <resource uri="file:///services.xml"/> <resource uri="assembly://MyAssembly/MyDataAccess/data-access.xml"/> </context> </spring>
To acquire a reference to an IApplicationContext using a custom configuration section, one simply uses the following code;
IApplicationContext ctx = ContextRegistry.GetContext();
The ContextRegistry is used to both instantiate the application context and to perform service locator style access to other objects. (See Section 5.15, Service Locator access for more information). The glue that makes this possible is an implementation of the Base Class Library (BCL) provided IConfigurationSectionHandler interface, namely the Spring.Context.Support.ContextHandler class. The handler class needs to be registered in the configSections section of the .NET configuration file as shown below.
<configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> </sectionGroup> </configSections>
This declaration now enables the use of a custom context section starting at the spring root element. In some usage scenarios, user code will not have to explicitly instantiate an appropriate implementation IApplicationContext interface, since Spring.NET code will do it for you. For example, the ASP.NET web layer provides support code to load a Spring.NET WebApplicationContext automatically as part of the normal startup process of an ASP.NET web application. As such, once the container has been created for you, it is often the case that you will never need to explicitly interact with it again in your code, for example when configuring ASP.NET pages. Spring.NET comes with an XSD schema to make the validation of the XML object definitions a whole lot easier. The XSD document is thoroughly documented so feel free to take a peek inside (see Appendix D, Spring.NET's spring-objects.xsd). The XSD is currently used in the implementation code to validate the XML document. The XSD schema serves a dual purpose in that it also facilitates the editing of XML object definitions inside an XSD aware editor (typically Visual Studio) by providing validation (and Intellisense support in the case of Visual
15
The IoC container Studio). You may wish to refer to Chapter 36, Visual Studio.NET Integration for more information regarding such integration. Your XML object definitions can also be defined within the standard .NET application configuration file by registering the Spring.Context.Support.DefaultSectionHandler class as the configuration section handler for inline object definitions. This allows you to completely configure one or more IApplicationContext instances within a single standard .NET application configuration file as shown in the following example.
<configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> </sectionGroup> </configSections> <spring> <context> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> ... </objects> </spring> </configuration>
Other options available to structure the configuration files are described in Section 5.12.1, Context Hierarchies and Section 5.2.2.3, Composing XML-based configuration metadata. The IApplicationContext can be configured to register other resource handlers, custom parsers to integrate user-contributed XML schema into the object definitions section, type converters, and define type aliases. These features are discussed in section Section 5.11, Configuration of IApplicationContext 5.2.2.3. Composing XML-based configuration metadata It can be useful to have object definitions span multiple XML files. Often each individual XML configuration file represents a logical layer or module in your architecture. You can use the IApplicationContext constructor to load object definitions from all these XML fragments. This constructor takes multiple IResource locations, as was shown in the previous section. Alternatively, use one or more occurrences of the <import/> element to load object definitions from another file (or files). For example:
<objects xmlns="http://www.springframework.net"> <import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <object id="object1" type="..."/> <object id="object2" type="..."/> </objects>
In the preceeding example, external object definitions are being loaded from three files, services.xml, messageSource.xml, and themeSource.xml. All location paths are relative to the definition file doing the importing, so services.xml must be in the same directory as the file doing the importing, while messageSource.xml and themeSource.xml must be in a resources location below the location of the importing file. As you can see, a leading slash is ignored, but given that these paths are relative, it is better form not to use
16
The IoC container the slash at all. The contents of the files being imported, including the top level <objects/> element, must be valid XML object definitions according to the Spring Schema.
You use the method GetObject to retrieve instances of your objects. The IApplicationContext interface has a few other methods for retrieving objects, but ideally your application code should never use them. Indeed, your application code should have no calls to the GetObject method at all, and thus no dependency on Spring APIs at all. For example, Spring's integration with web frameworks provides for dependency injection for various web framework classes such as ASP.NET pages and user controls.
Note
The syntactical inconvenience of the cast will be addressed in a future release of Spring.NET that is based on a generic API. Note, that even when using a generic API, looking up an object by name in no way guarantees that the return type will be that of the generic type.
17
The IoC container Property id and name singleton or prototype object properties constructor arguments autowiring mode dependency checking mode initialization method destruction method More info Section 5.2.4.1, Naming objects Section 5.4, Object Scopes Section 5.3.1, Dependency injection Section 5.3.1, Dependency injection Section 5.3.6, Autowiring collaborators Section 5.3.7, Checking for dependencies Section 5.6.1, Lifecycle interfaces Section 5.6.1, Lifecycle interfaces
In addition to object definitions which contain information on how to create a specific object, the IApplicationContext implementations also permit the registration of existing objects that are created outside the container, by users. This is done by accessing the ApplicationContext's IObjectFactory via the property ObjectFactory which returns the IObjectFactory implementation DefaultListableObjectFactory. DefaultListableObjectFactory supports registration through the methods RegisterSingleton(..) and RegisterObjectDefinition(..). However, typical applications work soley with objects defined through metadata object definitions. 5.2.4.1. Naming objects Every object has one or more identifiers. These identifiers must be unique within the container that hosts the objects. An object usually has only one identifier, but if it requires more than one, the extra ones can be considered aliases.
A convention that has evolved is to use the standard C# convention for Property names when naming objects. That is, object names start with a uppercase letter, and are camel-cased from then on. Examples of such names would be (without quotes) 'AccountManager', 'AccountService', 'UserDao', 'LoginController', and so forth. Naming object consistently makes your configuration easier to read and understand, and if you are using Spring AOP it helps a lot when applying advice to a set of objects related by name. When using XML-based configuration metadata, you use the 'id' and/or 'name'attributes to specify the object identifier(s). The 'id' attribute allows you to specify exactly one id, and because it is a real XML element ID attribute, the XML parser is able to do some extra validation when other elements reference the id. As such, it is the preferred way to specify an object id. However, the XML specification does limit the characters which are legal in XML IDs. This is usually not a constraint, but if you have a need to use one of these special XML characters, or want to introduce other aliases to the object, you can specify them in the 'name' attribute , separated by a comma (,), semicolon (;), or whitespace.
Note
You are not required to supply a name or id for a object. If no name or id is supplied explicitly, the container will generate a unique name for that object. However, if you want to refer to that object
18
The IoC container by name, through the use of the ref element or Service Location style lookup, you must provide a name. The motivations for not supplying a name for a object are to use autowiring and inline-objects which will be discussed later.
In this case, an object in the same container which is named fromName, may also after the use of this alias definition, be referred to as toName. For example, the configuration metadata for subsystem A may refer to a DbProvider via the name 'SubsystemADbProvider. The configuration metadata for subsystem B may refer to a DbProvider via the name 'SubsystemBDbProvider'. When composing the main application that uses both these subsystems the main application refers to the DbProvider via the name 'MyApp-DbProvider'. To have all three names refer to the same object you add to the MyApp configuration metadata the following aliases definitions:
<alias name="SubsystemA-DbProvider" alias="SubsystemB-DbProvider"/> <alias name="SubsystemA-DbProvider" alias="MyApp-DbProvider"/>
Now each component and the main app can refer to the connection through a name that is unique and guaranteed not to clash with any other definition (effectively there is a namespace), yet they refer to the same object.
19
The IoC container 5.2.5.1. Instantiatoin with a constructor When you create an object using the constructor approach, all normal classes are usable by and compatible with Spring. That is, the class being developed does not need to implement any specific interfaces or to be coded in a specific fashion. Simply specifying the object type should be sufficient. However, depending on what type of IoC you are going to use for that specific object, you may need to create a default constructor. With XML-based configuration metadata you can specify your object class as follows:
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary"/>
For details about the mechanism for supplying arguments to the constructor (if required), and setting object instance properties after the object is constructed, see Section 5.3.1, Dependency injection. This XML fragment describes an object definition that will be identified by the exampleObject name, instances of which will be of the Examples.ExampleObject type that has been compiled into the ExamplesLibrary assembly. Take special note of the structure of the type attribute's value... the namespace-qualified name of the class is specified, followed by a comma, followed by (at a bare minimum) the name of the assembly that contains the class. In the preceding example, the ExampleObject class is defined in the Examples namespace, and it has been compiled into the ExamplesLibrary assembly. The name of the assembly that contains the type must be specified in the type attribute. Furthermore, it is recommended that you specify the fully qualified assembly name 2 in order to guarantee that the type that Spring.NET uses to instantiate your object (s) is indeed the one that you expect. Usually this is only an issue if you are using classes from (strongly named) assemblies that have been installed into the Global Assembly Cache (GAC). If you have defined nested classes use the addition symbol, +, to reference the nested class. For example, if the class Examples.ExampleObject had a nested class Person the XML declaration would be
<object id="exampleObject" type="Examples.ExampleObject+Person, ExamplesLibrary"/>
If you are defining classes that have been compiled into assemblies that are available to your application (such as the bin directory in the case of ASP.NET applications) via the standard assembly probing mechanisms, then you can specify simply the name of the assembly (e.g. ExamplesLibrary.Data)... this way, when (or if) the assemblies used by your application are updated, you won't have to change the value of every <object/> definition's type attribute to reflect the new version number (if the version number has changed)... Spring.NET will automatically locate and use the newer versions of your assemblies (and their attendant classes) from that point forward. 5.2.5.2. Instantiation with a static factory method When defining an object which is to be created using a static factory method, you use the type attribute to specify the type containing the static factory method and an attribute named factory-method to specify the name of the factory method itself. You should be able to call this method (with an optional list of arguments as described later) and return a live object, which subsequently is treated as if it had been created through a constructor. One use for such an object definition is to call static factories in legacy code. The following object definition specifies that the object will be created by calling a factory-method. The definition does not specify the type of the returned object, only the type containing the factory method. In this example, CreateInstance must be a static method.
2
More information about assembly names can be found in the Assembly Names section of the .NET Framework Developer's Guide (installed as part of the .NET SDK), or online at Microsoft's MSDN website, by searching for Assembly Names.
20
For details about the mechanism for supplying (optional) arguments to the factory method and setting object instance properties after it has been returned from the factory, see Section 5.3.2, Dependencies and configuration in detail 5.2.5.3. Object creation via an instance factory method Similar to instantiation through a static factory method, instantiation with an instance factory method invokes a a non-static method on an existing object from the container to create a new object. To use this mechanism, leave the type attribute empty, and in the factory-object attribute specify the name of an object in the current (or parent/ancestor) container that contains the instance method that is to be invoked to create the object. Set the name of the factory method itself with the factory-method attribute.
<!-- the factory object, which contains an instance method called 'CreateInstance' --> <object id="exampleFactory" type="..."> <!-- inject any dependencies required by this object --> </object> <!-- the object that is to be created by the factory object --> <object id="exampleObject" factory-method="CreateInstance" factory-object="exampleFactory"/>
This approach shows that the factory object itself can be managed and configured through dependency injection (DI). See Dependencies and configuraiton in detail.
Note
In Spring documentation, 'factory object', refers to an object that is configured in the Spring container that will create objects via an instance or static factory method. By contrast, IFactoryObject (notice the capitalization) refers to a Spring-specific IFactoryObject .
21
The XML configuration to create and configure this object is shown below
<object id="myFilteredIntList" type="GenericsPlay.FilterableList<int>, GenericsPlay"> <property name="Name" value="My Integer List"/> </object>
There are a few items to note in terms how to specify a generic type. First, the left bracket that specifies the generic type, i.e. <, is replaced with the string < due to XML escape syntax for the less than symbol. Yes, we all realize this is less than ideal from the readability point of view. Second, the generic type arguments can not be fully assembly qualified as the comma is used to separate generic type arguments. Alternative characters used to overcome the two quirks can be implemented in the future but so far, all proposals don't seem to help clarify the text. The suggested solution to improve readability is to use type aliases as shown below
<typeAliases> <alias name="GenericDictionary" type=" System.Collections.Generic.Dictionary<,>" /> <alias name="myDictionary" type="System.Collections.Generic.Dictionary<int,string>" /> </typeAliases>
It can be shortened to
<object id="myOtherGenericObject" type="GenericsPlay.ExampleGenericObject<GenericDictionary<int , string>>, GenericsPlay" />
or even shorter
<object id="myOtherOtherGenericObject" type="GenericsPlay.ExampleGenericObject<MyIntStringDictionary>, GenericsPlay" />
Refer to Section 5.11, Configuration of IApplicationContext for additional information on using type aliases. 5.2.6.2. Object creation of generic types via static factory method The following classes are used to demonstrate the ability to create instances of generic types that themselves are created via a static generic factory method.
public class TestGenericObject<T, U> { public TestGenericObject() { } private IList<T> someGenericList = new List<T>(); private IDictionary<string, U> someStringKeyedDictionary = new Dictionary<string, U>(); public IList<T> SomeGenericList {
22
The XML snippet to create an instance of TestGenericObject where V is a List of integers and W is an integer is shown below
<object id="myTestGenericObject" type="GenericsPlay.TestGenericObjectFactory, GenericsPlay" factory-method="StaticCreateInstance<System.Collections.Generic.List<int>,int>" />
The StaticCreateInstance method is responsible for instantiating the object that will be associated with the id 'myTestGenericObject'. 5.2.6.3. Object creation of generic types via instance factory method Using the class from the previous example the XML snippet to create an instance of a generic type via an instance factory method is shown below
<object id="exampleFactory" type="GenericsPlay.TestGenericObject<int,string>, GenericsPlay"/> <object id="anotherTestGenericObject" factory-object="exampleFactory" factory-method="CreateInstance<System.Collections.Generic.List<int>,int>"/>
5.3. Dependencies
A typical enterprise application does not consist of a single object. Even the simplest application has a few objects that work together to present what the end-user sees as a coherent application. This next section explains how you go from defining a number of object definitions that stand-alone to a fully realized application where objects collaborate to achieve a goal.
23
The IoC container is constructed. (Factory methods may be considered a special case of providing constructor arguments for the purposes of this description). The container injects these dependencies when it creates the object. This process is fundamentally the inverse to the case when the object itself is controlling the instantiation or location of its dependencies by using direct construction of classes, or the Service Locator pattern. The inverting of this responsibility is why the name Inversion of Control (IoC) is used to describe the container's actions. Code is cleaner when using DI and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies, and does not know the location or class of the dependencies. Long sections of initialization code that you used to hide in a #region tag simply go away, and are placed by container configuration metadata. One can also consider this clean up an application of the principal of Separation of Concerns. Before using DI, you class was responsible for business logic AND its configuration, it was concerns with doing more than one thing. DI removes the responsibility of configuration from the class, leaving it only with a single purpose, as the location of business logic. Furthermore, since you class does not know the location of its dependencies these classes also become easier to test, in particular when the dependencies are interfaces or abstract base classes allowing for stub or mock implementation to be used in unit tests. Dependency injection exists in two major variants, Constructor-based dependency injection and Setter-based dependency injection. 5.3.1.1. Constructor-based dependency injection Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static factory method with specific arguments to construct the object is nearly equivalent, and this discussion treats arguments to a constructor and to a static factory method similarly. The following example shows a class that can only be dependency-injected with constructor injection. Notice that there is nothing special about this class (no container specific interfaces, base classes or attributes)
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinder private IMovieFinder movieFinder; // a constructor so that the Spring container can 'inject' a MovieFinder public MovieLister(IMovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually 'uses' the injected IMovieFinder is omitted... }
24
The IoC container No potential ambiguity exists, assuming of course that Bar and Baz classes are not related by inheritance. Thus the following configuration will work just fine, and you do not need to specify the constructor argument indexes and / or types explicitly in the <contructor-arg/> element.
<object id="foo" type="X.Y.Foo, Example"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </object> <object id="bar" type="X.Y.Bar, Example"/> <object id="baz" type="X.Y.Baz, Example"/>
When another object is referenced, the type is known, and matching can occur (as was the case with the preceding example). When a simple type is used, such as <value>true<value>, Spring cannot determine the type of the value, and so cannot match by type without help. Consider the following class:
using System; namespace SimpleApp { public class ExampleObject { private int years; private string ultimateAnswer;
//No. of years to the calculate the Ultimate Answer //The Answer to Life, the Universe, and Everything
5.3.1.1.1.1. Constructor argument type matching In the preceding scenario, the container can use type matching with simple types by explicitly specifying the type of the constructor argument using the 'type' attribute. For example:
<object name="exampleObject" type="SimpleApp.ExampleObject, SimpleApp"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="string" value="42"/> </object>
The type attribute specifies the System.Type of the constructor argument, such as System.Int32. Alias' are available to for common simple types (and their array equivalents). These alias' are... Table 5.2. Type aliases Type Alias' Array Alias' char[], Char() short[], Short() int[], Integer() long[], Long() ushort[] uint[]
System.Char Char char, System.Int16 Short short, System.Int32 Integer int, System.Int64 Long long, System.UInt16 ushort System.UInt32 uint
25
The IoC container Type Alias' Array Alias' ulong[] float[], Single() double[], Double() date[], Date() decimal[], Decimal() bool[], Boolean() string[], String()
System.UInt64 ulong System.Float Single float, System.Double Double double, System.Date Date date, System.Decimal Decimal decimal, System.Bool Boolean bool, System.String String string, 5.3.1.1.1.2. Constructor argument Index
Use the index attribute to specify explicitly the index of constructor arguments. For example:
<object name="exampleObject" type="SimpleApp.ExampleObject, SimpleApp"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </object>
In addition to resolving the ambiguity of multiple simple values, specifying an index also resolves ambiguity where a constructor has two arguments of the same type. Note that the index is 0 based. 5.3.1.1.1.3. Constructor arguments by name You can specify constructor argumetn by name using name attribute of the <constructor-arg> element.
<object name="exampleObject" type="SimpleApp.ExampleObject, SimpleApp"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </object>
5.3.1.2. Setter-based dependency injection Setter-based DI is accomplished by the container invoking setter properties on your objects after invoking a noargument constructor or no-argument static factory method to instantiate your object. The following eample shows a class that can only be dependency injected using pure setter injection.
public class MovieLister { private IMovieFinder movieFinder; public IMovieFinder MovieFinder { set { movieFinder = value; } } // business logic that actually 'uses' the injected IMovieFinder is omitted... }
26
The IoC container Constructor-based or setter-based DI? The Spring team generally advocates the usage of setter injection, since a large number of constructor arguments can get unwieldy, especially when some properties are optional. The presence of setter properties also makes objects of that class amenable to reconfigured or reinjection later. Managment through WMI is a compelling use case. Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection. Use the DI that makes the most sense for a particular class. Sometimes, when dealing with third-party classes to which you do not have the source, the choice is made for you. A legacy class may not expose any setter methods, and so constructor injection is the only available DI. Since you can mix both, Constructor- and Setter-based DI, it is a good rule of thumb to use constructor arguments for mandatory dependencies and setters for optional dependencies. The IAppliationContext supports constructor- and setter-based DI for the objects it manages. It also supports setter-based DI after some dependencies have already been supplied via the constructor approach.. The configuration for the dependencies comes in the form of the IObjectDefinition class, which is used together with TypeConverters to know how to convert properties from one format to another. However, most users of Spring.NET will not be dealing with these classes directly (that is programatically), but rather with an XML definition file which will be converted internally into instances of these classes, and used to load an entire Spring IoC container instance. Refer to Section 6.3, Type conversion for more information regarding type conversion, and how you can design your classes to be convertible by Spring.NET. The container resolves object dependeices as: 1. The IApplicationContext is created and initialized with a configuration that describes all the objects. Most Spring.NET users use an IObjectFactory or IApplicationContext variant that supports XML format configuration files. 2. Each object has dependencies expressed in the form of properties, constructor arguments, or arguments to the static-factory method if you are using that instead of a normal constructor. These dependencies apre provided to the object, when the object is actually created. 3. Each property or constructor argument is either an actual definition of the value to set, or a reference to another object in the container. 4. Each property or constructor argument which is a value must be able to be converted from whatever format it was specified in, to the actual System.Type of that property or constructor argument. By default Spring.NET can convert a value supplied in string format to all built-in types, such as int, long, string, bool, etc. The Spring container validates the configuration of each object as the container is created, including the validation of whether object reference properties refer to valid object. However, the object properties themselves are not set until the object is actually created. Objects that are defined as singletons and set to be pre-instantiated, are created when the container is created. Otherwise, the object is created only when it is requested. Creation of an object potentially causes a graph of objects to be created as the objects dependencies and its dependencies' dependencies (and so on) are created and assigned.
27
The IoC container Circular Dependencies If you are using predominantly constructor injection it is possible to create unresolvable circular dependency scenario. For example: Class A, which requires an instance of class B to be provided via constructor injection, and class B, which requires an instance of class A to be provided via constructor injection. If you configure objects for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throw a ObjectCurrentlyInCreationException. One possible solution to this issue is to edit the source code of some of your classes to be configured via setters instead of via constructors. Alternatively, avoid constructor injection and stick to setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection. Unlike the typical case (with no circular dependencies), a circular dependency between object A and object B will force one of the objects to be injected into the other prior to being fully initialized itself (a classic chicken/egg scenario). You can generally trust Spring.NET to do the right thing. It detects configuration problems, such as references to non-existent object definitions and circular dependencies, at container load-time. Spring sets properties and resolves dependencies as late as possible, which is when the object is actually created. This means that a Spring container which has loaded correctly can later generate an exception when you request an object if there is a problem creating that object or one of its dependencies. For example, the object throws an exception as a result of a missing or invalid property. This potentially delayed visibility of some configuration issues is why IApplicationContext by default pre-instantiate singleton objects. At the cost of some upfront time and memory to create these objects before they are actually needed, you discover configuration issues when the IApplicationContext is created, not later. If you wish, you can still override this default behavior and set any of these singleton objects will lazy-initialize, rather than be pre-instantiated. If no circular dependencies exist, when one or more collaborating objects are being injected into a dependent object, each collaborating object is totally configured prior to being passed into the dependent object. This means that if object A has a dependency on object B, the Spring IoC container completely configures object B prior to invoking the setter method on object A. In other words, the object is instantiated (if not a pre-instantiated singleton), its dependencies are set, and the relevant lifecycle methods (such as a configured init method or the IIntializingObject callback method) will all be invoked. 5.3.1.3. Examples of dependency injection First, an example of using XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifying some object definitions:
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary"> <!-- setter injection using the ref attribute --> <property name="objectOne" ref="anotherExampleObject"/> <property name="objectTwo" ref="yetAnotherObject"/> <property name="IntegerProperty" value="1"/> </object> <object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/> <object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
28
In the preceding example, setters have been declared to match against the properties specified in the XML file. Find below an example of using constructor-based DI.
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary"> <constructor-arg name="objectOne" ref="anotherExampleObject"/> <constructor-arg name="objectTwo" ref="yetAnotherObject"/> <constructor-arg name="IntegerProperty" value="1"/> </object> <object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/> <object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
[Visual Basic.NET] Public Class ExampleObject Private myObjectOne As AnotherObject Private myObjectTwo As YetAnotherObject Private i As Integer Public Sub New ( anotherObject as AnotherObject, yetAnotherObject as YetAnotherObject, i as Integer) myObjectOne = anotherObject myObjectTwo = yetAnotherObject Me.i = i End Sub End Class
Ghe constructor arguments specified in the object definition will be used to pass in as arguments to the constructor of the ExampleObject. Now consider a variant of this where instead of using a constructor, Spring is told to call a static factory method to return an instance of the object
<object id="exampleObject" type="Examples.ExampleFactoryMethodObject, ExamplesLibrary" factory-method="CreateInstance"> <constructor-arg name="objectOne" ref="anotherExampleObject"/> <constructor-arg name="objectTwo" ref="yetAnotherObject"/> <constructor-arg name="intProp" value="1"/> </object> <object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/> <object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
29
Arguments to the static factory method are supplied via <constructor-arg/> elements, exactly the same as if a constructor had actually been used. The type of the class being returned by the factory method does not have to be of the same type as the class which contains the static factory method, although in this example it is. An instance (non-static) factory method would be used in an essentially identical fashion (aside from the use of the factory-object attribute instead of the type attribute), so will not be detailed here. Note that Setter Injection and Constructor Injectionare not mutually exclusive. It is perfectly reasonable to use both for a single object definition, as can be seen in the following example:
<object id="exampleObject" type="Examples.MixedIocObject, ExamplesLibrary"> <constructor-arg name="objectOne" ref="anotherExampleObject"/> <property name="objectTwo" ref="yetAnotherObject"/> <property name="IntegerProperty" value="1"/> </object> <object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/> <object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
[C#] public class MixedIocObject { private AnotherObject objectOne; private YetAnotherObject objectTwo; private int i; public MixedIocObject (AnotherObject obj) { this.objectOne = obj; } public YetAnotherObject ObjectTwo { set { this.objectTwo = value; } } public int IntegerProperty { set { this.i = value; } } }
30
The IoC container configuration metadata supports sub-element types within its <property/> and <constructor-arg/> elements for this purpose. 5.3.2.1. Straight values (primitives, strings, and so on) The value attribute of the <property/> element specifies a property or constructor argument as a human-readable string representation. As mentioned previously, TypeConverter instances are used to convert these string values from a System.String to the actual property or argument type. In the following example, we use a SqlConnection from the System.Data.SqlClient namespace. This class (like many other existing classes) can easily configured by Spring as it offers a convenient public property for configuration of its ConnectionString property.
<objects xmlns="http://www.springframework.net"> <object id="myConnection" type="System.Data.SqlClient.SqlConnection"> <!-- results in a call to the setter of the ConnectionString property --> <property name="ConnectionString" value="Integrated Security=SSPI;database=northwind;server=mySQLServer"/> </object> </objects>
This above object definition snipped is exactly equivalent (at runtime) to the following snippit:
<object id="theTargetObject" type="..."> . . . </object> <object id="theClientObject" type="..."> <property name="targetName" value="theTargetObject"/> </object>
The first form is preferable to the second is that using the idref tag allows the container to validate at deployment time that the referenced, named object actually exists. In the second variation, no validation is performed on the value that is passed to the targetName property of the client object. Typos are only discovered (with ost mikely fatal results) when the 'client' object is actually instantiated. If the 'client' object is a prototype object, this typo and the resulting exception may only be discovered long after the container is deployed. Additionally, if the reference object is in the same XML unit, and the object name is the object id, you can use the local attribute which allows the XML parser itself to validate the object name earlier, at XML document parse time.
<property name="targetName"> <idref local="theTargetObject"/> </property>
31
The above configuration will result in the string " \n\r\t". Note, that you don't have to explicitely specifiy the 'xml' namespace on top of your configuration. 5.3.2.2. References to other objects (collaborators) The ref element is the final element allowed inside a <constructor-arg/> or <property/> definition element. Here you set the value of the specified property to be a reference to another object (a collaborator) managed by the container. The referenced object is a dependency of the object whose property will be set, and it is initialzed on demand as needed before the property is set. (If the collaborator is a singleton object it may be initialized already by the container.) All references are ultimately just a reference to another object. Scoping and validation depend on whether you specify the id/name of the object through the object, local, or parent attributes. Specifying the target object through the object attribute of the ref tag is the most general form, and allows creation of a reference to any object in the same container or parent container, regardless of whether it is in the same XML file. The value of the object attribute may be the same as the id attribute of the target object, or as one of the values in the name attribute of the target object.
<ref object="someObject"/>
Specifying the target object by using the local attribute leverages the ability of the XML parser to validate XML id references within the same file. The value of the local attribute must be the same as the id attribute of the target object. The XML parser will issue an error if no matching element is found in the same file. As such, using the local variant is the best choice (in order to know about errors are early as possible) if the target object is in the same XML file.
<ref local="someObject"/>
Specifying the target object through the parent attribute creates a reference to an object that is in a parent container of the current container. The value of the 'parent' attribute may be the same as either the 'id' attribute of the target object, or one of the values in the 'name' attribute of the target object, and the target object must be in a parent container to the current one. You use this object reference variant mainly when you have a hierarchy of containers and you want to wrap an existing object in a parent container with some sort of proxy which will have the same name as the parent object.
<!-- in the parent context --> <object id="AccountService" type="MyApp.SimpleAccountService, MyApp"> <!-- insert dependencies as required as here --> </object>
<!-- in the child (descendant) context --> <object id="AccountService" <-- notice that the name of this object is the same as the name of the 'parent' object type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="target"> <ref parent="AccountService"/> <-- notice how we refer to the parent object --> </property> <!-- insert other configuration and dependencies as required as here --> </object>
32
The IoC container 5.3.2.3. Inner objects An <object/> element inside the <constructor-arg/> or <property/> element defines so called inner object.
<object id="outer" type="..."> <!-- Instead of using a reference to target, just use an inner object --> <property name="target"> <object type="ExampleApp.Person, ExampleApp"> <property name="name" value="Tony"/> <property name="age" value="51"/> </object> </property> </object>
An inner object definition does not require a defined id or name; the container ignores these values. It also ignores the scope flag. Inner object are always anonymous and they are always scoped as prototypes. It is not possible to inject inner objects into collaborating objects other than into the enclosing object. 5.3.2.4. Setting collection values The list, set, name-values and dictionary elements allow properties and arguments of the type IList, ISet, NameValueCollection and IDictionary, respectively, to be defined and set.
<objects xmlns="http://www.springframework.net"> <object id="moreComplexObject" type="Example.ComplexObject"> <!-results in a call to the setter of the SomeList (System.Collections.IList) property --> <property name="SomeList"> <list> <value>a list element followed by a reference</value> <ref object="myConnection"/> </list> </property> <!-results in a call to the setter of the SomeDictionary (System.Collections.IDictionary) property --> <property name="SomeDictionary"> <dictionary> <entry key="a string => string entry" value="just some string"/> <entry key-ref="myKeyObject" value-ref="myConnection"/> </dictionary> </property> <!-results in a call to the setter of the SomeNameValue (System.Collections.NameValueCollection) property --> <property name="SomeNameValue"> <name-values> <add key="HarryPotter" value="The magic property"/> <add key="JerrySeinfeld" value="The funny (to Americans) property"/> </name-values> </property> <!-results in a call to the setter of the SomeSet (Spring.Collections.ISet) property --> <property name="someSet"> <set> <value>just some string</value> <ref object="myConnection"/> </set> </property> </object> </objects>
Many classes in the BCL expose only read-only properties for collection classes. When Spring.NET encounters a read-only collection, it will configure the collection by using the getter property to obtain a reference to the
33
The IoC container collection class and then proceed to add the additional elements to the existing collection. This results in an additive behavior for collection properties that are exposed in this manner. The value of a Dictionary entry, or a set value, can also again be any of the following elements:
(object | ref | idref | expression | list | set | dictionary | name-values | value | null)
The shortcut forms for value and references are useful to reduce XML verbosity when setting collection properties. See Section 5.3.2.9, Value and ref shortcut forms for more information. 5.3.2.5. Setting generic collection values Spring supports setting values for classes that expose properties based on the generic collection interfaces IList<T> and IDictionary<TKey, TValue>. The type parameter for these collections is specified by using the XML attribute element-type for IList<T> and the XML attributes key-type and valuetype for IDictionary<TKey, TValue>. The values of the collection are automaticaly converted from a string to the appropriate type. If you are using your own user-defined type as a generic type parameter you will likely need to register a custom type converter. Refer to Section 5.5, Type conversion for more information. The implementations of IList<T> and IDictionary<TKey, TValue> that is created are System.Collections.Generic.List and System.Collections.Generic.Dictionary. The following class represents a lottery ticket and demonstrates how to set the values of a generic IList.
public class LotteryTicket { List<int> list; DateTime date; public List<int> Numbers { set { list = value; } get { return list; } } public DateTime Date { get { return date; } set { date = value; } } }
The XML fragment that can be used to configure this class is shown below
<object id="MyLotteryTicket" type="GenericsPlay.Lottery.LotteryTicket, GenericsPlay"> <property name="Numbers"> <list element-type="int"> <value>11</value> <value>21</value> <value>23</value> <value>34</value> <value>36</value> <value>38</value> </list> </property> <property name="Date" value="4/16/2006"/> </object>
The following shows the definition of a more complex class that demonstrates the use of generics using the Spring.Expressions.IExpression interface as the generic type parameter for the IList element-type and the value-type for IDictionary. Spring.Expressions.IExpression has an associated type converter, Spring.Objects.TypeConverters.ExpressionConverter that is already pre-registered with Spring.
public class GenericExpressionHolder
34
5.3.2.6. Collection Merging As of Spring 1.3, the container supports the merging of collections. An application developer can define a parentstyle <list/>, <dictionary/>, <set/> or <name-value/> element, and have child-style <list/>, <dictionary/ >, <set/> or <name-value/> elements inherit and override values from the parent collection. That is, the child collection's values are the result of merging the elements of the parent and child collections, with the child's collection elements overriding values specified in the parent collection. This section on merging discusses the parent-child object mechanism. Readers unfamiliar with parent and child object definitions may wish to read the relevant section before continuing.
35
Notice the use of the merge=true attribute on the <name-values/> element of the AdminEmails property of the child object definition. When the child object is resolved and instantiated by the container, the resulting instance has an AdminEmails Properties collection that contains the result of the merging of the child's AdminEmails collection with the parent's AdminEmails collection.
[email protected] [email protected] [email protected]
The child Properties collection's value set inherits all property elements from the parent <name-values/>, and the child's value for the support value overrides the value in the parent collection. This merging behavior applies similarly to the <list/>, <dictionary/>, and <set/> collection types. In the specific case of the <list/> element, the semantics associated with the IList collection type, that is, the notion of an ordered collection of values, is maintained; the parent's values precede all of the child list's values. In the case of the IDictionary, ISet, and NameValue collection types, no ordering exists. Hence no ordering semantics are in effect for the collection types that underlie the associated IDictionary, ISet, and NameValueCollection implementation types that the container uses internally. 5.3.2.7. Null and empty values Spring treats empty arguments for properties and the like as empty Strings. The following XML-based configuration metadata snippet sets the email property to the empty String value ("")
<object type="Examples.ExampleObject, ExamplesLibrary"> <property name="email" value=""/> </object>
This results in the email property being set to the empty string value (""), in much the same way as can be seen in the following snippet of C# code
exampleObject.Email = "";
This results in the email property being set to null, again in much the same way as can be seen in the following snippet of C# code:
36
5.3.2.8. Setting indexer properties An indexer lets you set and get values from a collection using a familiar bracket [] notation. Spring's XML configuration supports the setting of indexer properties. Overloaded indexers as well as multiparameter indexers are also supported. The property expression parser described in Chapter 11, Expression Evaluation is used to perform the type conversion of the indexer name argument from a string in the XML file to a matching target type. As an example consider the following class
public class Person { private IList favoriteNames = new ArrayList(); private IDictionary properties = new Hashtable(); public Person() { favoriteNames.Add("p1"); favoriteNames.Add("p2"); } public string this[int index] { get { return (string) favoriteNames[index]; } set { favoriteNames[index] = value; } } public string this[string keyName] { get { return (string) properties[keyName]; } set { properties.Add(keyName, value); } } }
The XML configuration snippet to populate this object with data is shown below
<object id="person" type="Test.Objects.Person, Test.Objects"> <property name="[0]" value="Master Shake"/> <property name="['one']" value="uno"/> </object>
Note
The use of the property expression parser in Release 1.0.2 changed how you configure indexer properties. The following section describes this usage. The older style configuration uses the following syntax
<object id="objectWithIndexer" type="Spring.Objects.TestObject, Spring.Core.Tests"> <property name="Item[0]" value="my string value"/> </object>
You can also change the name used to identify the indexer by adorning your indexer method declaration with the attribute [IndexerName("MyItemName")]. You would then use the string MyItemName[0] to configure the first element of that indexer. There are some limitations to be aware in the older indexer configuration. The indexer can only be of a single parameter that is convertible from a string to the indexer parameter type. Also, multiple indexers are not supported. You can get around that last limitation currently if you use the IndexerName attribute.
37
The IoC container 5.3.2.9. Value and ref shortcut forms Spring XML used to be even more verbose. What is now popular usage is actually the shortcut from of the original way to specify values and references. There are also some shortcut forms that are less verbose than using the full value and ref elements. The property, constructor-arg, and entry elements all support a value attribute which may be used instead of embedding a full value element. Therefore, the following:
<property name="myProperty"> <value>hello</value> </property> <constructor-arg> <value>hello</value> </constructor-arg> <entry key="myKey"> <value>hello</value> </entry>
In general, when typing definitions by hand, you will probably prefer to use the less verbose shortcut form. The property and constructor-arg elements support a similar shortcut ref attribute which may be used instead of a full nested ref element. Therefore, the following...
<property name="myProperty"> <ref object="anotherObject"/> </property> <constructor-arg index="0"> <ref object="anotherObject"/> </constructor-arg>
is equivalent to...
<property name="myProperty" ref="anotherObject"/> <constructor-arg index="0" ref="anotherObject"/>
Note
The shortcut form is equivalent to a <ref object="xxx"> element; there is no shortcut for either the <ref local="xxx"> or <ref parent="xxx"> elements. For a local or parent ref, you must still use the long form. Finally, the entry element allows a shortcut form the specify the key and/or value of a dictionary, in the form of key/key-ref and value/value-ref attributes. Therefore, the following
<entry> <key> <ref object="MyKeyObject"/> </key> <ref object="MyValueObject"/> </entry>
38
As mentioned previously, the equivalence is to <ref object="xxx"> and not the local or parent forms of object references. 5.3.2.10. Compound property names and Spring expression references You can use compound or nested property names when you set object properties. Property names are interpreted using the Spring Expression Language (SpEL) and therefore can leverage its many features to set property names. For example, in this object definition a simple nested property name is configured
<object id="foo" type="Spring.Foo, Spring.Foo"> <property name="bar.baz.name" value="Bingo"/> </object>
As an example of some alternative ways to declare the property name, you can use SpEL's support for indexers to configure a Dictionary key value pair as an alternative to the nested <dictionary> element. More importantly, you can use the 'expression' element to refer to a Spring expression as the value of the property. Simple examples of this are shown below
<property name=minValue expression=int.MinValue /> <property name=weekFromToday expression="DateTime.Today + 7"/>
Using SpEL's support for method evaluation, you can easily call static method on various helper classes in your XML configuraiton.
In use, .NET events are combined with one or more event handler methods. Each handler method is programmatically added, or removed, from the event and corresponds to an object's method that should be invoked when a particular event occurs. When more than one handler method is added to an event, then each of the registered methods will be invoked in turn when an event occurs.
TestObject source = new TestObject(); TestEventHandler eventListener1 = new TestEventHandler(); TestEventHandler eventListener2 = new TestEventHandler(); source.Click += eventListener1.HandleEvent; // Adding the first event handler method to the event source.Click += eventListener2.HandleEvent; // Adding a second event handler method to the event source.OnClick(); // First eventListener1.HandleEvent is invoked, then eventListener2.HandleEvent
39
One of the not so nice things about using events is that, without employing late binding, you declare the objects that are registered with a particular event programmatically. Spring .NET offers a way to declaratively register your handler methods with particular events using the <listener> element inside your <object> elements. 5.3.3.1. Declarative event handlers Rather than having to specifically declare in your code that you are adding a method to be invoked on an event, using the <listener> element you can register a plain object's methods with the corresponding event declaratively in your application configuration. Using the listener element you can: Configure a method to be invoked when an event is fired. Register a collection of handler methods based on a regular expression. Register a handler method against an event name that contains a regular expression. 5.3.3.2. Configuring a method to be invoked when an event is fired The same event registration in the example above can be achieved using configuration using the <listener> element.
<object id="eventListener1" type="SpringdotNETEventsExample.TestEventHandler, SpringdotNETEventsExample"> <!-- wired up to an event exposed on an instance --> <listener event="Click" method="HandleEvent"> <ref object="source"/> </listener> </object> <object id="eventListener2" type="SpringdotNETEventsExample.TestEventHandler, SpringdotNETEventsExample"> <!-- wired up to an event exposed on an instance --> <listener event="Click" method="HandleEvent"> <ref object="source"/> </listener> </object>
In this case the two different objects will have their HandleEvent method invoked, as indicated explicitly using the method attribute, when a Click event, as specified by the event attribute, is triggered on the object referred to by the ref element. 5.3.3.3. Registering a collection of handler methods based on a regular expression Regular expressions can be employed to wire up more than one handler method to an object that contains one or more events.
<object id="eventListener" type="SpringdotNETEventsExample.TestEventHandler, SpringdotNETEventsExample"> <listener method="Handle.+"> <ref object="source"/> </listener> </object>
Here all the eventListener's handler methods that begin with 'Handle', and that have the corresponding two parameters of a System.Object and a System.EventArgs, will be registered against all events exposed by the source object. You can also use the name of the event in regular expression to filter your handler methods based on the type of event triggered.
40
5.3.3.4. Registering a handler method against an event name that contains a regular expression Finally, you can register an object's handler methods against a selection of events, filtering based on their name using a regular expression.
<object id="eventListener" type="SpringdotNETEventsExample.TestEventHandler, SpringdotNETEventsExample"> <listener method="HandleEvent" event="Cl.+"> <ref object="source"/> </listener> </object>
In this example the eventListener's HandleEvent handler method will be invoked for any event that begins with 'Cl'.
To express a dependency on multiple objects, supply a list of object names as the value of the 'depends-on' attribute, with commas, whitespace and semicolons used as valid delimiters:
<object id="objectOne" type="Examples.ExampleObject, ExamplesLibrary" depends-on="manager,accountDao"> <property name="manager" ref="manager" /> </object> <object id="manager" type="Examples.ManagerObject, ExamplesLibrary" /> <object id="accountDao" type="Examples.AdoAccountDao, ExamplesLibrary" />
Note
The depends-on attribute in the object definition can specify both an initialization time dependency and, in the case of a singleton object only, a corresponding destroy time dependency. Dependent objects that define a depends-on relationship with a given object are destroyed first, prior to the given object itself being destroyed. Thus depends-on can also control shutdown order.
41
The IoC container surrounding environment are discovered immediately, as opposed to hours or even days later. When this behavior is not desirable, you can prevent pre-instantiation of a singleton object by marking the object definition as lazyinitialized. A lazy-initialized object tells the IoC container to create an object instance when it is first requested, rather than at startup. In XML, this behavior is controlled by the 'lazy-init'attribute on the <object/> element; for example:
<object id="lazy" type="MyCompany.ExpensiveToCreateObject, MyApp" lazy-init="true"/> <object name="not.lazy" type="MyCompany.AnotherObject, MyApp"/>
When the preceding configuration is consumed by an IApplicationContext, the object named lazy is not eagerly pre-instantiated when the IApplicationContext is starting up, whereas the not.lazy object is eagerly preinstantiated. However, when a lazy-initialized object is a dependency of a singleton object that is not lazy-initialized, the IApplicationContext creates the lazy-initialized object at startup, because it must satisfy the singleton's dependencies. The lazy-initialized object is injected into a singleton object elsewhere that is not lazy-initialized. You can also control lazy-initialization at the container level by using the default-lazy-init attribute on the <objects/> element; for example:
<objects default-lazy-init="true"> <!-- no objects will be pre-instantiated... --> </objects>
byName
byType
42
The IoC container Mode Explanation definitions of the desired type exist in the container, a failure will be reported and you won't be able to use autowiring for that specific object. constructor This is analogous to byType, but applies to constructor arguments. If there isn't exactly one object of the constructor argument type in the object factory, a fatal error is raised. Chooses constructor or byType through introspection of the object class. If a default constructor is found, byType gets applied.
autodetect
Note that explicit dependencies in property and constructor-arg settings always override autowiring. Please also note that it is not currently possible to autowire so-called simple properties such as primitives, Strings, and Types (and arrays of such simple properties). (This is by-design and should be considered a feature.) When using either the byType or constructor autowiring mode, it is possible to wire arrays and typed-collections. In such cases all autowire candidates within the container that match the expected type will be provided to satisfy the dependency. Strongly-typed IDictionaries can even be autowired if the expected key type is string. An autowired IDictionary values will consist of all object instances that match the expected type, and the IDictionary's keys will contain the corresponding object names. Autowire behavior can be combined with dependency checking, which will be performed after all autowiring has been completed. It is important to understand the various advantages and disadvantages of autowiring. Some advantages of autowiring include: Autowiring can significantly reduce the volume of configuration required. However, mechanisms such as the use of a object template (discussed elsewhere in this chapter) are also valuable in this regard. Autowiring can cause configuration to keep itself up to date as your objects evolve. For example, if you need to add an additional dependency to a class, that dependency can be satisfied automatically without the need to modify configuration. Thus there may be a strong case for autowiring during development, without ruling out the option of switching to explicit wiring when the code base becomes more stable. Some disadvantages of autowiring: Autowiring is more magical than explicit wiring. Although, as noted in the above table, Spring is careful to avoid guessing in case of ambiguity which might have unexpected results, the relationships between your Spring-managed objects are no longer documented explicitly. Wiring information may not be available to tools that may generate documentation from a Spring container. Another issue to consider when autowiring by type is that multiple object definitions within the container may match the type specified by the setter method or constructor argument to be autowired. For arrays, collections, or IDictionary, this is not necessarily a problem. However for dependencies that expect a single value, this ambiguity will not be arbitrarily resolved. Instead, if no unique object definition is available, an Exception will be thrown. When deciding whether to use autowiring, there is no wrong or right answer in all cases. A degree of consistency across a project is best though; for example, if autowiring is not used in general, it might be confusing to developers to use it just to wire one or two object definitions.
43
The IoC container This feature useful when you want to ensure that all properties (or all properties of a certain type) are set on an object. An object often has default values for many properties, or some properties do not apply to all usage scenarios, so this feature is of limited use. You can enable dependency checking per object, just as with the autowiring functionality. The default is not not check dependencies. In XML-based configuration metadata, you specify dependency checking via the dependency-check attribute in an object definition, which may have the following values. Table 5.4. Dependency checking modes Mode none Explanation (Default) No dependency checking. Properties of the object which have no value specified for them are simply not set. Dependency checking for primitive types and collections (everything except collaborators). Dependency checking for collaborators only. Dependency checking for collaborators, primitive types and collections.
44
The preceding is not desirable, because the business code is aware of and coupled to the Sring Framework. Method Injection, a somewhat advanced feature of the Spring IoC container, allows this use case to be handled in a clean fashion. 5.3.8.1. Lookup Method Injection Lookup method injection is the ability of the container to override methods on container managed objects, to return the result of looking up another named object in the container. The lookup typically involves a prototype object as in the scenario described in the preceding section. The Spring framework implements this method injection by a dynamically generating a subclass overriding the method using the classes in the System.Reflection.Emit namespace.
Note
You can read more about the motivation for Method Injection in this blog entry [http:// blog.springframework.com/rod/?p=1]. Looking at the CommandManager class in the previous code snippit, you see that the Spring container will dynamically override the implementation of the CreateCommand() method. Your CommandManager class will not have any Spring dependencies, as can be seen in this reworked example below:
using System.Collections; namespace Fiona.Apple { public abstract class CommandManager { public object Process(IDictionary commandState) { Command command = CreateCommand(); command.State = commandState; return command.Execute(); } // okay... but where is the implementation of this method? protected abstract Command CreateCommand(); } }
In the client class containing the method to be injected (the CommandManager in this case) the method to be injected requires a signature of the following form:
<public|protected> [abstract] <return-type> TheMethodName(no-arguments);
If the method is abstract, the dynamically-generated subclass implements the method. Otherwise, the dynamically-generated subclass overrides the concrete method defined in the original class. Let's look at an example:
<!-- a stateful object deployed as a prototype (non-singleton) --> <object id="command" class="Fiona.Apple.AsyncCommand, Fiona" singleton="false"> <!-- inject dependencies here as required --> </object> <!-- commandProcessor uses a statefulCommandHelpder --> <object id="commandManager" type="Fiona.Apple.CommandManager, Fiona"> <lookup-method name="CreateCommand" object="command"/>
45
The object identified as commandManager will calls its own method CreateCommand whenever it needs a new instance of the command object. You must be careful to deploy the command object as prototype, if that is actually what is needed. If it is deployed as a singleton the same instance of singleShotHelper will be returned each time. Note that lookup method injection can be combined with Constructor Injection (supplying optional constructor arguments to the object being constructed), and also with Setter Injection (settings properties on the object being constructed). 5.3.8.2. Arbitrary method replacement A less commonly useful form of method injection than Lookup Method Injection is the ability to replace arbitrary methods in a managed object with another method implementation. With XML-based configuration metadata, you can use the replaced-method element to replace an existing method implementation with another, for a deployed object. Consider the following class, with a method ComputeValue, which we want to override:
public class MyValueCalculator { public virtual string ComputeValue(string input) { // ... some real code } // ... some other methods }
A class implementing the Spring.Objects.Factory.Support.IMethodReplacer interface is needed to provide the new (injected) method definition.
/// <summary> /// Meant to be used to override the existing ComputeValue(string) /// implementation in MyValueCalculator. /// </summary> public class ReplacementComputeValue : IMethodReplacer { public object Implement(object target, MethodInfo method, object[] arguments) { // get the input value, work with it, and return a computed result... string value = (string) arguments[0]; // compute... return result; } }
The object definition to deploy the original class and specify the method override would look like this:
<object id="myValueCalculator" type="Examples.MyValueCalculator, ExampleAssembly"> <!-- arbitrary method replacement --> <replaced-method name="ComputeValue" replacer="replacementComputeValue"> <arg-type match="String"/> </replaced-method> </object> <object id="replacementComputeValue" type="Examples.ReplacementComputeValue, ExampleAssembly"/>
You can use one or more contained arg-type elements within the replaced-method element to indicate the method signature of the method being overridden. The signature for the arguments is necessaryonly if the method is overloaded and multiple variants exist within the class. For convenience, the type string for an argument may be a substring of the fully qualified type name. For example, the following all match System.String.
System.String
46
Because the number of arguments is often enough to distinguish between each possible choice, this shortcut can save a lot of typing, by allowing you to typ just the shortest string which will match an argument type.
5.3.9. Setting a reference using the members of other objects and classes.
This section details those configuration scenarios that involve the setting of properties and constructor arguments using the members of other objects and classes. This kind of scenario is quite common, especially when dealing with legacy classes that you cannot (or won't) change to accommodate some of Spring.NET's conventions... consider the case of a class that has a constructor argument that can only be calculated by going to say, a database. The MethodInvokingFactoryObject handles exactly this scenario ... it will allow you to inject the result of an arbitrary method invocation into a constructor (as an argument) or as the value of a property setter. Similarly, PropertyRetrievingFactoryObject and FieldRetrievingFactoryObject allow you to retrieve values from another object's property or field value. These classes implement the IFactoryObject interface which indicates to Spring.NET that this object is itself a factory and the factories product, not the factory itself, is what will be associated with the object id. Factory objects are discussed further in Section 5.9.3, Customizing instantiation logic using IFactoryObjects 5.3.9.1. Setting a reference to the value of property. The PropertyRetrievingFactoryObject is an IFactoryObject that addresses the scenario of setting one of the properties and / or constructor arguments of an object to the value of a property exposed on another object or class. One can use it to get the value of any public property exposed on either an instance or a class (in the case of a property exposed on a class, the property must obviously be static). In the case of a property exposed on an instance, the target object that a PropertyRetrievingFactoryObject will evaluate can be either an object instance specified directly inline or a reference to another arbitrary object. In the case of a static property exposed on a class, the target object will be the class (the .NET System.Type) exposing the property. The result of evaluating the property lookup may then be used in another object definition as a property value or constructor argument. Note that nested properties are supported for both instance and class property lookups. The IFactoryObject is discussed more generally in Section 5.9.3, Customizing instantiation logic using IFactoryObjects. Here's an example where a property path is used against another object instance. In this case, an inner object definition is used and the property path is nested, i.e. spouse.age.
<object name="person" type="Spring.Objects.TestObject, Spring.Core.Tests"> <property name="age" value="20"/> <property name="spouse"> <object type="Spring.Objects.TestObject, Spring.Core.Tests"> <property name="age" value="21"/> </object> </property> </object> // will result in 21, which is the value of property 'spouse.age' of object 'person' <object name="theAge" type="Spring.Objects.Factory.Config.PropertyRetrievingFactoryObject, Spring.Core"> <property name="TargetObject" ref="person"/> <property name="TargetProperty" value="spouse.age"/> </object>
47
5.3.9.2. Setting a reference to the value of field. The FieldRetrievingFactoryObject class addresses much the same area of concern as the PropertyRetrievingFactoryObject described in the previous section. However, as its name might suggest, the FieldRetrievingFactoryObject class is concerned with looking up the value of a public field exposed on either an instance or a class (and similarly, in the case of a field exposed on a class, the field must obviously be static). The following example demonstrates using a FieldRetrievingFactoryObject to look up the value of a (public, static) field exposed on a class
<object id="withTypesField" type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests"> <property name="Types" ref="emptyTypesFactory"/> </object> <object id="emptyTypesFactory" type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core"> <property name="TargetType" value="System.Type, Mscorlib"/> <property name="TargetField" value="EmPTytypeS"/> </object>
The example in the next section demonstrates the look up of a (public) field exposed on an object instance.
<object id="instanceCultureAware" type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests"> <property name="Culture" ref="instanceCultureFactory"/> </object> <object id="instanceCultureFactory" type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core"> <property name="TargetObject" ref="instanceCultureAwareSource"/> <property name="TargetField" value="Default"/> </object> <object id="instanceCultureAwareSource" type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests"/>
48
The IoC container 5.3.9.3. Setting a property or constructor argument to the return value of a method invocation. The MethodInvokingFactoryObject rounds out the trio of classes that permit the setting of properties and constructor arguments using the members of other objects and classes. Whereas the PropertyRetrievingFactoryObject and FieldRetrievingFactoryObject classes dealt with simply looking up and returning the value of property or field on an object or class, the MethodInvokingFactoryObject allows one to set a constructor or property to the return value of an arbitrary method invocation, The MethodInvokingFactoryObject class handles both the case of invoking an (instance) method on another object in the container, and the case of a static method call on an arbitrary class. Additionally, it is sometimes necessary to invoke a method just to perform some sort of initialization.... while the mechanisms for handling object initialization have yet to be introduced (see Section 5.6.1.1, IInitializingObject / initmethod), these mechanisms do not permit any arguments to be passed to any initialization method, and are confined to invoking an initialization method on the object that has just been instantiated by the container. The MethodInvokingFactoryObject allows one to invoke pretty much any method on any object (or class in the case of a static method). The following example (in an XML based IObjectFactory definition) uses the MethodInvokingFactoryObject class to force a call to a static factory method prior to the instantiation of the object...
<object id="force-init" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core"> <property name="StaticMethod"> <value>ExampleNamespace.ExampleInitializerClass.Initialize</value> </property> </object> <object id="myService" depends-on="force-init"/>
Note that the definition for the myService object has used the depends-on attribute to refer to the force-init object, which will force the initialization of the force-init object first (and thus the calling of its configured StaticMethod static initializer method, when myService is first initialized. Please note that in order to effect this initialization, the MethodInvokingFactoryObject object must be operating in singleton mode (the default.. see the next paragraph). Note that since this class is expected to be used primarily for accessing factory methods, this factory defaults to operating in singleton mode. As such, as soon as all of the properties for a MethodInvokingFactoryObject object have been set, and if the MethodInvokingFactoryObject object is still in singleton mode, the method will be invoked immediately and the return value cached for later access. The first request by the container for the factory to produce an object will cause the factory to return the cached return value for the current request (and all subsequent requests). The IsSingleton property may be set to false, to cause this factory to invoke the target method each time it is asked for an object (in this case there is obviously no caching of the return value). A static target method may be specified by setting the targetMethod property to a string representing the static method name, with TargetType specifying the Type that the static method is defined on. Alternatively, a target instance method may be specified, by setting the TargetObject property to the name of another Spring.NET managed object definition (the target object), and the TargetMethod property to the name of the method to call on that target object. Arguments for the method invocation may be specified in two ways (or even a mixture of both)... the first involves setting the Arguments property to the list of arguments for the method that is to be invoked. Note that the ordering of these arguments is significant... the order of the values passed to the Arguments property must be the same as the order of the arguments defined on the method signature, including the argument Type. This is shown in the example below
49
The second way involves passing an arguments dictionary to the NamedArguments property... this dictionary maps argument names (Strings) to argument values (any object). The argument names are not case-sensitive, and order is (obviously) not significant (since dictionaries by definition do not have an order). This is shown in the example below
<object id="myObject" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core"> <property name="TargetObject"> <object type="Whatever.MyClassFactory, MyAssembly"/> </property> <property name="TargetMethod" value="Execute"/> <!-- the ordering of named arguments is not significant --> <property name="NamedArguments"> <dictionary> <entry key="argumentName"><value>1st</value></entry> <entry key="finalArgumentName"><value>and 3rd arguments</value></entry> <entry key="anotherArgumentName"><value>2nd</value></entry> </dictionary> </property> </object>
The following example shows how use MethodInvokingFactoryObject to call an instance method.
<object id="myMethodObject" type="Whatever.MyClassFactory, MyAssembly" /> <object id="myObject" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core"> <property name="TargetObject" ref="myMethodObject"/> <property name="TargetMethod" value="Execute"/> </object>
The above example could also have been written using an anonymous inner object definition... if the object on which the method is to be invoked is not going to be used outside of the factory object definition, then this is the preferred idiom because it limits the scope of the object on which the method is to be invoked to the surrounding factory object. Finally, if you want to use MethodInvokingFactoryObject in conjunction with a method that has a variable length argument list, then please note that the variable arguments need to be passed (and configured) as a list. Let us consider the following method definition that uses the params keyword (in C#), and its attendant (XML) configuration...
[C#] public class MyClassFactory { public object CreateObject(Type objectType, params string[] arguments) { return ... // implementation elided for clarity... } }
50
and
Spring.NET comes with other useful implementations of the IFactoryObject interface. These are discussed below.
FieldRetrievingFactoryObject
5.3.10.1. Common logging The LogFactoryObject is useful when you would like to share a Common.Logging log object across a number of classes instead of creating a logging instance per class or class hierarchy. Information on the Common.Logging project can be found here [http://netcommon.sourceforge.net/]. In the example shown below the same logging instance, with a logging category name of "DAOLogger", is used in both the SimpleAccountDao and SimpleProductDao data access objects.
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd" > <object name="daoLogger" type="Spring.Objects.Factory.Config.LogFactoryObject, Spring.Core"> <property name="logName" value="DAOLogger"/> </object> <object name="productDao" type="PropPlayApp.SimpleProductDao, PropPlayApp "> <property name="maxResults" value="100"/> <property name="dbConnection" ref="myConnection"/> <property name="log" ref="daoLogger"/> </object> <object name="accountDao" type="PropPlayApp.SimpleAccountDao, PropPlayApp "> <property name="maxResults" value="100"/> <property name="dbConnection" ref="myConnection"/> <property name="log" ref="daoLogger"/> </object> <object name="myConnection" type="System.Data.Odbc.OdbcConnection, System.Data"> <property name="connectionstring" value="dsn=MyDSN;uid=sa;pwd=myPassword;"/> </object> </objects>
51
The IoC container definition. This approach powerful and flexible in that you can choose the scope of the objects you create through configuration instead of having to bake in the scope of an object at the .NET class level. Ob jects can be defined to be deployed in one of a number of scopes: out of the box, the Spring Framework supports five scopes, three of which are available only if you use a web-aware IApplicationContext. The following scopes supported. Support for user defined custom scopes is planned for Spring .NET 2.0. Table 5.5. Object Scopes Scope singleton Description Scopes a single object definition to a single object instance per Spring IoC container. Scopes a single object definition to any number of object instances. Scopes a single object definition to the lifecycle of a single HTTP request; that is, each and every HTTP request has its own instance of an object created off the back of a single object definition. Only valid in the context of a web-aware Spring ApplicationContext. Scopes a single object definition to the lifecycle of a HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext. Scopes a single object definition to the lifecycle of a web application. Only valid in the context of a web-aware Spring ApplicationContext.
prototype request
session
application
52
The IoC container through a GetObject() method call on the container. As a rule use the prototype scope for all objects that are stateful and the singleton scope for stateless objects. The following examples defines an object as a prototype in XML:
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary" scope="prototype"/>
Note
The <singleton/> attribute was introduced Spring 1.0 as there were only two types of scopes, singleton and prototype. The element singleton=true refers to singleton scope and singleton=false refers to prototype scope. In Spring 1.1 the additional web scopes were introduced along with the new elment 'scope'. The scope element is the preferred element to use. In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype object: the container instantiates, configures, decorates and otherwise assembles a prototype object, hands it to the client, with no further record of that prototype instance. Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped objects and release any expensive resources that the prototype object(s) are holding. To get the Spring container to release resources held by prototype-scoped objects, try using a custom object post processor which would hold a reference to the objects that need to be cleaned up. In some respects, the Spring container's role in regard to a prototype-scoped object is a replacement for the C# 'new' operator. All lifecycle management past that point must be handled by the client. (For details on the lifecycle of an object in the Spring container, see Section 5.6.1, Lifecycle interfaces.
53
The IoC container rely on the standard .NET support for type conversion unless an alternative TypeConverter is registered for a given type. How to register custom TypeConverters will be described shortly. As a reminder, the standard .NET type converter support works by associating a TypeConverter attribute with the class definition by passing the type of the converter as an attribute argument. 3 For example, an abbreviated class definition for the BCL type Font is shown below.
[Serializable, TypeConverter(typeof(FontConverter)), ...] public sealed class Font : MarshalByRefObject, ICloneable, ISerializable, IDisposable { // Methods ... etc .. }
Explanation Parses strings representing System.Types to actual System.Types and the other way around. Capable of resolving strings to a System.IO.FileInfo object. Capable of resolving a comma-delimited list of strings to a stringarray and vice versa. Capable of resolving a string representation of a Uri to an actual Uriobject. Capable of resolving a string representation of a credential for Web client authentication into an instance of System.Net.ICredentials Capable of resolving Spring IResource Uri (string) to its corresponding InputStream-object. Capable of resolving Spring IResource Uri (string) to an IResource object.
FileInfoConverter StringArrayConverter
UriConverter
CredentialConverter
StreamConverter
ResourceConverter
More information about creating custom TypeConverter implementations can be found online at Microsoft's MSDN website, by searching for Implementing a Type Converter.
54
Explanation Capable of resolving a two part string (resource name, assembly name) to a System.Resources.ResourceManager object. Capable of resolving a comma separated list of Red, Green, Blue integer values to a System.Drawing.Color structure. Capable of resolving a string into an instance of an object that implements the IExpression interface. Capable of resolving an XML formatted string to a
RgbColorConverter
ExpressionConverter
NameValueConverter
Capable of resolving a string into an instance of Regex Capable of resolving a string into a Microsoft.Win32.RegistryKey object.
Spring.NET uses the standard .NET mechanisms for the resolution of System.Types, including, but not limited to checking any configuration files associated with your application, checking the Global Assembly Cache (GAC), and assembly probing.
then
RegisterCustomConverter(Type ConfigurableObjectFactory
requiredType,
you
the the
5.5.3.1. Using CustomConverterConfigurer This section shows in detail how to define a custom type converter that does not use the .NET TypeConverter attribute. The type converter class is standalone and inherits from the TypeConverter class. It uses the legacy factory post-processor approach. Consider a user class ExoticType, and another class DependsOnExoticType which needs ExoticType set as a property:
public class ExoticType { private string name; public ExoticType(string name) { this.name = name; }
55
and
public class DependsOnExoticType { public DependsOnExoticType() {} private ExoticType exoticType; public ExoticType ExoticType { get { return this.exoticType; } set { this.exoticType = value; } } public override string ToString() { return exoticType.Name; } }
When things are properly set up, we want to be able to assign the type property as a string, which a TypeConverter will convert into a real ExoticType object behind the scenes:
<object name="sample" type="SimpleApp.DependsOnExoticType, SimpleApp"> <property name="exoticType" value="aNameForExoticType"/> </object>
Finally, we use the CustomConverterConfigurer to register the new TypeConverter with the IApplicationContext, which will then be able to use it as needed:
<object id="customConverterConfigurer" type="Spring.Objects.Factory.Config.CustomConverterConfigurer, Spring.Core"> <property name="CustomConverters">
56
57
... but does not couple the code to Spring.NET. 5.6.1.2. IDisposable / destroy-method Implementing the System.IDisposable interface allows an object to get a callback callback when the container containing it is destroyed. The IDisposable interface specifies a single method: void Dispose(): and is called on destruction of the container. This allows you to release any resources you are keeping in this object (such as database connections). You can throw any Exception here... however, any such Exception will not stop the destruction of the container - it will only get logged.v Since the IDisposable interface resides in the core .NET library, it does not couple your class to Spring as in the case with the IInitializingObject interface. However, you may also specify a destruction method that is not tied to the IDisposable interface. In the case of XML-based configuration metadata, you use the destroy-method attribute to specify the name of the method that has a void no-argument signature. For example, the following definition:
<object id="exampleInitObject" type="Examples.ExampleObject" destroy-method="cleanup"/> [C#] public class ExampleObject { public void cleanup() { // do some destruction work (such as closing any open connection (s)) } }
Thus objects can manipulate programmatically the IApplicationContext that created them, through the IApplicationContext interface, or by casting the reference to a known subclass of this interface, such as IConfigurableApplicationContext, which exposes additional functionality. One use would be the programmatic retrieval of other objects. Sometimes this capability is useful; however, in general you should avoid it, because it couples the code to Spring and does not follow the Inversion of Control style, where collaborators are provided to objects as properties. Other methods of the IApplicationContext provide access to file resources, publishing application events, and accessing a IMessageSource. These additional features are described in Section 5.10, The IApplicationContext
58
The IoC container 5.6.2.1. IObjectNameAware When an IApplicationContext creates a class that implements the Spring.Objects.Factory.IObjectNameAware interface, the class is provided with a reference to the name defined in its associated object definition.
public interface IObjectNameAware { string ObjectName { set; } }
The callback is invoked after population of normal object properties but before an initialization callback such as IInitializingObject 's AfterPropertiesSet method or a custom initalization method is invoked.
A child object definition uses the object class from the parent definition if none is specified, but can also override it. In the latter case, the child object class must be compatible with the parent, that is, it must accept the parent's property values. A child object definition inherits constructor argument values, property values and method overrides from the parent, with the option to add new values. Any initialization method, destroy method and/or static factory methods that you specify will override the corresponding parent settings. The remaining settings are always be taken from the child definition: depends on, autowire mode, dependency check, singleton, lazy init. The preceding example explicitly marks the parent object definition as abstract using the abstract attribute. If the parent definition does not specify a class, explicitly marking the parent object definition as abstract is required, as follows:
<object id="inheritedTestObjectWithoutClass" abstract="true"> <property name="name" value="parent"/> <property name="age" value="1"/> </object> <object id="inheritsWithClass" type="Spring.Objects.DerivedTestObject, Spring.Core.Tests"
59
The parent object cannot be instantiated on its own since it incomplete, and it is also explicitly marked as abstract. When a definition is abstract like this, it is usable only as a pure template object definition that serves as a parent definition for child definitions. Trying to use such an abstract parent object on its own, by referring to it as a ref property of another object, or doing an explicit GetObject() with the parent object id, returns an error. The container's internal PreInstantiateSingletons method will completely ignore object definitions that are considered abstract.
Note
Application contexts pre-instantiate all singletons by default. Therefore it is important (at least for singleton objects) that if you have a (parent) object definition which you intend to use only as a template, and this definition specifies a class, you must make sure to set the abstract attribute to true , otherwise the application context will actually (attempt to) pre-instantiate the abstract object.
That is pretty much it. Using GetObject(string) (or the more concise indexer method factory ["string"]) you can retrieve instances of your objects...
[C#] object foo = factory.GetObject ("foo"); // gets the object defined as 'foo' object bar = factory ["bar"]; // same thing, just using the indexer
You'll get a reference to the same object if you defined it as a singleton (the default) or you'll get a new instance each time if you set the singleton property of your object definition to false.
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary"/> <object id="anotherObject" type="Examples.ExampleObject, ExamplesLibrary" singleton="false"/>
[C#] object one = factory ["exampleObject"]; object two = factory ["exampleObject"]; Console.WriteLine (one == two) object three = factory ["anotherObject"]; object four = factory ["anotherObject"]; Console.WriteLine (three == four);
// gets the object defined as 'exampleObject' // prints 'true' // gets the object defined as 'anotherObject' // prints 'false'
The client-side view of the IObjectFactory is surprisingly simple. The IObjectFactory interface has only seven methods (and the aforementioned indexer) for clients to call: bool ContainsObject(string): returns true if the IObjectFactory contains an object definition that matches the given name.
60
The IoC container object GetObject(string): returns an instance of the object registered under the given name. Depending on how the object was configured by the IObjectFactory configuration, either a singleton (and thus shared) instance or a newly created object will be returned. An ObjectsException will be thrown when either the object could not be found (in which case it'll be a NoSuchObjectDefinitionException), or an exception occurred while instantiated and preparing the object. Object this [string]: this is the indexer for the IObjectFactory interface. It functions in all other respects in exactly the same way as the GetObject(string) method. The rest of this documentation will always refer to the GetObject(string) method, but be aware that you can use the indexer anywhere that you can use the GetObject(string) method. Object GetObject(string, Type): returns an object, registered under the given name. The object returned will be cast to the given Type. If the object could not be cast, corresponding exceptions will be thrown (ObjectNotOfRequiredTypeException). Furthermore, all rules of the GetObject(string) method apply (see above). bool IsSingleton(string): determines whether or not the object definition registered under the given name is a singleton or a prototype. If the object definition corresponding to the given name could not be found, an exception will be thrown (NoSuchObjectDefinitionException) string[] GetAliases(string): returns the aliases for the given object name, if any were defined in the IObjectDefinition. void ConfigureObject(object target, string name): Injects dependencies into the supplied target instance assigning the supplied name to the abstract object definition. This method is typically used when objects are instantiated outside the control of a developer, for example when ASP.NET instantiates web controls and when a WinForms application creates UserControls. A sub-interface of IObjectFactory, IConfigurableObjectFactory adds some convenient methods such as void RegisterSingleton(string name, object objectInstance) : Register the given existing object as singleton in the object factory under the given object name. void RegisterAlias(string name, string theAlias); Given an object name, create an alias. Check the SDK docs for additional details on IConfigurableObjectFactory methods and properties and the full IObjectFactory class hierarchy.
61
The IoC container in order to provide your own (or override the containers default) instantiation logic, dependency-resolution logic, and so forth. If you want to do some custom logic after the Spring container has finished instantiating, configuring and otherwise initializing an object, you can plug in one or more IObjectPostProcessor implementations. You can configure multiple IObjectPostProcessors if you wish. You can control the order in which these IObjectPostProcessor execute by setting the 'Order' property (you can only set this property if the IObjectPostProcessor implements the IOrdered interface; if you write your own IObjectPostProcessor you should consider implementing the IOrdered interface too); consult the SDK docs for the IObjectPostProcessor and IOrdered interfaces for more details.
Note
IObjectPostProcessor operate on object instances; that is to say, the Spring IoC container will have
instantiated a object instance for you, and then IObjectPostProcessors get a chance to do their stuff. If you want to change the actual object definition (that is the recipe that defines the object), then you rather need to use a IObjectFactoryPostProcessor (described below in the section entitled Customizing configuration metadata with IObjectFactoryPostProcessors. Also, IObjectPostProcessors are scoped per-container. This is only relevant if you are using container hierarchies. If you define a IObjectPostProcessor in one container, it will only do its stuff on the objects in that container. Objects that are defined in another container will not be postprocessed by IObjectPostProcessors in another container, even if both containers are part of the same hierarchy. The Spring.Objects.Factory.Config.IObjectPostProcessor interface, which consists of two callback methods shown below.
object PostProcessBeforeInitialization(object instance, string name); object PostProcessAfterInitialization(object instance, string name);
When such a class is registered as a post-processor with the container, for each object instance that is created by the container,(see below for how this registration is effected), for each object instance that is created by the container, the post-processor will get a callback from the container both before any initialization methods (such as the AfterPropertiesSet method of the IInitializingObject interface and any declared init method) are called, and also afterwards. The post-processor is free to do what it wishes with the object, including ignoring the callback completely. An object post-processor will typically check for marker interfaces, or do something such as wrap an object with a proxy. Some Spring.NET AOP infrastructure classes are implemented as object postprocessors as they do this proxy-wrapping logic. Other extensions to the IObjectPostProcessors interface are IInstantiationAwareObjectPostProcessor and IDestructionAwareObjectPostProcessor defined below
public interface IInstantiationAwareObjectPostProcessor : IObjectPostProcessor { object PostProcessBeforeInstantiation(Type objectType, string objectName); bool PostProcessAfterInstantiation(object objectInstance, string objectName); IPropertyValues PostProcessPropertyValues(IPropertyValues pvs, PropertyInfo[] pis, object objectInstance, string objectName); } public interface IDestructionAwareObjectPostProcessor : IObjectPostProcessor { void PostProcessBeforeDestruction (object instance, string name); }
62
The IoC container The PostProcessBeforeInstantiation callback method is called right before the container creates the object. If the object returned by this method is not null then the default instantiation behavior of the container is short circuited. The returned object is the one registered with the container and no other IObjectPostProcessor callbacks will be invoked on it. This mechanism is useful if you would like to expose a proxy to the object instead of the actual target object. The PostProcessAfterInstantiation callback method is called after the object has been instantiated but before Spring performs property population based on explicit properties or autowiring. A return value of false would short circuit the standard Spring based property population. The callback method PostProcessPropertyValues is called after Spring collects all the property values to apply to the object, but before they are applied. This gives you the opportunity to perform additional processing such as making sure that a property is set to a value if it contains a [Required] attribute or to perform attribute based wiring, i.e. adding the attribute [Inject("objectName")] on a property. Both of these features are scheduled to be included in Spring .12. The
IDestructionAwareObjectPostProcessor
PostProcessBeforeDestruction,
callback contains a single which is called before a singleton's destroy method is invoked.
method,
It is important to know that the IObjectFactory treats object post-processors slightly differently than the IApplicationContext. An IApplicationContext will automatically detect any objects which are deployed into it that implement the IObjectPostProcessor interface, and register them as post-processors, to be then called appropriately by the factory on object creation. Nothing else needs to be done other than deploying the postprocessor in a similar fashion to any other object. On the other hand, when using plain IObjectFactories, object post-processors have to manually be explicitly registered, with a code sequence such as...
ConfigurableObjectFactory factory = new .....; // create an IObjectFactory ... // now register some objects // now register any needed IObjectPostProcessors MyObjectPostProcessor pp = new MyObjectPostProcessor(); factory.AddObjectPostProcessor(pp); // now start using the factory ...
This explicit registration step is not convenient, and this is one of the reasons why the various IApplicationContext implementations are preferred above plain IObjectFactory implementations in the vast majority of Spring-backed applications, especially when using IObjectPostProcessors.
Note
IObjectPostProcessors and AOP auto-proxying Classes that implement the IObjectPostProcessor interface are special, and so they are treated differently by the container. All IObjectPostProcessors and their directly referenced object will be instantiated on startup, as part of the special startup phase of the IApplicationContext, then all those IObjectPostProcessors will be registered in a sorted fashion - and applied to all further objects. Since AOP auto-proxying is implemented as a IObjectPostProcessor itself, no IObjectPostProcessors or directly referenced objects are eligible for auto-proxying (and thus will not have aspects 'woven' into them). For any such object, you should see an info log message: Object 'foo' is not eligible for getting processed by all IObjectPostProcessors (for example: not eligible for auto-proxying). 5.9.1.1. Example: Hello World, IObjectPostProcessor-style This first example is hardly compelling, but serves to illustrate basic usage. All we are going to do is code a custom IObjectPostProcessor implementation that simply invokes the ToString() method of each object as it is
63
The IoC container created by the container and prints the resulting string to the system console. Yes, it is not hugely useful, but serves to get the basic concepts across before we move into the second example which is actually useful. The basis of the example is the MovieFinder quickstart that is included with the Spring.NET distribution. Find below the custom IObjectPostProcessor implementation class definition
using System; using Spring.Objects.Factory.Config; namespace Spring.IocQuickStart.MovieFinder { public class TracingObjectPostProcessor : IObjectPostProcessor { public object PostProcessBeforeInitialization(object instance, string name) { return instance; } public object PostProcessAfterInitialization(object instance, string name) { Console.WriteLine("Object '" + name + "' created : " + instance.ToString()); return instance; } } }
Notice how the TracingObjectPostProcessor is simply defined; it doesn't even have a name, and because it is a object it can be dependency injected just like any other object. Find below a small driver script to exercise the above code and configuration;
IApplicationContext ctx = new XmlApplicationContext( "assembly://Spring.IocQuickStart.MovieFinder/Spring.IocQuickStart.MovieFinder/AppContext.xml"); MovieLister lister = (MovieLister) ctx.GetObject("MyMovieLister"); Movie[] movies = lister.MoviesDirectedBy("Roberto Benigni"); LOG.Debug("Searching for movie..."); foreach (Movie movie in movies) { LOG.Debug(string.Format("Movie Title = '{0}', Director = '{1}'.", movie.Title, movie.Director)); } LOG.Debug("MovieApp Done.");
64
5.9.1.2. Example: the RequiredAttributeObjectPostProcessor Using callback interfaces or annotations in conjunction with a custom IObjectPostProcessor implementation is a common means of extending the Spring IoC container. The [Required] attribute in the Spring.Objects.Factory.Attributes namespace can be used to mark a property as being 'required-to-be-set' (i.e. an setter property with this attribute applied must be configured to be dependency injected with a value), else an ObjectInitializationException will be thrown by the container at runtime. The best way to illustrate the usage of this attribute is with an example.
public class MovieLister { // the MovieLister has a dependency on the MovieFinder private IMovieFinder _movieFinder; // a setter property so that the Spring container can 'inject' a MovieFinder [Required] public IMovieFinder MovieFinder { set { _movieFinder = value; } } // business logic that actually 'uses' the injected MovieFinder is omitted... }
Hopefully the above class definition reads easy on the eye. Any and all IObjectDefinitions for the MovieLister class must be provided with a value. Let's look at an example of some XML configuraiton that will not pass validation.
<object id="MyMovieLister" type="Spring.IocQuickStart.MovieFinder.MovieLister, Spring.IocQuickStart.MovieFinder"> <!-- whoops, no MovieFinder is set (and this property is [Required]) --> </object>
There is one last little piece of Spring configuration that is required to actually 'switch on' this behavior. Simply annotating the 'setter' properties of your classes is not enough to get this behavior. You need to enable a component that is aware of the [Required] attribute and that can process it appropriately. This component is the RequiredAttributeObjectPostProcessor class. This is a special IObjectPostProcessor implementation that is [Required]-aware and actually provides the 'blow up if this required property has not been set' logic. It is very easy to configure; simply drop the following object definition into your Spring XML configuration.
<object type="Spring.Objects.Factory.Attributes.RequiredAttributeObjectPostProcessor, Spring.Core"/>
Finally, one can configure an instance of the RequiredAttributeObjectPostProcessor class to look for another Attribute type. This is great if you already have your own [Required]-style attribute. Simply plug it into the definition of a RequiredAttributeObjectPostProcessor and you are good to go. By way of an example,
65
The IoC container let's suppose you (or your organization / team) have defined an attribute called [Mandatory]. You can make a RequiredAttributeObjectPostProcessor instance [Mandatory]-aware like so:
<object type="Spring.Objects.Factory.Attributes.RequiredAttributeObjectPostProcessor, Spring.Core"> <property name="RequiredAttributeType" value="MyApp.Attributes.MandatoryAttribute, MyApp"/> </object>
next
extension
point
that
we
You can configure multiple IObjectFactoryPostProcessors if you wish. You can control the order in which these IObjectFactoryPostProcessors execute by setting the 'Order' property (you can only set this property if the IObjectFactoryPostProcessors implements the IOrdered interface; if you write your own IObjectFactoryPostProcessors you should consider implementing the IOrdered interface too); consult the SDK docs for the IObjectFactoryPostProcessors and IOrdered interfaces for more details.
Note
If you want to change the actual object instances (the objects that are created from the configuration metadata), then you rather need to use a IObjectObjectPostProcessor (described above in the section entitled Customizing objects with IObjectPostProcessors. Also, IObjectFactoryPostProcessors are scoped per-container. This is only relevant if you are using container hierarchies. If you define a IObjectFactoryPostProcessors in one container, it will only do its stuff on the object definitions in that container. Object definitions in another container will not be post-processed by IObjectFactoryPostProcessors in another container, even if both containers are part of the same hierarchy. An object factory post-processor is executed manually (in the case of a IObjectFactory) or automatically (in the case of an IApplicationContext) to apply changes of some sort to the configuration metadata that defines a container. Spring.NET includes a number of pre-existing object factory post-processors, such as PropertyResourceConfigurer and PropertyPlaceHolderConfigurer, both described below and ObjectNameAutoProxyCreator, which is very useful for wrapping other objects transactionally or with any other kind of proxy, as described later in this manual. In an IObjectFactory, the process of applying an IObjectFactoryPostProcessor is manual, and will be similar to this:
XmlObjectFactory factory = new XmlObjectFactory(new FileSystemResource("objects.xml")); // create placeholderconfigurer to bring in some property // values from a Properties file
66
This explicit registration step is not convenient, and this is one of the reasons why the various IApplicationContext implementations are preferred above plain IObjectFactory implementations in the vast majority of Spring-backed applications, especially when using IObjectFactoryPostProcessors. An IApplicationContext will detect any objects which are deployed into it that implement the ObjectFactoryPostProcessor interface, and automatically use them as object factory post-processors, at the appropriate time. Nothing else needs to be done other than deploying these post-processor in a similar fashion to any other object.
Note
Just as in the case of IObjectPostProcessors, you typically don't want to have IObjectFactoryPostProcessors marked as being lazily-initialized. If they are marked as such, then the Spring container will never instantiate them, and thus they won't get a chance to apply their custom logic. If you are using the 'default-lazy-init' attribute on the declaration of your <objects/> element, be sure to mark your various IObjectFactoryPostProcessor object definitions with 'lazy-init="false"'. 5.9.2.1. Example: The PropertyPlaceholderConfigurer The PropertyPlaceholderConfigurer is an excellent solution when you want to externalize a few properties from a file containing object definitions. This is useful to allow the person deploying an application to customize environment specific properties (for example database configuration strings, usernames, and passwords), without the complexity or risk of modifying the main XML definition file or files for the container. Variable substitution is performed on simple property values, lists, dictionaries, sets, constructor values, object type name, and object names in runtime object references. Furthermore, placeholder values can also crossreference other placeholders. Note that IApplicationContexts are able to automatically recognize and apply objects deployed in them that implement the IObjectFactoryPostProcessor interface. This means that as described here, applying a PropertyPlaceholderConfigurer is much more convenient when using an IApplicationContext. For this reason, it is recommended that users wishing to use this or other object factory postprocessors use an IApplicationContext instead of an IObjectFactory. In the example below a data access object needs to be configured with a database connection and also a value for the maximum number of results to return in a query. Instead of hard coding the values into the main Spring.NET configuration file we use place holders, in the NAnt style of ${variableName}, and obtain their values from NameValueSections in the standard .NET application configuration file. The Spring.NET configuration file looks like:
<configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> </sectionGroup> <section name="DaoConfiguration" type="System.Configuration.NameValueSectionHandler"/> <section name="DatabaseConfiguration" type="System.Configuration.NameValueSectionHandler"/> </configSections> <DaoConfiguration>
67
Notice the presence of two NameValueSections in the configuration file. These name value pairs will be referred to in the Spring.NET configuration file. In this example we are using an embedded assembly resource for the location of the Spring.NET configuration file so as to reduce the chance of accidental tampering in deployment. This Spring.NET configuration file is shown below.
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd" > <object name="productDao" type="DaoApp.SimpleProductDao, DaoApp "> <property name="maxResults" value="${maxResults}"/> <property name="dbConnection" ref="myConnection"/> </object> <object name="myConnection" type="System.Data.Odbc.OdbcConnection, System.Data"> <property name="connectionstring" value="${connection.string}"/> </object> <object name="appConfigPropertyHolder" type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core"> <property name="configSections"> <value>DaoConfiguration,DatabaseConfiguration</value> </property> </object> </objects>
The values of ${maxResults} and ${connection.string} match the key names used in the two NameValueSectionHandlers DaoConfiguration and DatabaseConfiguration. The PropertyPlaceholderConfigurer refers to these two sections via a comma delimited list of section names in the configSections property. If you are using section groups, prefix the section group name, for example myConfigSection/DaoConfiguraiton. The PropertyPlaceholderConfigurer class also supports retrieving name value pairs from other IResource locations. These can be specified using the Location and Locations properties of the PropertyPlaceHolderConfigurer class. If there are properties with the same name in different resource locations the default behavior is that the last property processed overrides the previous values. This is behavior is controlled by the LastLocationOverrides property. True enables overriding while false will append the values as one would normally expect using NameValueCollection.Add.
Note
In an ASP.NET environment you must specify the full, four-part name of the assembly when using a NameValueFileSectionHandler
68
If the class is unable to be resolved at runtime to a valid type, resolution of the object will fail once it is about to be created (which is during the PreInstantiateSingletons() phase of an ApplicationContext for a non-lazy-init object.) Similarly you can replace 'ref' and 'expression' metadata, as shown below
<object id="TestObject" type="Simple.TestObject, MyAssembly"> <property name="age" expression="${ageExpression}"/> <property name="spouse" ref="${spouse-ref}"/> </object>
5.9.2.2. Example: The PropertyOverrideConfigurer The another object factory post-processor, is similar to the PropertyPlaceholderConfigurer, but in contrast to the latter, the original definitions can have default values or no values at all for object properties. If an overriding configuration file does not have an entry for a certain object property, the default context definition is used. Note that the object factory definition is not aware of being overridden, so it is not immediately obvious when looking at the XML definition file that the override configurer is being used. In case that there are multiple PropertyOverrideConfigurer instances that define different values for the same object property, the last one will win (due to the overriding mechanism). The example usage is similar to when using PropertyPlaceHolderConfigurer except that the key name refers to the name given to the object in the Spring.NET configuration file and is suffixed via 'dot' notation with the name of the property For example, if the application configuration file is
<configuration>
PropertyOverrideConfigurer,
69
Then the value of 1000 will be used to overlay the value of 2000 set in the Spring.NET configuration file shown below
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/springobjects.xsd" > <object name="productDao" type="PropPlayApp.SimpleProductDao, PropPlayApp " > <property name="maxResults" value="2000"/> <property name="dbConnection" ref="myConnection"/> <property name="log" ref="daoLog"/> </object> <object name="daoLog" type="Spring.Objects.Factory.Config.LogFactoryObject, Spring.Core"> <property name="logName" value="DAOLogger"/> </object> <object name="myConnection" type="System.Data.Odbc.OdbcConnection, System.Data"> <property name="connectionstring"> <value>dsn=MyDSN;uid=sa;pwd=myPassword;</value> </property> </object> <object name="appConfigPropertyOverride" type="Spring.Objects.Factory.Config.PropertyOverrideConfigurer, Spring.Core"> <property name="configSections"> <value>DaoConfigurationOverride</value> </property> </object> </objects>
5.9.2.3. IVariableSource The IVariableSource is the base interface for providing the ability to get the value of property placeholders (namevalue) pairs from a variety of sources. Out of the box, Spring.NET supports a number of variable sources that allow users to obtain variable values from .NET config files, java-style property files, environment variables, command line arguments and the registry and the new connection strings configuration section in .NET 2.0. The list of implementing classes is listed below. Please refer to the SDK documentation for more information. ConfigSectionVariableSource PropertyFileVariableSource EnvironmentVariableSource CommandLineArgsVariableSource
70
The IoC container RegistryVariableSource SpecialFolderVariableSource ConnectionStringsVariableSource ConfigurableVariableSource You use this by defining an instance of Spring.Objects.Factory.Config.VariablePlaceholderConfigurer in your configuration and set the property VariableSource to a single IVariableSource instance or the list property VariableSources to a list of IVariableSource instances. In the case of the same property defined in multiple IVariableSource implementations, the first one in the list that contains the property value will be used.
<object type="Spring.Objects.Factory.Config.VariablePlaceholderConfigurer, Spring.Core"> <property name="VariableSources"> <list> <object type="Spring.Objects.Factory.Config.PropertyFileVariableSource, Spring.Core"> <property name="Location" value="~\application.properties" /> <property name="IgnoreMissingResources" value="true"/> </object> <object type="Spring.Objects.Factory.Config.ConfigSectionVariableSource, Spring.Core"> <property name="SectionNames" value="CryptedConfiguration" /> </object> </list> </property> </object>
Note
The use of the IgnoreMissingResources property above will mean that if the property file is not found it will be silently ignored and the resolution will continue to ConfigSectionVariableSource. The IVariableSource interface is shown below
public interface IVariableSource { string ResolveVariable(string name); }
This is a simple contract to implement if you should decide to create your own custom implemention. Look at the source code of the current implementations for some inspiration if you go that route. To register your own custom implemenation, simply configure VariablePlaceholderConfigurer to refer to your class.
71
The IoC container bool IsSingleton: has to return true if this IFactoryObject returns singletons, false otherwise. Type ObjectType: has to return either the object type returned by the GetObject() method or null if the type isn't known in advance.
IFactoryObject
The IFactoryObject concept and interface is used in a number of places within the Spring Framework. Some examples of its use is described in Section 5.3.9, Setting a reference using the members of other objects and classes. for the PropertyRetrievingFactoryObject and FieldRetrievingFactoryObject. An additional use of creating an custom IFactoryObject implementation is to retrieve an object from an embedded resource file and use it to set another objects dependency. An example of this is provided here [http://jira.springframework.org/ browse/SPRNET-133#action_19743]. Finally, there is sometimes a need to ask a container for an actual IFactoryObject instance itself, not the object it produces. This may be achieved by prepending the object id with '&' (sans quotes) when calling the GetObject method of the IObjectFactory (including IApplicationContext). So for a given IFactoryObject with an id of 'myObject', invoking GetObject("myObject") on the container will return the product of the IFactoryObject, but invoking GetObject("&myObject") will return the IFactoryObject instance itself. 5.9.3.1. IConfigurableFactoryObject The Spring.Objects.Factory.IConfigurableFactoryObject interface inherits from IFactoryObject interface and adds the following property. IObjectDefinition ProductTemplate : Gets the template object definition that should be used to configure the instance of the object managed by this factory.
IConfigurableFactoryObject WebServiceProxyFactory.
Loosely Coupled Event Propagation. Publishers and subscribers of events do not have to be directly aware of each other as they register their interest indirectly through the application context.
72
Table 5.7. Feature Matrix Feature Object instantiation/wiring Automatic IObjectPostProcessor registration Automatic
IObjectFactoryPostProcessor IObjectFactory IApplicationContext
Yes No
Yes Yes
No
Yes
No
Yes
publication
No No
Yes Yes
Singleton service locator style access Declarative registration of custom resource protocol handler, XML Parsers for object definitions, and type aliases
No
Yes
73
The IoC container the use of a custom configuration section handler. Note that the types shown for resource handlers and parsers are fictional.
<configuration> <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"/> <section name="resourceHandlers" type="Spring.Context.Support.ResourceHandlersSectionHandler, Spring.Core"/> <section name="typeAliases" type="Spring.Context.Support.TypeAliasesSectionHandler, Spring.Core"/> <section name="typeConverters" type="Spring.Context.Support.TypeConvertersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" /> <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /> </parsers> <resourceHandlers> <handler protocol="db" type="MyCompany.MyApp.Resources.MyDbResource"/> </resourceHandlers> <context caseSensitive="false"> <resource uri="config://spring/objects"/> <resource uri="db://user:pass@dbName/MyDefinitionsTable"/> </context> <typeAliases> <alias name="WebServiceExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web"/> <alias name="DefaultPointcutAdvisor" type="Spring.Aop.Support.DefaultPointcutAdvisor, Spring.Aop"/> <alias name="AttributePointcut" type="Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop"/> <alias name="CacheAttribute" type="Spring.Attributes.CacheAttribute, Spring.Core"/> <alias name="MyType" type="MyCompany.MyProject.MyNamespace.MyType, MyAssembly"/> </typeAliases> <typeConverters> <converter for="Spring.Expressions.IExpression, Spring.Core" type="Spring.Objects.TypeConverters.ExpressionConverter, Spring.Core"/> <converter for="MyTypeAlias" type="MyCompany.MyProject.Converters.MyTypeConverter, MyAssembly"/> </typeConverters> <objects xmlns="http://www.springframework.net"> ... </objects> </spring> </configuration>
The new sections are described below. The attribute caseSensitive allows the for both IObjectFactory and IApplicationContext implementations to not pay attention to the case of the object names. This is important in web applications so that ASP.NET pages can be resolved in a case independent manner. The default value is true.
74
The IoC container and int ParseElement(XmlElement element, XmlResourceReader reader). Registering custom parsers outside of App.config will be addressed in a future release.
root, XmlResourceReader reader)
To
section handler of the type Spring.Context.Support.NamespaceParsersSectionHandler in the configSecitons section of App.config. The parser configuration section contains one or more <parser> elements each with a type attribute. Below is an example that registers all the namespaces provided in Spring.
register
custom
parser
register
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other configuration section handler defined here --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" /> <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /> <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" /> <parser type="Spring.Validation.Config.ValidationNamespaceParser, Spring.Core" /> <parser type="Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services" /> </parsers> </spring> </configuration>
You can also register custom parser programmatically using the NamespaceParserRegistry. Here is an example taken from the code used in the Transactions Quickstart application.
NamespaceParserRegistry.RegisterParser(typeof(DatabaseNamespaceParser)); NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser)); NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); IApplicationContext context = new XmlApplicationContext("assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/system-test-localconfig.xml");
75
To
a section handler of the type in the configSecitons section of App.config. The type alias configuration section contains one or more <alias> elements each with a name and a type attribute. Below is an example that registers the alias for WebServiceExporter
Spring.Context.Support.TypeAliasesSectionHandler
<configuration> <configSections> <sectionGroup name="spring"> <!-- other configuration section handler defined here --> <section name="typeAliases" type="Spring.Context.Support.TypeAliasesSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <typeAliases> <alias name="WebServiceExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web"/> </typeAliases> </spring> </configuration>
register
type
alias
register
For an example showing type aliases for generic types see Section 5.2.6, Object creation of generic types. Another way is to define an object of the type Spring.Objects.Factory.Config.TypeAliasConfigurer within the regular <objects> section of any standard Spring configuration file. This approach allows for more modularity in defining type aliases, for example if you can't access App.config/Web.config. An example of registration using a TypeAliasConfigurer is shown below
<object id="myTypeAlias" type="Spring.Objects.Factory.Config.TypeAliasConfigurer, Spring.Core"> <property name="TypeAliases"> <dictionary> <entry key="WebServiceExporter" value="Spring.Web.Services.WebServiceExporter, Spring.Web"/> <entry key="DefaultPointcutAdvisor" value="Spring.Aop.Support.DefaultPointcutAdvisor, Spring.Aop"/> <entry key="MyType" value="MyCompany.MyProject.MyNamespace.MyType, MyAssembly"/> </dictionary> </property>
76
The nesting of context elements reflects the parent-child hierarchy you are creating. The nesting can be to any level though it is unlikely one would need a deep application hierarchy. The xml file must contain the <objects> as the root name. Another example of a hierarchy, but using sections in the application configuration file is shown below.
<configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> <sectionGroup name="child"> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> </sectionGroup> </sectionGroup> </configSections> <spring>
77
<context name="ParentContext"> <resource uri="config://spring/objects"/> <context name="ChildContext"> <resource uri="config://spring/child/objects"/> </context> </context> <objects xmlns="http://www.springframework.net"> ... </objects> <child> <objects xmlns="http://www.springframework.net"> ... </objects> </child> </spring>
As
the context tag is optional and defaults to Spring.Context.Support.XmlApplicationContext. The name of the context can be used in conjunction with the service locator class, ContextRegistry, discussed in Section 5.15, Service Locator access
type
reminder,
the
attribute
of
78
The IoC container void ApplyResources(object value, string objectName, CultureInfo cultureInfo): Uses a ComponentResourceManager to apply resources to all object properties that have a matching key name. Resource key names are of the form objectName.propertyName When an IApplicationContext gets loaded, it automatically searches for an IMessageSource object defined in the context. The object has to have the name messageSource. If such an object is found, all calls to the methods described above will be delegated to the message source that was found. If no message source was found, the IApplicationContext checks to see if it has a parent containing a similar object, with a similar name. If so, it uses that object as the IMessageSource. If it can't find any source for messages, an empty StaticMessageSource will be instantiated in order to be able to accept calls to the methods defined above.
Fallback behavior
The fallback rules for localized resources seem to have a bug that is fixed by applying Service Pack 1 for .NET 1.1. This affects the use of IMessageSource.GetMessage methods that specify CultureInfo. The core of the issue in the .NET BCL is the method ResourceManager.GetObject that accepts CultureInfo. Spring.NET provides two IMessageSource implementations. These are ResourceSetMessageSource and StaticMessageSource. Both implement IHierarchicalMessageSource to resolve messages hierarchically. The StaticMessageSource is hardly ever used but provides programmatic ways to add messages to the source. The ResourceSetMessageSource is more interesting and an example is provided for in the distribution and discussed more extensively in the Chapter 37, IoC Quickstarts section. The ResourceSetMessageSource is configured by providing a list of ResourceManagers. When a message code is to be resolved, the list of ResourceManagers is searched to resolve the code. For each ResourceManager a ResourceSet is retrieved and asked to resolve the code. Note that this search does not replace the standard hub-and-spoke search for localized resources. The ResourceManagers list specifies the multiple 'hubs' where the standard search starts.
<object name="messageSource" type="Spring.Context.Support.ResourceSetMessageSource, Spring.Core"> <property name="resourceManagers"> <list> <value>Spring.Examples.AppContext.MyResource, Spring.Examples.AppContext</value> </list> </property> </object>
You can specify the arguments to construct a ResourceManager as a two part string value containing the base name of the resource and the assembly name. This will be converted to a ResourceManager via the ResourceManagerConverter TypeConverter. This converter can be similarly used to set a property on any object that is of the type ResourceManager. You may also specify an instance of the ResourceManager to use via an object reference. The convenience class Spring.Objects.Factory.Config.ResourceManagerFactoryObject can be used to conveniently create an instance of a ResourceManager.
<object name="myResourceManager" type="Spring.Objects.Factory.Config.ResourceManagerFactoryObject, Spring.Core"> <property name="baseName"> <value>Spring.Examples.AppContext.MyResource</value> </property> <property name="assemblyName"> <value>Spring.Examples.AppContext</value> </property> </object>
In application code, a call to GetMessage will retrieve a properly localized message string based on a code value. Any arguments present in the retrieved string are replaced using String.Format semantics. The ResourceManagers, ResourceSets and retrieved strings are cached to provide quicker lookup performance. The
79
The IoC container key 'HelloMessage' is contained in the resource file with a value of Hello {0} {1}. The following call on the application context will return the string Hello Mr. Anderson. Note that the caching of ResourceSets is via the concatenation of the ResourceManager base name and the CultureInfo string. This combination must be unique.
string msg = ctx.GetMessage("HelloMessage", new object[] {"Mr.", "Anderson"}, CultureInfo.CurrentCulture );
It is possible to chain the resolution of messages by passing arguments that are themselves messages to be resolved giving you greater flexibility in how you can structure your message resolution. This is achieved by passing as an argument a class that implements IMessageResolvable instead of a string literal. The convenience class DefaultMessageResolvable is available for this purpose. As an example if the resource file contains a key name error.required that has the value '{0} is required {1}' and another key name field.firstname with the value 'First name'. The following code will create the string 'First name is required dude!'
string[] codes = {"field.firstname"}; DefaultMessageResolvable dmr = new DefaultMessageResolvable(codes, null); ctx.GetMessage("error.required", new object[] { dmr, "dude!" }, CultureInfo.CurrentCulture ));
The examples directory in the distribution contains an example program, Spring.Examples.AppContext, that demonstrates usage of these features. The IMessageSourceAware interface can also be used to acquire a reference to any IMessageSource that has been defined. Any object that is defined in an IApplicationContext that implements the IMessageSourceAware interface will be injected with the application context's IMessageSource when it (the object) is being created and configured.
80
The IoC container void Subscribe(object subscriber, Type targetSourceType ): The subscriber receives all events from a source object of a particular type for which it has matching handler methods. void Unsubscribe(object subscriber ): Unsubscribe all events from the source object for which it has matching handler methods. void Unsubscribe(object subscriber, Type targetSourceType ): Unsubscribe all events from a source object of a particular type for which it has matching handler methods. IApplicationContext implements this interface and delegates the implementation to an instance of Spring.Objects.Events.Support.EventRegistry. You are free to create and use as many EventRegistries as you like but since it is common to use only one in an application, IApplicationContext provides convenient access to a single instance. Within the example/Spring/Spring.Examples.EventRegistry directory you will find an example on how to use this functionality. When you open up the project, the most interesting file is the EventRegistryApp.cs file. This application loads a set of object definitions from the application configuration file into an IApplicationContext instance. From there, three objects are loaded up: one publisher and two subscribers. The publisher publishes its events to the IApplicationContext instance:
// Create the Application context using configuration file IApplicationContext ctx = ContextRegistry.GetContext(); // Gets the publisher from the application context MyEventPublisher publisher = (MyEventPublisher)ctx.GetObject("MyEventPublisher"); // Publishes events to the context. ctx.PublishEvents( publisher );
One of the two subscribers subscribes to all events published to the IApplicationContext instance, using the publisher type as the filter criteria.
// Gets first instance of subscriber MyEventSubscriber subscriber = (MyEventSubscriber)ctx.GetObject("MyEventSubscriber"); // Gets second instance of subscriber MyEventSubscriber subscriber2 = (MyEventSubscriber)ctx.GetObject("MyEventSubscriber"); // Subscribes the first instance to the any events published by the type MyEventPublisher ctx.Subscribe( subscriber, typeof(MyEventPublisher) );
This will wire the first subscriber to the original event publisher. Anytime the event publisher fires an event, (publisher.ClientMethodThatTriggersEvent1();) the first subscriber will handle the event, but the second subscriber will not. This allows for selective subscription, regardless of the original prototype definition.
81
The IoC container use. The Closed is published when the IApplicationContext is closed using the Dispose() method on the IConfigurableApplicationContext interface. Closed here means that singletons are destroyed. Implementing custom events can be done as well. Simply call the PublishEvent method on the IApplicationContext, specifying a parameter which is an instance of your custom event argument subclass. Let's have a look at an example. First, the IApplicationContext:
<object id="emailer" type="Example.EmailObject"> <property name="blackList"> <list> <value>[email protected]</value> <value>[email protected]</value> <value>[email protected]</value> </list> </property> </object> <object id="blackListListener" type="Example.BlackListNotifier"> <property name="notificationAddress"> <value>[email protected]</value> </property> </object>
82
post-processors
are
83
The implementation of IObjectDefinitionReader is responsible for creating the configuration metadata, i.e., implementations of RootObjectDefinition, etc. Note a web version of this application class has not yet been implemented. An example, with a yet to be created DLL scanner, that would get configuration metadata from the .dll named MyAssembly.dll located in the runtime path, would look something like this
GenericApplicationContext ctx = new GenericApplicationContext(); ObjectDefinitionScanner scanner = new ObjectDefinitionScanner(ctx); scanner.scan("MyAssembly.dll"); ctx.refresh();
This would retrieve the nested context for the context configuration shown previously.
<spring> <context> <resource uri="assembly://MyAssembly/MyProject/root-objects.xml"/> <context name="mySubContext"> <resource uri="file://objects.xml"/> </context> </context> </spring>
Do not call ContextRegistry.GetContext within a constructor as it will result in and endless recursion. (This is scheduled to be fixed in 1.1.1) In this case it is quite likely you can use the IApplicationContextAware interface and then retrieve other objects in a service locator style inside an initialization method.
84
The IoC container The ContextRegistry.Clear() method will remove all contexts. On .NET 2.0, this will also call the ConfigurationManager's RefreshSection method so that the Spring context configuration section will be reread from disk when it is retrieved again. Note that in a web application RefeshSection will not work as advertised and you will need to touch the web.config files to reload a configuration.
85
86
The IObjectWrapper and Type conversion Path account[2] Explanation Indicates the third element of the account property of the wrapped object. Indexed properties are typically collections such as lists and dictionaries, but can be any class that exposes an indexer.
Below you'll find some examples of working with the IObjectWrapper to get and set properties. Consider the following two classes:
[C#] public class Company { private string name; private Employee managingDirector; public string Name { get { return this.name; } set { this.name = value; } } public Employee ManagingDirector { get { return this.managingDirector; } set { this.managingDirector = value; } } }
[C#] public class Employee { private string name; private float salary; public string Name { get { return this.name; } set { this.name = value; } } public float Salary { get { return salary; } set { this.salary = value; } } }
The following code snippets show some examples of how to retrieve and manipulate some of the properties of IObjectWrapper-wrapped Company and Employee instances.
[C#] Company c = new Company(); IObjectWrapper owComp = new ObjectWrapper(c); // setting the company name... owComp.SetPropertyValue("name", "Salina Inc."); // can also be done like this... PropertyValue v = new PropertyValue("name", "Salina Inc."); owComp.SetPropertyValue(v); // ok, let's create the director and bind it to the company... Employee don = new Employee(); IObjectWrapper owDon = new ObjectWrapper(don); owDon.SetPropertyValue("name", "Don Fabrizio"); owComp.SetPropertyValue("managingDirector", don); // retrieving the salary of the ManagingDirector through the company float salary = (float)owComp.GetPropertyValue("managingDirector.salary");
87
The IObjectWrapper and Type conversion Note that since the various Spring.NET libraries are compliant with the Common Language Specification (CLS), the resolution of arbitrary strings to properties, events, classes and such is performed in a case-insensitive fashion. The previous examples were all written in the C# language, which is a case-sensitive language, and yet the Name property of the Employee class was set using the all-lowercase 'name' string identifier. The following example (using the classes defined previously) should serve to illustrate this...
[C#] // ok, let's create the director and bind it to the company... Employee don = new Employee(); IObjectWrapper owDon = new ObjectWrapper(don); owDon.SetPropertyValue("naMe", "Don Fabrizio"); owDon.GetPropertyValue("nAmE"); // gets "Don Fabrizio" IObjectWrapper owComp = new ObjectWrapper(new Company()); owComp.SetPropertyValue("ManaGINGdirecToR", don); owComp.SetPropertyValue("mANaGiNgdirector.salARY", 80000); Console.WriteLine(don.Salary); // puts 80000
The case-insensitivity of the various Spring.NET libraries (dictated by the CLS) is not usually an issue... if you happen to have a class that has a number of properties, events, or methods that differ only by their case, then you might want to consider refactoring your code, since this is generally regarded as poor programming practice.
The TypeConverter class from the System.ComponentModel namespace of the .NET BCL is used extensively by the various classes in the Spring.Core library, as said class ... provides a unified way of converting types of values to other types, as well as for accessing standard values and subproperties. 1 For example, a date can be represented in a human readable format (such as 30th August 1984), while we're still able to convert the human readable form to the original date format or (even better) to an instance of the System.DateTime class. This behavior can be achieved by using the standard .NET idiom of decorating a class with the TypeConverterAttribute. Spring.NET also offers another means of associating a TypeConverters with a class. You might want to do this to achieve a conversion that is not possible using standard idiom... for example,
1
More information about creating custom TypeConverter implementations can be found online at Microsoft's MSDN website, by searching for Implementing a Type Converter.
88
The IObjectWrapper and Type conversion the Spring.Core library contains a custom TypeConverter that converts comma-delimited strings to String array instances. Registering custom converters on an IObjectWrapper instance gives the wrapper the knowledge of how to convert properties to the desired Type. An example of where property conversion is used in Spring.NET is the setting of properties on objects, accomplished using the aforementioned TypeConverters. When mentioning System.String as the value of a property of some object (declared in an XML file for instance), Spring.NET will (if the type of the associated property is System.Type) use the RuntimeTypeConverter class to try to resolve the property value to a Type object. The example below demonstrates this automatic conversion of the Example.Xml.SAXParser (a string) into the corresponding Type instance for use in this factory-style class.
<objects xmlns="http://www.springframework.net"> <object id="parserFactory" type="Example.XmlParserFactory, ExamplesLibrary" destroy-method="Close"> <property name="ParserClass" value="Example.Xml.SAXParser, ExamplesLibrary"/> </object> </objects>
[C#] public class XmlParserFactory { private Type parserClass; public Type ParserClass { get { return this.parserClass; } set { this.parserClass = value; } } public XmlParser GetParser () { return Activator.CreateInstance (ParserClass); } }
Explanation Parses strings representing System.Types to actual System.Types and the other way around. Capable of resolving strings to a System.IO.FileInfo object.
FileInfoConverter
89
Explanation Capable of resolving a comma-delimited list of strings to a stringarray and vice versa. Capable of resolving a string representation of a URI to an actual Uri-object. Capable of resolving a string representation of a FileInfo to an actual FileInfo-object. Capable of resolving Spring IResource URI (string) to its corresponding InputStream-object. Capable of resolving Spring IResource URI (string) to an IResource object. Capable of resolving a two part string (resource name, assembly name) to a System.Resources.ResourceManager object. Capable of resolving a comma separated list of Red, Green, Blue integer values to a System.Drawing.Color structure. Converts string representation of regular expression into an instance of System.Text.RegularExpressions.Regex
UriConverter
FileInfoConverter
StreamConverter
ResourceConverter
ResourceManagerConverter
RgbColorConverter
RegexConverter
Spring.NET uses the standard .NET mechanisms for the resolution of System.Types, including, but not limited to checking any configuration files associated with your application, checking the Global Assembly Cache (GAC), and assembly probing.
90
Chapter 7. Resources
7.1. Introduction
The IResource interface contained in the Spring.Core.IO namespace provides a common interface to describe and access data from diverse resource locations. This abstraction lets you treat the InputStream from a file and from a URL in a polymorphic and protocol-independent manner... the .NET BCL does not provide such an abstraction. The IResource interface inherits from IInputStream that provides a single property Stream InputStream. The IResource interface adds descriptive information about the resource via a number of additional properties. Several implementations for common resource locations, i.e. file, assembly, uri, are provided and you may also register custom IResource implementations.
Explanation Inherited from IInputStream. Opens and returns a System.IO.Stream. It is expected that each invocation returns a fresh Stream. It is the responsibility of the caller to close the stream. returns a boolean indicating whether this resource actually exists in physical form. returns a boolean indicating whether this resource represents a handle with an open stream. If true, the InputStream cannot be read multiple times, and must be read once only and then closed to avoid resource leaks. Will be false for all usual resource implementations, with the exception of InputStreamResource. Returns a description of the resource, such as the fully qualified file name or the actual URL. The Uri representation of the resource. Returns a System.IO.FileInfo for this resource if it can be resolved to an absolute file path.
Exists
IsOpen
Description
Uri File
91
Explanation Creates a resource relative to this resource using relative path like notation (./ and ../).
(string relativePath)
You can obtain an actual URL or File object representing the resource if the underlying implementation is compatible and supports that functionality. The Resource abstraction is used extensively in Spring itself, as an argument type in many method signatures when a resource is needed. Other methods in some Spring APIs (such as the constructors to various IApplicationContext implementations), take a String which is used to create a Resource appropriate to that context implementation While the Resource interface is used a lot with Spring and by Spring, it's actually very useful to use as a general utility class by itself in your own code, for access to resources, even when your code doesn't know or care about any other parts of Spring. While this couples your code to Spring, it really only couples it to this small set of utility classes and can be considered equivalent to any other library you would use for this purpose
ConfigSectionResource accesses Spring.NET configuration data stored in a custom configuration section in the .NET application configuration file (i.e. App.config). Uri syntax is config://<path to section> FileSystemResource accesses file system data. Uri syntax is file://<filename> InputStreamResource a wrapper around a raw System.IO.Stream . Uri syntax is not supported. UriResource accesses data from the standard System.Uri protocols such as http and https. In .NET 2.0 you can use this also for the ftp protocol. Standard Uri syntax is supported. Refer to the MSDN documentation for more information on supported Uri scheme types.
92
Resources
</configSections> <spring> <resourceHandlers> <handler protocol="db" type="MyCompany.MyApp.Resources.MyDbResource, MyAssembly"/> </resourceHandlers> <context> <resource uri="db://user:pass@dbName/MyDefinitionsTable"/> </context> </spring> </configuration>
Other protocols can be registered along with a new implementations of an IResource that must correctly parse a Uri string in its constructor. An example of this can be seen in the Spring.Web namespace that uses Server.MapPath to resolve the filename of a resource. The CreateRelative method allows you to easily load resources based on a relative path name. In the case of relative assembly resources, the relative path navigates the namespace within an assembly. For example:
IResource res = new AssemblyResource("assembly://Spring.Core.Tests/Spring/TestResource.txt"); IResource res2 = res.CreateRelative("./IO/TestIOResource.txt");
This loads the resource TestResource.txt and then navigates to the Spring.Core.IO namespace and loads the resource TestIOResource.txt
When a class implements IResourceLoaderAware and is deployed into an application context (as a Springmanaged object), it is recognized as IResourceLoaderAware by the application context. The application context
93
Resources will then invoke the ResourceLoader property, supplying itself as the argument (remember, all application contexts in Spring implement the IResourceLoader interface). Of course, since an IApplicationContext is a IResourceLoader, the object could also implement the IApplicationContextAware interface and use the supplied application context directly to load resources, but in general, it's better to use the specialized IResourceLoader interface if that's all that's needed. The code would just be coupled to the resource loading interface, which can be considered a utility interface, and not the whole Spring IApplicationContext interface.
94
The methods GetData and SetData are responsible for retrieving and setting the object that is to be bound to thread local storage and associating it with a name. Clearing the thread local storage is done via the method FreeNamedDataSlot. In Spring.Core is the implementation, CallContextStorage, that directly uses CallContext and also the implementation LogicalThreadContext which by default uses CallContextStorage but can be configured via the static method SetStorage(IThreadStorage). The methods on CallContextStorage and LogicalThreadContext are static. In Spring.Web is the implementation HttpContextStorage which uses the HttpContext to store thread local data and HybridContextStorage that uses HttpContext if within a web environment, i.e. HttpContext.Current ! = null, and CallContext otherwise.
95
Threading and Concurrency Support Spring internally uses LogicalThreadContext as this doesn't require a coupling to the System.Web namespace. In the case of Spring based web applications, Spring's WebSupportModule sets the storage strategy of LogicalThreadContext to be HybridContextStorage.
8.3.1. ISync
is the central interface for all classes that control access to resources from multiple threads. It's a simple interface which has two basic use cases. The first case is to block indefinitely until a condition is met:
ISync
void ConcurrentRun(ISync lock) { lock.Acquire(); // block until condition met try { // ... access shared resources } finally { lock.Release(); } }
The other case is to specify a maximum amount of time to block before the condition is met:
void ImpatientConcurrentRun(ISync lock) { // block for at most 10 milliseconds for condition if ( lock.Attempt(10) ) { try { // ... access shared resources } finally { lock.Release(); } } else { // complain of time out } }
8.3.2. SyncHolder
The SyncHolder class implements the System.IDisposable interface and so provides a way to use an ISync with the using C# keyword: the ISync will be automatically Acquired and then Released on exiting from the block. This should simplify the programming model for code using (!) an ISync:
ISync sync = ... ... using (new SyncHolder(sync)) { // ... code to be executed // holding the ISync lock }
There is also the timed version, a little more cumbersome as you must deal with timeouts:
96
ISync sync = ... long msecs = 100; ... // try to acquire the ISync for msecs milliseconds try { using (new SyncHolder(sync, msecs)) { // ... code to be executed // holding the ISync lock } } catch (TimeoutException) { // deal with failed lock acquisition }
8.3.3. Latch
The Latch class implements the ISync interface and provides an implementation of a latch. A latch is a boolean condition that is set at most once, ever. Once a single release is issued, all acquires will pass. It is similar to a ManualResetEvent initialized unsignalled (Reset) and can only be Set(). A typical use is to act as a start signal for a group of worker threads.
class Boss { Latch _startPermit; void Worker() { // very slow worker initialization ... // ... attach to messaging system // ... connect to database _startPermit.Acquire(); // ... use resources initialized in Mush // ... do real work } void Mush() { _startPermit = new Latch(); for (int i=0; i<10; ++i) { new Thread(new ThreadStart(Worker)).Start(); } // very slow main initialization ... // ... parse configuration // ... initialize other resources used by workers _startPermit.Release(); } }
8.3.4. Semaphore
The Semaphore class implements the ISync interface and provides an implementation of a semaphore. Conceptually, a semaphore maintains a set of permits. Each Acquire() blocks if necessary until a permit is available, and then takes it. Each Release() adds a permit. However, no actual permit objects are used; the Semaphore just keeps a count of the number available and acts accordingly. A typical use is to control access to a pool of shared objects.
class LimitedConcurrentUploader { // ensure we don't exceed maxUpload simultaneous uploads Semaphore _available; public LimitedConcurrentUploader(maxUploads) { _available = new Semaphore(maxUploads); } // no matter how many threads call this method no more
97
98
99
10.2. PathMatcher
Spring.Util.PathMatcher
matches:
fooAbar.txt foo1bar.txt foo_bar.txt foo-bar.txt
matches:
foo.db .db foo foo.bar.db foo.db.db db.db.db
100
matches:
c:/.spring-assemblies c:/.spring-assembliesabcd73xs c:/app/.spring-assembliesabcd73xs c:/app/.spring-assembliesabcd73xs/foo.dll //server/app/.spring-assembliesabcd73xs
101
Spring.NET miscellanea
**/db/**/*.DB
matches:
c:/spring/service/deploy/app/db/foo.DB
If you do not matter about case, you should explicitly tell the Pathmatcher. Back and forward slashes, in the very same cross-platform spirit, are not important:
spring/foo.bar
102
The first argument is the 'root' object that the expression string (2nd argument) will be evaluated against. The third argument is used to support variables in the expression and will be discussed later. Simple usage to get the value of an object property is shown below using the Inventor class. You can find the class listing in section Section 11.4, Classes used in the examples.
Inventor tesla = new Inventor("Nikola Tesla", new DateTime(1856, 7, 9), "Serbian"); tesla.PlaceOfBirth.City = "Smiljan"; string evaluatedName = (string) ExpressionEvaluator.GetValue(tesla, "Name"); string evaluatedCity = (string) ExpressionEvaluator.GetValue(tesla, "PlaceOfBirth.City"));
The value of 'evaluatedName' is 'Nikola Tesla' and that of 'evaluatedCity' is 'Smiljan'. A period is used to navigate the nested properties of the object. Similarly to set the property of an object, say we want to rewrite history and change Tesla's city of birth, we would simply add the following line
ExpressionEvaluator.SetValue(tesla, "PlaceOfBirth.City", "Novi Sad");
103
Expression Evaluation A much better way to evaluate expressions is to parse them once and then evaluate as many times as you want usingExpressionclass. Unlike ExpressionEvaluator, which parses expression every time you invoke one of its methods, Expression class will cache the parsed expression for increased performance. The methods of this class are listed below:
public static IExpression Parse(string expression) public override object Get(object context, IDictionary variables) public override void Set(object context, IDictionary variables, object newValue)
The retrieval of the Name property in the previous example using the Expression class is shown below
IExpression exp = Expression.Parse("Name"); string evaluatedName = (string) exp.GetValue(tesla, null);
The difference in performance between the two approaches, when evaluating the same expression many times, is several orders of magnitude, so you should only use convenience methods of the ExpressionEvaluator class when you are doing one-off expression evaluations. In all other cases you should parse the expression first and then evaluate it as many times as you need. There are a few exception classes to be aware of when using the ExpressionEvaluator. These are InvalidPropertyException, when you refer to a property that doesn't exist, NullValueInNestedPathException, when a null value is encountered when traversing through the nested property list, and ArgumentException and NotSupportedException when you pass in values that are in error in some other manner. The expression language is based on a grammar and uses ANTLR to construct the lexer and parser. Errors relating to bad syntax of the language will be caught at this level of the language implementation. For those interested in the digging deeper into the implementation, the grammar file is named Expression.g and is located in the src directory of the namespace. As a side note, the release version of the ANTLR DLL included with Spring.NET was signed with the Spring.NET key, which means that you should always use the included version of antlr.runtime.dll within your application. Upcoming releases of ANTLR will provide strongly signed assemblies, which will remove this requirement.
double avogadrosNumber = (double) ExpressionEvaluator.GetValue(null, "6.0221415E+23"); int maxValue = (int) ExpressionEvaluator.GetValue(null, "0x7FFFFFFF"); // evals to 2147483647
DateTime birthday = (DateTime) ExpressionEvaluator.GetValue(null, "date('1974/08/24')"); DateTime exactBirthday = (DateTime) ExpressionEvaluator.GetValue(null, " date('19740824T131030', 'yyyyMMddTHHmmss')"); bool trueValue = (bool) ExpressionEvaluator.GetValue(null, "true");
104
Expression Evaluation
Note that the extra backslash character in Tony's Pizza is to satisfy C# escape syntax. Numbers support the use of the negative sign, exponential notation, and decimal points. By default real numbers are parsed using Double.Parse unless the format character "M" or "F" is supplied, in which case Decimal.Parse and Single.Parse would be used respectfully. As shown above, if two arguments are given to the date literal then DateTime.ParseExact will be used. Note that all parse methods of classes that are used internally reference the CultureInfo.InvariantCulture.
For the sharp-eyed, that isn't a typo in the property name for place of birth. The expression uses mixed cases to demonstrate that the evaluation is case insensitive. The contents of arrays and lists are obtained using square bracket notation.
// Inventions Array string invention = (string) ExpressionEvaluator.GetValue(tesla, "Inventions[3]"); // "Induction motor" // Members List string name = (string) ExpressionEvaluator.GetValue(ieee, "Members[0].Name"); // "Nikola Tesla" // List and Array navigation string invention = (string) ExpressionEvaluator.GetValue(ieee, "Members[0].Inventions[6]") // "Wireless communication"
The contents of dictionaries are obtained by specifying the literal key value within the brackets. In this case, because keys for the Officers dictionary are strings, we can specify string literal.
// Officer's Dictionary Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers['president']"; string city = (string) ExpressionEvaluator.GetValue(ieee, "Officers['president'].PlaceOfBirth.City"); // "Idvor" ExpressionEvaluator.SetValue(ieee, "Officers['advisors'][0].PlaceOfBirth.Country", "Croatia");
You may also specify non literal values in place of the quoted literal values by using another expression inside the square brackets such as variable names or static properties/methods on other types. These features are discussed in other sections. Indexers are similarly referenced using square brackets. The following is a small example that shows the use of indexers. Multidimensional indexers are also supported.
public class Bar { private int[] numbers = new int[] {1, 2, 3}; public int this[int index] { get { return numbers[index];} set { numbers[index] = value; }
105
Expression Evaluation
} } Bar b = new Bar(); int val = (int) ExpressionEvaluator.GetValue(bar, "[1]") // evaluated to 2 ExpressionEvaluator.SetValue(bar, "[1]", 3); // set value to 3
11.3.2.1. Defining Arrays, Lists and Dictionaries Inline In addition to accessing arrays, lists and dictionaries by navigating the graph for the context object, Spring.NET Expression Language allows you to define them inline, within the expression. Inline lists are defined by simply enclosing a comma separated list of items with curly brackets:
{1, 2, 3, 4, 5} {'abc', 'xyz'}
If you want to ensure that a strongly typed array is initialized instead of a weakly typed list, you can use array initializer instead:
new int[] {1, 2, 3, 4, 5} new string[] {'abc', 'xyz'}
Dictionary definition syntax is a bit different: you need to use a # prefix to tell expression parser to expect key/ value pairs within the brackets and to specify a comma separated list of key/value pairs within the brackets:
#{'key1' : 'Value 1', 'today' : DateTime.Today} #{1 : 'January', 2 : 'February', 3 : 'March', ...}
Arrays, lists and dictionaries created this way can be used anywhere where arrays, lists and dictionaries obtained from the object graph can be used, which we will see later in the examples. Keep in mind that even though examples above use literals as array/list elements and dictionary keys and values, that's only to simplify the examples -- you can use any valid expression wherever literals are used.
11.3.3. Methods
Methods are invoked using typical C# programming syntax. You may also invoke methods on literals.
//string literal char[] chars = (char[]) ExpressionEvaluator.GetValue(null, "'test'.ToCharArray(1, 2)")) // 't','e'
//date literal int year = (int) ExpressionEvaluator.GetValue(null, "date('1974/08/24').AddYears(31).Year") // 2005 // object usage, calculate age of tesla navigating from the IEEE society. ExpressionEvaluator.GetValue(ieee, "Members[0].GetAge(date('2005-01-01')") // 149 (eww..a big anniversary is coming up ;)
11.3.4. Operators
11.3.4.1. Relational operators The relational operators; equal, not equal, less than, less than or equal, greater than, and greater than or equal are supported using standard operator notation. These operators take into account if the object implements the IComparable interface. Enumerations are also supported but you will need to register the enumeration type, as described in Section Section 11.3.8, Type Registration, in order to use an enumeration value in an expression if it is not contained in the mscorlib.
106
Expression Evaluation
ExpressionEvaluator.GetValue(null, "2 == 2") // true // true
ExpressionEvaluator.GetValue(null, "DateTime.Today <= date('1974-08-24')") // false ExpressionEvaluator.GetValue(null, "'Test' >= 'test'") // true
In addition to standard relational operators, Spring.NET Expression Language supports some additional, very useful operators that were "borrowed" from SQL, such as in, like and between, as well as is and matches operators, which allow you to test if object is of a specific type or if the value matches a regular expression.
ExpressionEvaluator.GetValue(null, "3 in {1, 2, 3, 4, 5}") ExpressionEvaluator.GetValue(null, "'Abc' like '[A-Z]b*'") ExpressionEvaluator.GetValue(null, "'Abc' like '?'") ExpressionEvaluator.GetValue(null, "1 between {1, 5}") // true // true
// true // false
// true
Note that the Visual Basic and not SQL syntax is used for the like operator pattern string. 11.3.4.2. Logical operators The logical operators that are supported are and, or, and not. Their use is demonstrated below
// AND bool falseValue = (bool) ExpressionEvaluator.GetValue(null, "true and false"); //false string expression = @"IsMember('Nikola Tesla') and IsMember('Mihajlo Pupin')"; bool trueValue = (bool) ExpressionEvaluator.GetValue(ieee, expression); //true // OR bool trueValue = (bool) ExpressionEvaluator.GetValue(null, "true or false");
//true
107
Expression Evaluation
bool trueValue = (bool) ExpressionEvaluator.GetValue(ieee, expression); // true // NOT bool falseValue = (bool) ExpressionEvaluator.GetValue(null, "!true"); // AND and NOT string expression = @"IsMember('Nikola Tesla') and !IsMember('Mihajlo Pupin')"; bool falseValue = (bool) ExpressionEvaluator.GetValue(ieee, expression);
11.3.4.3. Bitwise operators The bitwise operators that are supported are and, or, xor and not. Their use is demonstrated below. Note, that the logical and bitwise operators are the same and their interpretation depends if you pass in integral values or boolean values.
// AND int result = (int) ExpressionEvaluator.GetValue(null, "1 and 3"); // 1 & 3 // OR int result = (int) ExpressionEvaluator.GetValue(null, "1 or 3"); // XOR int result = (int) ExpressionEvaluator.GetValue(null, "1 xor 3"); // NOT int result = (int) ExpressionEvaluator.GetValue(null, "!1"); // ~1
// 1 | 3
// 1 ^ 3
11.3.4.4. Mathematical operators The addition operator can be used on numbers, strings and dates. Subtraction can be used on numbers and dates. Multiplication and division can be used only on numbers. Other mathematical operators supported are modulus (%) and exponential power (^). Standard operator precedence is enforced. These operators are demonstrated below
// Addition int two = (int)ExpressionEvaluator.GetValue(null, "1 + 1"); // 2 String testString = (String)ExpressionEvaluator.GetValue(null, "'test' + ' ' + 'string'"); //'test string' DateTime dt = (DateTime)ExpressionEvaluator.GetValue(null, "date('1974-08-24') + 5"); // 8/29/1974 // Subtraction int four = (int) ExpressionEvaluator.GetValue(null, "1 - -3"); //4 Decimal dec = (Decimal) ExpressionEvaluator.GetValue(null, "1000.00m - 1e4"); // 9000.00 TimeSpan ts = (TimeSpan) ExpressionEvaluator.GetValue(null, "date('2004-08-14') date('1974-08-24')"); //10948.00:00:00 // Multiplication int six = (int) ExpressionEvaluator.GetValue(null, "-2 * -3"); // 6 int twentyFour = (int) ExpressionEvaluator.GetValue(null, "2.0 * 3e0 * 4"); // 24 // Division int minusTwo = (int) ExpressionEvaluator.GetValue(null, "6 / -3"); // -2 int one = (int) ExpressionEvaluator.GetValue(null, "8.0 / 4e0 / 2"); // 1 // Modulus int three = (int) ExpressionEvaluator.GetValue(null, "7 % 4"); // 3 int one = (int) ExpressionEvaluator.GetValue(null, "8.0 % 5e0 % 2"); // 1 // Exponent
108
Expression Evaluation
int sixteen = (int) ExpressionEvaluator.GetValue(null, "-2 ^ 4"); // 16 // Operator precedence int minusFortyFive = (int) ExpressionEvaluator.GetValue(null, "1+2-3*8^2/2/2"); // -45
11.3.5. Assignment
Setting of a property is done by using the assignment operator. This would typically be done within a call to GetValue since in the simple case SetValue offers the same functionality. Assignment in this manner is useful when combining multiple operators in an expression list, discussed in the next section. Some examples of assignment are shown below
Inventor inventor = new Inventor(); String aleks = (String) ExpressionEvaluator.GetValue(inventor, "Name = 'Aleksandar Seovic'"); DateTime dt = (DateTime) ExpressionEvaluator.GetValue(inventor, "DOB = date('1974-08-24')"); //Set the vice president of the society Inventor tesla = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers['vp'] = Members[0]");
11.3.7. Types
In many cases, you can reference types by simply specifying type name:
ExpressionEvaluator.GetValue(null, "1 is int") ExpressionEvaluator.GetValue(null, "DateTime.Today") ExpressionEvaluator.GetValue(null, "new string[] {'abc', 'efg'}")
This is possible for all standard types from mscorlib, as well as for any other type that is registered with the TypeRegistry as described in the next section. For all other types, you need to use special T(typeName) expression:
Type dateType = (Type) ExpressionEvaluator.GetValue(null, "T(System.DateTime)") Type evalType = (Type) ExpressionEvaluator.GetValue(null, "T(Spring.Expressions.ExpressionEvaluator, Spring.Core)") bool trueValue = (bool) ExpressionEvaluator.GetValue(tesla, "T(System.DateTime) == DOB.GetType()")
Note
The implementation delegates to Spring's ObjectUtils.ResolveType method for the actual type resolution, which means that the types used within expressions are resolved in the exactly the same way as the types specified in Spring configuration files.
109
Expression Evaluation
11.3.9. Constructors
Constructors can be invoked using the new operator. For classes outside mscorlib you will need to register your types so they can be resolved. Examples of using constructors are shown below:
// simple ctor DateTime dt = (DateTime) ExpressionEvaluator.GetValue(null, "new DateTime(1974, 8, 24)"); // Register Inventor type then create new inventor instance within Add method inside an expression list. // Then return the new count of the Members collection. TypeRegistry.RegisterType(typeof(Inventor)); int three = (int) ExpressionEvaluator.GetValue(ieee.Members, "{ Add(new Inventor('Aleksandar Seovic', date('1974-08-24'), 'Serbian')); Count}"));
As a convenience, Spring.NET also allows you to define named constructor arguments, which are used to set object's properties after instantiation, similar to the way standard .NET attributes work. For example, you could create an instance of the Inventor class and set its Inventions property in a single statement:
Inventor aleks = (Inventor) ExpressionEvaluator.GetValue(null, "new Inventor('Aleksandar Seovic', date('1974-08-24'), 'Serbian', Inventions = {'SPELL'})");
The only rule you have to follow is that named arguments should be specified after standard constructor arguments, just like in the .NET attributes. While we are on the subject, Spring.NET Expression Language also provides a convenient syntax for .NET attribute instance creation. Instead of using standard constructor syntax, you can use a somewhat shorter and more familiar syntax to create an instance of a .NET attribute class:
WebMethodAttribute webMethod = (WebMethodAttribute) ExpressionEvaluator.GetValue(null, "@[WebMethod(true, CacheDuration = 60, Description = 'My Web Method')]");
As you can see, with the exception of the @ prefix, syntax is exactly the same as in C#. Slightly different syntax is not the only thing that differentiates an attribute expression from a standard constructor invocation expression. In addition to that, attribute expression uses slightly different type resolution mechanism and will attempt to load both the specified type name and the specified type name with an Attribute suffix, just like the C# compiler.
11.3.10. Variables
Variables can referenced in the expression using the syntax #variableName. The variables are passed in and out of the expression using the dictionary parameter in ExpressionEvaluator's GetValue or SetValue methods.
public static object GetValue(object root, string expression, IDictionary variables)
110
Expression Evaluation
public static void SetValue(object root, string expression, IDictionary variables, object newValue)
The variable name is the key value of the dictionary. Example usage is shown below;
IDictionary vars = new Hashtable(); vars["newName"] = "Mike Tesla"; ExpressionEvaluator.GetValue(tesla, "Name = #newName", vars));
You can also use the dictionary as a place to store values of the object as they are evaluated inside the expression. For example to change Tesla's first name back again and keep the old value;
ExpressionEvaluator.GetValue(tesla, "{ #oldName = Name; Name = 'Nikola Tesla' }", vars); String oldName = (String)vars["oldName"]; // Mike Tesla
Variable names can also be used inside indexers or maps instead of literal values. For example;
vars["prez"] = "president"; Inventor pupin = (Inventor) ExpressionEvaluator.GetValue(ieee, "Officers[#prez]", vars);
11.3.10.1. The '#this' and '#root' variables There are two special variables that are always defined and can be references within the expression: #this and #root. The #this variable can be used to explicitly refer to the context for the node that is currently being evaluated:
// sets the name of the president and returns its instance ExpressionEvaluator.GetValue(ieee, "Officers['president'].( #this.Name = 'Nikola Tesla'; #this )")
Similarly, the #root variable allows you to refer to the root context for the expression:
// removes president from the Officers dictionary and returns removed instance ExpressionEvaluator.GetValue(ieee, "Officers['president'].( #root.Officers.Remove('president'); #this )")
In this case, the boolean false results in returning the string value 'trueExp'. A less artificial example is shown below
ExpressionEvaluator.SetValue(ieee, "Name", "IEEE"); IDictionary vars = new Hashtable(); vars["queryName"] = "Nikola Tesla"; string expression = @"IsMember(#queryName) ? #queryName + ' is a member of the ' + Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; String queryResultString = (String) ExpressionEvaluator.GetValue(ieee, expression, vars)); // queryResultString = "Nikola Tesla is a member of the IEEE Society"
111
Expression Evaluation For example, let's say that we need a list of the cities where our inventors were born. This could be easily obtained by projecting on the PlaceOfBirth.City property:
IList placesOfBirth = (IList) ExpressionEvaluator.GetValue(ieee, "Members.!{PlaceOfBirth.City}") { 'Smiljan', 'Idvor' } //
As you can see from the examples, projection uses !{projectionExpression} syntax and will return a new list of the same length as the original list but typically with the elements of a different type. On the other hand, selection, which uses ?{projectionExpression} syntax, will filter the list and return a new list containing a subset of the original element list. For example, selection would allow us to easily get a list of Serbian inventors:
IList serbianInventors = (IList) ExpressionEvaluator.GetValue(ieee, "Members.?{Nationality == 'Serbian'}") { tesla, pupin } //
Or we can combine selection and projection to get a list of sonar inventors' names:
IList sonarInventorsNames = (IList) ExpressionEvaluator.GetValue(ieee, "Members.?{'Sonar' in Inventions}.! {Name}") // { 'Mihajlo Pupin' }
As a convenience, Spring.NET Expression Language also supports a special syntax for selecting the first or last match. Unlike regular selection, which will return an empty list if no matches are found, first or last match selection expression will either return an instance of the matched element, or null if no matching elements were found. In order to return a first match you should prefix your selection expression with ^{ instead of ?{, and to return last match you should use ${ prefix:
ExpressionEvaluator.GetValue(ieee, "Members.^{Nationality == 'Serbian'}.Name") ExpressionEvaluator.GetValue(ieee, "Members.${Nationality == 'Serbian'}.Name") // 'Nikola Tesla' // 'Mihajlo Pupin'
Notice that we access the Name property directly on the selection result, because an actual matched instance is returned by the first and last match expression instead of a filtered list.
112
Expression Evaluation context without throwing a NullReferenceException. It will simply return zero in this case, which makes it much safer than standard .NET properties within larger expression.
null
ExpressionEvaluator.GetValue(null, "{1, 5, -3}.count()") ExpressionEvaluator.GetValue(null, "count()") // 0 // 3
11.3.13.2. Sum Aggregator The sum aggregator can be used to calculate a total for the list of numeric values. If numbers within the list are not of the same type or precision, it will automatically perform necessary conversion and the result will be the highest precision type. If any of the collection elements is not a number, this aggregator will throw an InvalidArgumentException.
ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.sum()") // 13 (int) ExpressionEvaluator.GetValue(null, "{5, 5.8, 12.2, 1}.sum()") // 24.0 (double)
11.3.13.3. Average Aggregator The average aggregator will return the average for the collection of numbers. It will use the same type coercion rules, as the sum aggregator in order to be as precise as possible. Just like the sum aggregator, if any of the collection elements is not a number, it will throw an InvalidArgumentException.
ExpressionEvaluator.GetValue(null, "{1, 5, -4, 10}.average()") ExpressionEvaluator.GetValue(null, "{1, 5, -2, 10}.average()") // 3 // 3.5
11.3.13.4. Minimum Aggregator The minimum aggregator will return the smallest item in the list. In order to determine what "the smallest" actually means, this aggregator relies on the assumption that the collection items are of the uniform type and that they implement the IComparable interface. If that is not the case, this aggregator will throw an InvalidArgumentException.
ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.min()") // -3 ExpressionEvaluator.GetValue(null, "{'abc', 'efg', 'xyz'}.min()") // 'abc'
11.3.13.5. Maximum Aggregator The maximum aggregator will return the largest item in the list. In order to determine what "the largest" actually means, this aggregator relies on the assumption that the collection items are of the uniform type and that they implement IComparable interface. If that is not the case, this aggregator will throw an InvalidArgumentException.
ExpressionEvaluator.GetValue(null, "{1, 5, -3, 10}.max()") // 10 ExpressionEvaluator.GetValue(null, "{'abc', 'efg', 'xyz'}.max()") // 'xyz'
11.3.13.6. Non-null Processor A non-null processor is a very simple collection processor that eliminates all null values from the collection.
ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', null, 'abc', 'def', null}.nonNull()") // { 'abc', 'xyz', 'abc', 'def' } ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', null, 'abc', 'def', null}.nonNull().distinct().sort()") // { 'abc', 'def', 'xyz' }
11.3.13.7. Distinct Processor A distinct processor is very useful when you want to ensure that you don't have duplicate items in the collection. It can also accept an optional Boolean argument that will determine whether null values should be included in the results. The default is false, which means that they will not be included.
113
Expression Evaluation
ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.distinct(true).sort()") // { null, 'abc', 'def', 'xyz' } ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.distinct(false).sort()") // { 'abc', 'def', 'xyz' }
11.3.13.8. Sort Processor The sort processor can be used to sort uniform collections of elements that implement IComparable.
ExpressionEvaluator.GetValue(null, "{1.2, 5.5, -3.3}.sort()") // { -3.3, 1.2, 5.5 } ExpressionEvaluator.GetValue(null, "{ 'abc', 'xyz', 'abc', 'def', null, 'def' }.sort()") 'abc', 'def', 'def', 'xyz' } // { null, 'abc',
The sort processor also accepts a boolean value as an argument to determine sort order, sort(false) will sort the collection in decending order. 11.3.13.9. Type Conversion Processor The convert processor can be used to convert a collection of elements to a given Type.
object[] arr = new object[] { "0", 1, 1.1m, "1.1", 1.1f }; decimal[] result = (decimal[]) ExpressionEvaluator.GetValue(arr, "convert(decimal)");
11.3.13.10. Reverse Processor The reverse processor returns the reverse order of elements in the list
object[] arr = new object[] { "0", 1, 2.1m, "3", 4.1f }; object[] result = new ArrayList( (ICollection) ExpressionEvaluator.GetValue(arr, "reverse()") ).ToArray(); // { 4.1f, "3", 2.1m, 1, "0" }
11.3.13.11. OrderBy Processor Collections can be ordered in three ways, an expression, a SpEL lamda expreression, or a delegate.
// orderBy expression IExpression exp = Expression.Parse("orderBy('ToString()')"); object[] input = new object[] { 'b', 1, 2.0, "a" }; object[] ordered = exp.GetValue(input); // { 1, 2.0, "a", 'b' }
// SpEL lambda expressions IExpression exp = Expression.Parse("orderBy({|a,b| $a.ToString().CompareTo($b.ToString())})"); object[] input = new object[] { 'b', 1, 2.0, "a" }; object[] ordered = exp.GetValue(input); // { 1, 2.0, "a", 'b' } Hashtable vars = new Hashtable(); Expression.RegisterFunction( "compare", "{|a,b| $a.ToString().CompareTo($b.ToString())}", vars); exp = Expression.Parse("orderBy(#compare)"); ordered = exp.GetValue(input, vars); // { 1, 2.0, "a", 'b' } // .NET delegate private delegate int CompareCallback(object x, object y); private int CompareObjects(object x, object y) { if (x == y) return 0; return x.ToString().CompareTo(""+y); } Hashtable vars = new Hashtable(); vars["compare"] = new CompareCallback(CompareObjects); IExpression exp = Expression.Parse("orderBy(#compare)"); object[] input = new object[] { 'b', 1, 2.0, "a" }; object[] ordered = exp.GetValue(input); // { 1, 2.0, "a", 'b' }
114
Expression Evaluation 11.3.13.12. User Defined Collection Processor You can register your own collection processor for use in evaluation a collection. Here is an example of a ICollectionProcessor implementation that sums only the even numbers of an integer list
public class IntEvenSumCollectionProcessor : ICollectionProcessor { public object Process(ICollection source, object[] args) { object total = 0d; foreach (object item in source) { if (item != null) { if (NumberUtils.IsInteger(item)) { if ((int)item % 2 == 0) { total = NumberUtils.Add(total, item); } } else { throw new ArgumentException("Sum can only be calculated for a collection of numeric values."); } } } return total; } }
public void DoWork() { Hashtable vars = new Hashtable(); vars["EvenSum"] = new IntEvenSumCollectionProcessor(); int result = (int)ExpressionEvaluator.GetValue(null, "{1, 2, 3, 4}.EvenSum()", vars)); }
// 6
115
Expression Evaluation
functionBody }
For example, you could define a max function and call it like this:
ExpressionEvaluator.GetValue(null, "(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", new Hashtable()) // 25
As you can see, any arguments defined for the expression can be referenced within the function body using a local variable syntax, $varName. Invocation of the function defined using lambda expression is as simple as specifying the comma-separated list of function arguments in parentheses, after the function name. Lambda expressions can be recursive, which means that you can invoke the function within its own body:
ExpressionEvaluator.GetValue(null, "(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))", new Hashtable()) // 120
Notice that in both examples above we had to specify a variables parameter for the GetValue method. This is because lambda expressions are actually nothing more than parameterized variables and we need variables dictionary in order to store them. If you don't specify a valid IDictionary instance for the variables parameter, you will get a runtime exception. Also, in both examples above we used an expression list in order to define and invoke a function in a single expression. However, more likely than not, you will want to define your functions once and then use them within as many expressions as you need. Spring.NET provides an easy way to pre-register your lambda expressions by exposing a static Expression.RegisterFunction method, which takes function name, lambda expression and variables dictionary to register function in as parameters:
IDictionary vars = new Hashtable(); Expression.RegisterFunction("sqrt", "{|n| Math.Sqrt($n)}", vars); Expression.RegisterFunction("fact", "{|n| $n <= 1 ? 1 : $n * #fact($n-1)}", vars);
Once the function registration is done, you can simply evaluate an expression that uses these functions, making sure that the vars dictionary is passed as a parameter to expression evaluation engine:
ExpressionEvaluator.GetValue(null, "#fact(5)", vars) ExpressionEvaluator.GetValue(null, "#sqrt(9)", vars) // 120 // 3
Finally, because lambda expressions are treated as variables, they can be assigned to other variables or passed as parameters to other lambda expressions. In the following example we are defining a delegate function that accepts function f as the first argument and parameter n that will be passed to function f as the second. Then we invoke the functions registered in the previous example, as well as the lambda expression defined inline, through our delegate:
Expression.RegisterFunction("delegate", "{|f, n| $f($n) }", vars); ExpressionEvaluator.GetValue(null, "#delegate(#sqrt, 4)", vars) // 2 ExpressionEvaluator.GetValue(null, "#delegate(#fact, 5)", vars) // 120 ExpressionEvaluator.GetValue(null, "#delegate({|n| $n ^ 2 }, 5)", vars)
// 25
While this particular example is not particularly useful, it does demonstrate that lambda expressions are indeed treated as nothing more than parameterized variables, which is important to remember.
116
Expression Evaluation
public void DoWork() { Hashtable vars = new Hashtable(); vars["max"] = new DoubleFunctionTwoArgs(Max); double result = (double) ExpressionEvaluator.GetValue(null, "#max(5,25)", vars); }
// 25
117
Expression Evaluation
// not very accurate, but it will do the job ;-) return on.Year - dob.Year; } } public class Place { public string City; public string Country; } public class Society { public string Name; public static string Advisors = "advisors"; public static string President = "president"; private IList members = new ArrayList(); private IDictionary officers = new Hashtable(); public IList Members { get { return members; } } public IDictionary Officers { get { return officers; } } public bool IsMember(string name) { bool found = false; foreach (Inventor inventor in members) { if (inventor.Name == name) { found = true; break; } } return found; } }
The code listings in this chapter use instances of the data populated with the following information.
Inventor tesla = new Inventor("Nikola Tesla", new DateTime(1856, 7, 9), "Serbian"); tesla.Inventions = new string[] { "Telephone repeater", "Rotating magnetic field principle", "Polyphase alternating-current system", "Induction motor", "Alternating-current power transmission", "Tesla coil transformer", "Wireless communication", "Radio", "Fluorescent lights" }; tesla.PlaceOfBirth.City = "Smiljan"; Inventor pupin = new Inventor("Mihajlo Pupin", new DateTime(1854, 10, 9), "Serbian"); pupin.Inventions = new string[] {"Long distance telephony & telegraphy", "Secondary X-Ray radiation", "Sonar"}; pupin.PlaceOfBirth.City = "Idvor"; pupin.PlaceOfBirth.Country = "Serbia"; Society ieee = new Society(); ieee.Members.Add(tesla); ieee.Members.Add(pupin); ieee.Officers["president"] = pupin; ieee.Officers["advisors"] = new Inventor[] {tesla, pupin};
118
119
Validation Framework
<object type="TripForm.aspx" parent="standardPage"> <property name="TripValidator" ref="tripValidator" /> </object> <v:group id="tripValidator"> <v:required id="departureAirportValidator" test="StartingFrom.AirportCode"> <v:message id="error.departureAirport.required" providers="departureAirportErrors, validationSummary"/ > </v:required> <v:group id="destinationAirportValidator"> <v:required test="ReturningFrom.AirportCode"> <v:message id="error.destinationAirport.required" providers="destinationAirportErrors, validationSummary"/> </v:required> <v:condition test="ReturningFrom.AirportCode != StartingFrom.AirportCode" when="ReturningFrom.AirportCode != ''"> <v:message id="error.destinationAirport.sameAsDeparture" providers="destinationAirportErrors, validationSummary"/> </v:condition> </v:group> <v:group id="departureDateValidator"> <v:required test="StartingFrom.Date"> <v:message id="error.departureDate.required" providers="departureDateErrors, validationSummary"/> </v:required> <v:condition test="StartingFrom.Date >= DateTime.Today" when="StartingFrom.Date != DateTime.MinValue"> <v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary"/> </v:condition> </v:group> <v:group id="returnDateValidator" when="Mode == 'RoundTrip'"> <v:required test="ReturningFrom.Date"> <v:message id="error.returnDate.required" providers="returnDateErrors, validationSummary"/> </v:required> <v:condition test="ReturningFrom.Date >= StartingFrom.Date" when="ReturningFrom.Date != DateTime.MinValue"> <v:message id="error.returnDate.beforeDeparture" providers="returnDateErrors, validationSummary"/> </v:condition> </v:group> </v:group> </objects>
There are a few things to note in the example above: You need to reference the validation schema by adding a xmlns:v="http://www.springframework.net/ validation" namespace declaration to the root element. You can mix standard object definitions and validator definitions in the same configuration file as long as both schemas are referenced. The Validator defined in the configuration file is identified by and id attribute and can be referenced in the standard Spring way, i.e. the injection of tripValidator into TripForm.aspx page definition in the first <object> tag above. The validation framework uses Spring's powerful expression evaluation engine to evaluate both validation rules and applicability conditions for the validator. As such, any valid Spring expression can be specified within the test and when attributes of any validator. The example above shows many of the features of the framework, so let's discuss them one by one in the following sections.
120
Validation Framework
One thing to remember is that a validator group is a validator like any other and can be used anywhere validator is expected. You can nest groups within other groups and reference them using validator reference syntax (described later), so they really allow you to structure your validation rules in the most reusable way.
12.4. Validators
Ultimately, you will have one or more validator definitions for each piece of data that you want to validate. Spring.NET has several built-in validators that are sufficient for most validations, even fairly complex ones. The framework is extensible so you can write your own custom validators and use them in the same way as the builtin ones.
In this example the StartingFrom property of the Trip object is compared to see if it is later than the current date, i.e. DateTime but only when the date has been set (the initial value of StartingFrom.Date was set to DateTime.MinValue). The condition validator could be considered "the mother of all validators". You can use it to achieve almost anything that can be achieved by using other validator types, but in some cases the test expression might be very
121
Validation Framework complex, which is why you should use more specific validator type if possible. However, condition validator is still your best bet if you need to check whether particular value belongs to a particular range, or perform a similar test, as those conditions are fairly easy to write.
Note
Keep in mind that Spring.NET Validation Framework typically works with domain objects. This is after data binding from the controls has been performed so that the object being validated is strongly typed. This means that you can easily compare numbers and dates without having to worry if the string representation is comparable.
The specific tests done to determine if the required value is set is listed below Table 12.2. Rules to determine if required value is valid System.Type System.Type System.String system.DateTime One of the number types. System.Char Any reference type other than System.String Test Type exists not null or an empty string
Not System.DateTime.MinValue and not system.DateTime.MaxV not zero Not System.Char.MinValue or whitespace. not null
Required validator is also one of the most commonly used ones, and it is much more powerful than the ASP.NET Required validator, because it works with many other data types other than strings. For example, it will allow you to validate DateTime instances (both MinValue and MaxValue return false), integer and decimal numbers, as well as any reference type, in which case it returns true for a non-null value and false for {{null}}s. The test attribute for the required validator will typically specify an expression that resolves to a property of a domain object, but it could be any valid expression that returns a value, including a method call.
122
Validation Framework
<v:regex id="id" test="valueToEvaluate" expression="regularExpressionToMatch" when="applicabilityCondition" parent="parentVal <v:property name="Options" value="regexOptions"/> actions </v:regex>
Regular expression validator is very useful when validating values that need to conform to some predefined format, such as telephone numbers, email addresses, URLs, etc.
Note
Note that current behavior limits the Regular Expression Validator to expressions to being full matches, i.e., ^(expression)$, thus limiting functionality. To not change this behavior in a point release, a property AllowPartialMatching has been added in 1.3.1 to support the correct behavior. This property will be removed for next major/minor version and implementation will be fixed to get the intented behavior.
Generic validator allows you to plug in your custom validator by specifying its type name. Custom validators are very simple to implement, because all you need to do is extend BaseValidator class and implement abstract bool Validate(object objectToValidate) method. Your implementation simply needs to return true if it determines that object is valid, or false otherwise
Validators within this group will only be evaluated for round trips.
123
Validation Framework
Note
You should also note that you can compare enums using the string value of the enumeration. You can also use fully qualified enum name, such as:
Mode == TripMode.RoundTrip
However, in this case you need to make sure that alias for the TripMode enum type is registered using Spring's standard type aliasing mechanism.
There are several things that you have to be aware of when dealing with error messages: id is used to look up the error message in the appropriate Spring.NET message source. providers specifies a comma separated list of "error buckets" particular error message should be added to. These "buckets" will later be used by the particular presentation technology in order to display error messages as necessary. a message can have zero or more parameters. Each parameter is an expression that will be resolved using current validation context and the resolved values will be passed as parameters to IMessageSource.GetMessage method, which will return the fully resolved message.
This will throw an exception of the type ValidationException and you can access error information via its ValidationErrors property. To throw your own custom exception, provide a SpEL fragment that instantiates the custom exception.
124
Validation Framework
<v:exception throw='new System.InvalidOperationException("invalid")'/>
Generic actions can be used to perform all kinds of validation actions. In simple cases, such as in the example above where we turn control's visibility on or off depending on the validation result, you can use the built-in ExpressionAction class and simply specify expressions to be evaluated based on the validator result. In other situations you may want to create your own action implementation, which is fairly simple thing to do all you need to do is implement IValidationAction interface:
public interface IValidationAction { /// <summary> /// Executes the action. /// </summary> /// <param name="isValid">Whether associated validator is valid or not.</param> /// <param name="validationContext">Validation context.</param> /// <param name="contextParams">Additional context parameters.</param> /// <param name="errors">Validation errors container.</param> void Execute(bool isValid, object validationContext, IDictionary contextParams, ValidationErrors errors); }
125
Validation Framework It is as simple as that you define validation rules for ObjectC separately and reference them from within other validation groups. Important thing to realize that in most cases you will also want to "narrow" the context for the referenced validator, typically by specifying the name of the property that holds referenced object. In the example above, ObjectA.MyObjectC and ObjectB.ObjectCProperty are both of type ObjectC, which objectC.validator expects to receive as the validation context.
ValidatorGroup userInfoValidator = new ValidatorGroup(); userInfoValidator.Validators .Add(new RequiredValidator("Name", null)); userInfoValidator.Validators .Add(new RequiredValidator("Password", null)); ValidationErrors errors = new ValidationErrors(); bool userInfoIsValid = userInfoValidator.Validate(userInfo, errors);
No matter if you create your validators programmatically or declaratively, you can invoke them in service side code via the 'Validate' method shown above and then handle error conditions. Spring provides AOP parameter validation advice as part of ithe aspect library which may also be useful for performing server-side validation.
Once that's done, you need to perform validation in one or more of the page event handlers, which typically looks similar to this:
public void SearchForFlights(object sender, EventArgs e) {
126
Validation Framework
if (Validate(Controller.Trip, tripValidator)) { Process.SetView(Controller.SearchForFlights()); } }
Note
Keep in mind that your ASP.NET page needs to extend Spring.Web.UI.Page in order for the code above to work. Finally, you need to define where validation errors should be displayed by adding one or more <spring:validationError/> and <spring:validationSummary/> controls to the ASP.NET form:
<!-- code snippet taken from the SpringAir sample application --> <%@ Page Language="c#" MasterPageFile="~/Web/ StandardTemplate.master" Inherits="TripForm" CodeFile="TripForm.aspx.cs" %> <!-- render all error messages sent to 'errorSummary' provider --> <spring:ValidationSummary ID="summary" Provider="errorSummary" runat="server" /> <table> <tr> <td> <asp:Label ID="leavingFrom" runat="server" /></td> <td> <asp:DropDownList ID="leavingFromAirportCode" AutoCallBack="true" runat="server" /> <!-- render error messages sent to 'departureAirportErrors' provider --> <spring:ValidationError ID="leavingFromError" Provider="departureAirportErrors" runat="server" /> </td> <td> <asp:Label ID="goingTo" runat="server" /></td> <td> <asp:DropDownList ID="goingToAirportCode" AutoCallBack="true" runat="server" /> <!-- render error messages sent to 'destinationAirportErrors' provider --> <spring:ValidationError ID="goingToError" Provider="destinationAirportErrors" runat="server" /> </td> </tr> </table>
Block Spring.Web.Validation.DivValidationErrorsRenderer as list items within a <div> tag. Default Renders validation errors renderer for <spring:validationSummary> control. Inline Spring.Web.Validation.SpanValidationErrorsRenderer within a <span> tag. Default renderer for Renders validation errors <spring:validationError> control. Icon
Spring.Web.Validation.IconValidationErrorsRenderer as error icon, with error messages displayed Renders validation errors
in a tooltip. Best option when saving screen real estate is important. These three error renderers should be sufficient for most applications, but in case you want to display errors in some other way you can write your own renderer by implementing Spring.Web.Validation.IValidationErrorsRenderer interface:
127
Validation Framework
namespace Spring.Web.Validation { /// <summary> /// This interface should be implemented by all validation errors renderers. /// </summary> /// <remarks> /// <para> /// Validation errors renderers are used to decouple rendering behavior from the /// validation errors controls such as <see cref="ValidationError"/> and /// <see cref="ValidationSummary"/>. /// </para> /// <para> /// This allows users to change how validation errors are rendered by simply plugging in /// appropriate renderer implementation into the validation errors controls using /// Spring.NET dependency injection. /// </para> /// </remarks> public interface IValidationErrorsRenderer { /// <summary> /// Renders validation errors using specified <see cref="HtmlTextWriter"/>. /// </summary> /// <param name="page">Web form instance.</param> /// <param name="writer">An HTML writer to use.</param> /// <param name="errors">The list of validation errors.</param> void RenderErrors(Page page, HtmlTextWriter writer, IList errors); } }
12.8.1.1. Configuring which Error Renderer to use. The best part of the errors renderer mechanism is that you can easily change it across the application by modifying configuration templates for <spring:validationSummary> and <spring:validationError> controls:
<!-- Validation errors renderer configuration --> <object id="Spring.Web.UI.Controls.ValidationError" abstract="true"> <property name="Renderer"> <object type="Spring.Web.Validation.IconValidationErrorsRenderer, Spring.Web"> <property name="IconSrc" value="validation-error.gif"/> </object> </property> </object> <object id="Spring.Web.UI.Controls.ValidationSummary" abstract="true"> <property name="Renderer"> <object type="Spring.Web.Validation.DivValidationErrorsRenderer, Spring.Web"> <property name="CssClass" value="validationError"/> </object> </property> </object>
128
Validation Framework
<spring:ValidationError ID="leavingFromError" Provider="departureAirportErrors" runat="server" />
<script language="C#" runat="server"> public void SearchForFlights(object sender, EventArgs e) { if (Validate(Controller.Trip, tripValidator)) { Process.SetView(Controller.SearchForFlights()); } } </script>
If you need to render errors from a UserControl not in the hierarchy of your Validation control, you can specify the name of the target validation container control:
// ASPX / ASCX Template Code <%@ Page Language="c#" %> <%@ Register TagPrefix="user" TagName="EmployeeInfoEditor" Src="EmployeeInfoEditor.ascx" %> <spring:ValidationSummary ID="summary" Provider="summary" ValidationContainerName="editor" runat="server" /> <user:EmployeeInfoEditor ID="editor" runat="server" />
129
130
Aspect Oriented Programming with Spring.NET Target object: Object containing the joinpoint. Also referred to as advised or proxied object. AOP proxy: Object created by the AOP framework, including advice. In Spring.NET, an AOP proxy is a dynamic proxy that uses IL code generated at runtime. Weaving: Assembling aspects to create an advised object. This can be done at compile time (using the GripperLoom.NET compiler, for example), or at runtime. Spring.NET performs weaving at runtime. Different advice types include: Around advice: Advice that surrounds a joinpoint such as a method invocation. This is the most powerful kind of advice. Around advice will perform custom behaviour before and after the method invocation. They are responsible for choosing whether to proceed to the joinpoint or to shortcut executing by returning their own return value or throwing an exception. Before advice: Advice that executes before a joinpoint, but which does not have the ability to prevent execution flow proceeding to the joinpoint (unless it throws an exception). Throws advice: Advice to be executed if a method throws an exception. Spring.NET provides strongly typed throws advice, so you can write code that catches the exception (and subclasses) you're interested in, without needing to cast from Exception. After returning advice: Advice to be executed after a joinpoint completes normally: for example, if a method returns without throwing an exception. Spring.NET provides a full range of advice types. We recommend that you use the least powerful advice type that can implement the required behaviour. For example, if you need only to update a cache with the return value of a method, you are better off implementing an after returning advice than an around advice, although an around advice can accomplish the same thing. Using the most specific advice type provides a simpler programming model with less potential for errors. For example, you don't need to invoke the proceed() method on the IMethodInvocation used for around advice, and hence can't fail to invoke it. The pointcut concept is the key to AOP, distinguishing AOP from older technologies offering interception. Pointcuts enable advice to be targeted independently of the OO hierarchy. For example, an around advice providing declarative transaction management can be applied to a set of methods spanning multiple objects. Thus pointcuts provide the structural element of AOP.
131
Aspect Oriented Programming with Spring.NET tag interface. Advices supported out the box are IMethodInterceptor ; IThrowsAdvice; IBeforeAdvice; and IAfterReturningAdvice. We'll discuss advice types in detail below. Spring.NET provides a .NET translation of the Java interfaces defined by the AOP Alliance . Around advice must implement the AOP Alliance AopAlliance.Interceptr.IMethodInterceptor interface. Whilst there is wide support for the AOP Alliance in Java, Spring.NET is currently the only .NET AOP framework that makes use of these interfaces. In the short term, this will provide a consistent programming model for those doing development in both .NET and Java, and in the longer term, we hope to see more .NET projects adopt the AOP Alliance interfaces. The aim of Spring.NET AOP support is not to provide a comprehensive AOP implementation on par with the functionality available in AspectJ. However, Spring.NET AOP provides an excellent solution to most problems in .NET applications that are amenable to AOP. Thus, it is common to see Spring.NET's AOP functionality used in conjunction with a Spring.NET IoC container. AOP advice is specified using normal object definition syntax (although this allows powerful "autoproxying" capabilities); advice and pointcuts are themselves managed by Spring.NET IoC.
132
13.2.1. Concepts
Spring.NET's pointcut model enables pointcut reuse independent of advice types. It's possible to target different advice using the same pointcut. The Spring.Aop.IPointcut interface is the central interface, used to target advices to particular types and methods. The complete interface is shown below:
public interface IPointcut { ITypeFilter TypeFilter { get; } IMethodMatcher MethodMatcher { get; } }
Splitting the IPointcut interface into two parts allows reuse of type and method matching parts, and fine-grained composition operations (such as performing a "union" with another method matcher). The ITypeFilter interface is used to restrict the pointcut to a given set of target classes. If the Matches() method always returns true, all target types will be matched:
public interface ITypeFilter { bool Matches(Type type); }
The IMethodMatcher interface is normally more important. The complete interface is shown below:
public interface IMethodMatcher { bool IsRuntime { get; } bool Matches(MethodInfo method, Type targetType); bool Matches(MethodInfo method, Type targetType, object[] args); }
The Matches(MethodInfo, Type) method is used to test whether this pointcut will ever match a given method on a target type. This evaluation can be performed when an AOP proxy is created, to avoid the need for a test on every method invocation. If the 2-argument matches method returns true for a given method, and the IsRuntime property for the IMethodMatcher returns true, the 3-argument matches method will be invoked on every method invocation. This enables a pointcut to look at the arguments passed to the method invocation immediately before the target advice is to execute. Most IMethodMatchers are static, meaning that their IsRuntime property returns false. In this case, the 3-argument Matches method will never be invoked. Whenever possible, try to make pointcuts static... this allows the AOP framework to cache the results of pointcut evaluation when an AOP proxy is created.
133
Aspect Oriented Programming with Spring.NET Pointcuts can be composed using the static methods in the Spring.Aop.Support.Pointcuts class, or using the ComposablePointcut class in the same namespace.
As a convenience, Spring provides the RegularExpressionMethodPointcutAdvisor class that allows us to reference an IAdvice instance as well as defining the pointcut rules (remember that an IAdvice instance can be an interceptor, before advice, throws advice etc.) This simplifies wiring, as the one object serves as both pointcut and advisor, as shown below:
<object id="settersAndAbsquatulateAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop"> <property name="advice"> <ref local="objectNameOfAopAllianceInterceptor"/> </property> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </object>
The RegularExpressionMethodPointcutAdvisor class can be used with any Advice type. If you only have one pattern you can use the property name pattern and specify a single value instead of using the property name patterns and specifying a list.
134
Aspect Oriented Programming with Spring.NET You may also specify a Regex object from the System.Text.RegularExpressions namespace. The built in RegexConverter class will perform the conversion. See Section 6.4, Built-in TypeConverters for more information on Spring's build in type converters. The Regex object is created as any other object within the IoC container. Using an inner-object definition for the Regex object is a handy way to keep the definition close to the PointcutAdvisor declaration. Note that the class SdkRegularExpressionMethodPointcut has a DefaultOptions property to set the regular expression options if they are not explicitly specified in the constructor.
where aspNetCacheAdvice is an implementation of an IMethodInterceptor that caches method return values. See the SDK docs for Spring.Aop.Advice.CacheAdvice for more information on this particular advice. As a convenience the class AttributeMatchMethodPointcutAdvisor is provided to defining an attribute based Advisor as a somewhat shorter alternative to using the generic DefaultPointcutAdvisor. An example is shown below.
<object id="AspNetCacheAdvice" type="Spring.Aop.Support.AttributeMatchMethodPointcutAdvisor, Spring.Aop"> <property name="advice"> <object type="Aspect.AspNetCacheAdvice, Aspect"/> </property> <property name="attribute" value="Framework.AspNetCacheAttribute, Framework" /> </object>
13.2.3.2. Dynamic Pointcuts Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account method arguments, as well as static information. This means that they must be evaluated with every method invocation; the result cannot be cached, as arguments will vary. The main example is the control flow pointcut.
135
Aspect Oriented Programming with Spring.NET and not when ClassA.A() is executed from another call stack. Control flow pointcuts are specified using the Spring.Aop.Support.ControlFlowPointcut class.
Note
Control flow pointcuts are significantly more expensive to evaluate at runtime than even other dynamic pointcuts. When using control flow point cuts some attention should be paid to the fact that at runtime the JIT compiler can inline the methods, typically for increased performance, but with the consequence that the method no longer appears in the current call stack. This is because inlining takes the callee's IL code and inserts it into the caller's IL code effectively removing the method call. The information returned from System.Diagnostics.StackTrace, used in the implementation of ControlFlowPointcut is subject to these optimizations and therefore a control flow pointcut will not match if the method has been inlined. Generally speaking, a method will be a candidate for inlining when its code is 'small', just a few lines of code (less than 32 bytes of IL). For some interesting reading on this process read David Notario's blog entries (JIT Optimizations I and JIT Optimizations II). Additionally, when an assembly is compiled with a Release configuration the assembly metadata instructs the CLR to enable JIT optimizations. When compiled with a Debug configuration the CLR will disable (some?) these optimizations. Empirically, method inlining is turned off in a Debug configuration. The way to ensure that your control flow pointcut will not be overlooked because of method inlining is to apply the System.Runtime.CompilerServices.MethodImplAttribute attribute with the value MethodImplOptions.NoInlining. In this (somewhat artificial) simple example, if the code is compiled in release mode it will not match a control flow pointcut for the method "GetAge".
public int GetAge(IPerson person) { return person.GetAge(); }
However, applying the attributes as shown below will prevent the method from being inlined even in a release build.
[MethodImpl(MethodImplOptions.NoInlining)] public int GetAge(IPerson person) { return person.GetAge(); }
136
The IMethodInvocation argument to the Invoke() method exposes the method being invoked; the target joinpoint; the AOP proxy; and the arguments to the method. The Invoke() method should return the invocation's result: the return value of the joinpoint. A simple IMethodInterceptor implementation looks as follows:
public class DebugInterceptor : IMethodInterceptor { public object Invoke(IMethodInvocation invocation) { Console.WriteLine("Before: invocation=[{0}]", invocation); object rval = invocation.Proceed(); Console.WriteLine("Invocation returned"); return rval; } }
Note the call to the IMethodInvocation's Proceed() method. This proceeds down the interceptor chain towards the joinpoint. Most interceptors will invoke this method, and return its return value. However, an IMethodInterceptor, like any around advice, can return a different value or throw an exception rather than invoke the Proceed() method. However, you don't want to do this without good reason!
137
Aspect Oriented Programming with Spring.NET 13.3.2.2. Before advice A simpler advice type is a before advice. This does not need an IMethodInvocation object, since it will only be called before entering the method. The main advantage of a before advice is that there is no need to invoke the Proceed()method, and therefore no possibility of inadvertently failing to proceed down the interceptor chain. The IMethodBeforeAdvice interface is shown below.
public interface IMethodBeforeAdvice : IBeforeAdvice { void Before(MethodInfo method, object[] args, object target); }
Note the return type is void. Before advice can insert custom behaviour before the joinpoint executes, but cannot change the return value. If a before advice throws an exception, this will abort further execution of the interceptor chain. The exception will propagate back up the interceptor chain. If it is unchecked, or on the signature of the invoked method, it will be passed directly to the client; otherwise it will be wrapped in an unchecked exception by the AOP proxy. An example of a before advice in Spring.NET, which counts all methods that return normally:
public class CountingBeforeAdvice : IMethodBeforeAdvice { private int count; public void Before(MethodInfo method, object[] args, object target) { ++count; } public int Count { get { return count; } } }
Before advice can be used with any pointcut. 13.3.2.3. Throws advice Throws advice is invoked after the return of the joinpoint if the joinpoint threw an exception. The Spring.Aop.IThrowsAdvice interface does not contain any methods: it is a tag interface identifying that the implementing advice object implements one or more typed throws advice methods. These throws advice methods must be of the form:
AfterThrowing([MethodInfo method, Object[] args, Object target], Exception subclass)
Throws-advice methods must be named 'AfterThrowing'. The return value will be ignored by the Spring.NET AOP framework, so it is typically void. With regard to the method arguments, only the last argument is required. Thus there are exactly one or four arguments, depending on whether the advice method is interested in the method, method arguments and the target object. The following method snippets show examples of throws advice. This advice will be invoked if a RemotingException is thrown (including subclasses):
public class RemoteThrowsAdvice : IThrowsAdvice { public void AfterThrowing(RemotingException ex) { // Do something with remoting exception
138
The following advice is invoked if a SqlException is thrown. Unlike the above advice, it declares 4 arguments, so that it has access to the invoked method, method arguments and target object:
public class SqlExceptionThrowsAdviceWithArguments : IThrowsAdvice { public void AfterThrowing(MethodInfo method, object[] args, object target, SqlException ex) { // Do something will all arguments } }
The final example illustrates how these two methods could be used in a single class, which handles both RemotingException and SqlException. Any number of throws advice methods can be combined in a single class, as can be seen in the following example.
public class CombinedThrowsAdvice : IThrowsAdvice { public void AfterThrowing(RemotingException ex) // Do something with remoting exception } {
public void AfterThrowing(MethodInfo method, object[] args, object target, SqlException ex) { // Do something will all arguments } }
Finally, it is worth stating that throws advice is only applied to the actual exception being thrown. What does this mean? Well, it means that if you have defined some throws advice that handles RemotingExceptions, the applicable AfterThrowing method will only be invoked if the type of the thrown exception is RemotingException... if a RemotingException has been thrown and subsequently wrapped inside another exception before the exception bubbles up to the throws advice interceptor, then the throws advice that handles RemotingExceptions will never be called. Consider a business method that is advised by throws advice that handles RemotingExceptions; if during the course of a method invocation said business method throws a RemoteException... and subsequently wraps said RemotingException inside a business-specific BadConnectionException (see the code snippet below) before throwing the exception, then the throws advice will never be able to respond to the RemotingException... because all the throws advice sees is a BadConnectionException. The fact that the RemotingException is wrapped up inside the BadConnectionException is immaterial.
public void BusinessMethod() { try { // do some business operation... } catch (RemotingException ex) { throw new BadConnectionException("Couldn't connect.", ex); } }
Note
Please note that throws advice can be used with any pointcut. 13.3.2.4. After Returning advice An after returning advice in Spring.NET must implement the Spring.Aop.IAfterReturningAdvice interface, shown below:
139
An after returning advice has access to the return value (which it cannot modify), invoked method, methods arguments and target. The following after returning advice counts all successful method invocations that have not thrown exceptions:
public class CountingAfterReturningAdvice : IAfterReturningAdvice { private int count; public void AfterReturning(object returnValue, MethodBase m, object[] args, object target) { ++count; } public int Count { get { return count; } } }
This advice doesn't change the execution path. If it throws an exception, this will be thrown up the interceptor chain instead of the return value.
Note
Please note that after-returning advice can be used with any pointcut. 13.3.2.5. Advice Ordering When multiple pieces of advice want to run on the same joinpoint the precedence is determined by having the advice implement the IOrdered interface or by specifying order information on an advisor. 13.3.2.6. Introduction advice Spring.NET allows you to add new methods and properties to an advised class. This would typically be done when the functionality you wish to add is a crosscutting concern and want to introduce this functionality as a change to the static structure of the class hierarchy. For example, you may want to cast objects to the introduction interface in your code. Introductions are also a means to emulate multiple inheritance. Introduction advice is defined by using a normal interface declaration that implements the tag interface IAdvice.
Note
The need for implementing this marker interface will likely be removed in future versions. As an example, consider the interface IAuditable that describes the last modified time of an object.
public interface IAuditable : IAdvice { DateTime LastModifiedDate { get; set; } }
where
public interface IAdvice { }
140
Aspect Oriented Programming with Spring.NET Access to the advised object can be obtained by implementing the interface ITargetAware
public interface ITargetAware { IAopProxy TargetProxy { set; } }
with the IAopProxy reference providing a layer of indirection through which the advised object can be accessed.
public interface IAopProxy { object GetProxy(); }
Introduction advice is not associated with a pointcut, since it applies at the class and not the method level. As such, introductions use their own subclass of the interface IAdvisor, namely IIntroductionAdvisor, to specify the types that the introduction can be applied to.
public interface IIntroductionAdvisor : IAdvisor { ITypeFilter TypeFilter { get; } Type[] Interfaces { get; } void ValidateInterfaces(); }
The TypeFilter property returns the filter that determines which target classes this introduction should apply to. The Interfaces property returns the interfaces introduced by this advisor.
141
Aspect Oriented Programming with Spring.NET The ValidateInterfaces() method is used internally to see if the introduced interfaces can be implemented by the introduction advice. Spring.NET provides a default implementation of this interface (the DefaultIntroductionAdvisor class) that should be sufficient for the majority of situations when you need to use introductions. The most simple implementation of an introduction advisor is a subclass that simply passes a new instance the base constructor. Passing a new instance is important since we want a new instance of the mixin classed used for each advised object.
public class AuditableAdvisor : DefaultIntroductionAdvisor { public AuditableAdvisor() : base(new AuditableMixin()) { } }
Other constructors let you explicitly specify the interfaces of the class that will be introduced. See the SDK documentation for more details. We can apply this advisor Programatically, using the IAdvised.AddIntroduction(), method, or (the recommended way) in XML configuration using the IntroductionNames property on ProxyFactoryObject, which will be discussed later. Unlike the AOP implementation in the Spring Framework for Java, introduction advice in Spring.NET is not implemented as a specialized type of interception advice. The advantage of this approach is that introductions are not kept in the interceptor chain, which allows some significant performance optimizations. When a method is called that has no interceptors, a direct call is used instead of reflection regardless of whether the target method is on the target object itself or one of the introductions. This means that introduced methods perform the same as target object methods, which could be useful for adding introductions to fine grained objects. The disadvantage is that if the mixin functionality would benefit from having access to the calling stack, it is not available. Introductions with this functionality will be addressed in a future version of Spring.NET AOP.
142
Aspect Oriented Programming with Spring.NET introduces a layer of indirection, enabling it to create objects of a different type - Section 5.3.9, Setting a reference using the members of other objects and classes.). The basic way to create an AOP proxy in Spring.NET is to use the Spring.Aop.Framework.ProxyFactoryObject class. This gives complete control over ordering and application of the pointcuts and advice that will apply to your business objects. However, there are simpler options that are preferable if you don't need such control.
13.5.1. Basics
The ProxyFactoryObject, like other Spring.NET IFactoryObject implementations, introduces a level of indirection. If you define a ProxyFactoryObject with name foo, what objects referencing foo see is not the ProxyFactoryObject instance itself, but an object created by the ProxyFactoryObject's implementation of the GetObject() method. This method will create an AOP proxy wrapping a target object. One of the most important benefits of using a ProxyFactoryObject or other IoC-aware classes that create AOP proxies, is that it means that advice and pointcuts can also be managed by IoC. This is a powerful feature, enabling certain approaches that are hard to achieve with other AOP frameworks. For example, an advice may itself reference application objects (besides the target, which should be available in any AOP framework), benefiting from all the pluggability provided by Dependency Injection.
143
Aspect Oriented Programming with Spring.NET InterceptorNames: string array of IAdvisor, interceptor or other advice names to apply. Ordering is significant... first come, first served that is. The first interceptor in the list will be the first to be able to interceptor the invocation (assuming it concerns a regular MethodInterceptor or BeforeAdvice). The names are object names in the current container, including objectnames from container hierarchies. You can't mention object references here since doing so would result in the ProxyFactoryObject ignoring the singleton setting of the advise. IntroductionNames: The names of objects in the container that will be used as introductions to the target object. If the object referred to by name does not implement the IIntroductionAdvisor it will be passed to the default constructor of DefaultIntroductionAdvisor and all of the objects interfaces will be added to the target object. Objects that implement the IIntroductionAdvisor interface will be used as is, giving you a finer level of control over what interfaces you may want to expose and the types for which they will be matched against. IsSingleton: whether or not the factory should return a single proxy object, no matter how often the GetObject() method is called. Several IFactoryObject implementations offer such a method. The default value is true. If you would like to be able to apply advice on a per-proxy object basis, use a IsSingleton value of false and a IsFrozen value of false. If you want to use stateful advice--for example, for stateful mixins-use prototype advices along with a IsSingleton value of false.
Note that the InterceptorNames property takes a list of strings: the object names of the interceptor or advisors in the current context. Advisors, interceptors, before, after returning and throws advice objects can be used. The ordering of advisors is significant.
144
Aspect Oriented Programming with Spring.NET You might be wondering why the list doesn't hold object references. The reason for this is that 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's necessary to be able to obtain an instance of the prototype from the context; holding a reference isn't sufficient. The "person" object definition above can be used in place of an IPerson implementation, as follows:
IPerson person = (IPerson) factory.GetObject("person");
Other objects in the same IoC context can express a strongly typed dependency on it, as with an ordinary .NET object:
<object id="personUser" type="MyCompany.MyApp.PersonUser, MyCompany"> <property name="person" ref="person"/> </object>
The PersonUser class in this example would expose a property of type IPerson. As far as it's concerned, the AOP proxy can be used transparently in place of a "real" person implementation. However, its type would be a proxy type. It would be possible to cast it to the IAdvised interface (discussed below). It's possible to conceal the distinction between target and proxy using an anonymous inline object, as follows. (for more information on inline objects see Section 5.3.2.3, Inner objects.) Only the ProxyFactoryObject definition is different; the advice is included only for completeness:
<object id="myCustomInterceptor" type="MyCompany.MyApp.MyCustomInterceptor, MyCompany"> <property name="customProperty" value="configuration string"/> </object> <object id="debugInterceptor" type="Spring.Aop.Advice.DebugAdvice, Spring.Aop"> </object> <object id="person" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="proxyInterfaces" value="MyCompany.MyApp.IPerson"/> <property name="target"> <!-- Instead of using a reference to target, just use an inline object --> <object type="MyCompany.MyApp.Person, MyCompany"> <property name="name" value="Tony"/> <property name="age" value="51"/> </object> </property> <property name="interceptorNames"> <list> <value>debugInterceptor</value> <value>myCustomInterceptor</value> </list> </property> </object>
This has the advantage that there's only one object of type Person: useful if we want to prevent users of the application context obtaining a reference to the un-advised object, or need to avoid any ambiguity with Spring IoC autowiring. There's also arguably an advantage in that the ProxyFactoryObject definition is self-contained. However, there are times when being able to obtain the un-advised target from the factory might actually be an advantage: for example, in certain test scenarios. 13.5.1. Applying advice on a per-proxy basis. Let's look at an example of configuring the proxy objects retrieved from ProxyFactoryObject.
145
<!-- create the object to reference --> <object id="RealObjectTarget" type="MyRealObject" singleton="false"/> <!-- create the proxied object for everyone to use--> <object id="MyObject" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="proxyInterfaces" value="MyInterface" /> <property name="isSingleton" value="false"/> <property name="targetName" value="RealObjectTarget" /> </object>
If you are using a prototype as the target you must set the TargetName property with the name/object id of your object and not use the property Target with a reference to that object. This will then allow a new proxy to be created around a new prototype target instance. Consider the above Spring.Net object configuration. Notice that the IsSingleton property of the ProxyFactoryObject instance is set to false. This means that each proxy object will be unique. Thus, you can configure each proxy object with its' own individual advice(s) using the following syntax
// Will return un-advised instance of proxy object MyInterface myProxyObject1 = (MyInterface)ctx.GetObject("MyObject"); // myProxyObject1 instance now has an advice attached to it. IAdvised advised = (IAdvised)myProxyObject1; advised.AddAdvice( new DebugAdvice() ); // Will return a new, un-advised instance of proxy object MyInterface myProxyObject2 = (MyInterface)ctx.GetObject("MyObject");
146
This will never be instantiated itself, so may actually be incomplete. Then each proxy which needs to be created is just a child object definition, which wraps the target of the proxy as an inner object definition, since the target will never be used on its own anyway.
<object name="testObjectManager" parent="txProxyTemplate"> <property name="Target"> <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object> </property> </object>
It is of course possible to override properties from the parent template, such as in this case, the transaction propagation settings:
<object name="testObjectManager" parent="txProxyTemplate"> <property name="Target"> <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object> </property> <property name="TransactionAttributes"> <name-values> <add key="Save*" value="PROPAGATION_REQUIRED"/> <add key="Delete*" value="PROPAGATION_REQUIRED"/> <add key="Find*" value="PROPAGATION_REQUIRED,readonly"/> </name-values> </property> </object>
Note that in the example above, we have explicitly marked the parent object definition as abstract by using the abstract attribute, as described previously, so that it may not actually ever be instantiated. Application contexts (but not simple object factories) will by default pre-instantiate all singletons. It is therefore important (at least for singleton object) that if you have a (parent) object definition which you intend to use only as a template, and this definition specifies a class, you must make sure to set the abstract attribute to true, otherwise the application context will actually try to pre-instantiate it.
147
Aspect Oriented Programming with Spring.NET other methods on the target object will not be advised. To force inheritance based proxies you should either set the ProxyTargetType to true property of a ProxyFactory or set the XML namespace element proxy-target-type = true when using an AOP schema based configuration.
Note
An important alternative approach to inheritance based proxies is disucssed in the next section. In .NET 2.0 you can define the assembly level attribute, InternalsVisibleTo, to allow access of internal interfaces/classes to specified 'friend' assemblies. If you need to create an AOP proxy on an internal class/interface add the following code, [assembly: InternalsVisibleTo("Spring.Proxy")] and [assembly: InternalsVisibleTo("Spring.DynamicReflection")] to your to AssemblyInfo file.
13.6.1. InheritanceBasedAopConfigurer
There is an important limitation in the inheritance based proxy as described above, all methods that manipulate the state of the object should be declared as virtual. Otherwise some method invocations get directed to the private 'target' field member and others to the base class. Winform object are an example of case where this approach does not apply. To address this limitation, a new post-processing mechanism was introduced in version 1.2 that creates a proxy type without the private 'target' field. Interception advice is added directly in the method body before invoking the base class method. To use this new inheritance based proxy described in the note above, declare an instance of the InheritanceBasedAopConfigurer, and IObjectFactoryPostProcessor, in yoru configuraiton file. Here is an example.
<object type="Spring.Aop.Framework.AutoProxy.InheritanceBasedAopConfigurer, Spring.Aop"> <property name="ObjectNames"> <list> <value>Form*</value> <value>Control*</value> </list> </property> <property name="InterceptorNames"> <list> <value>debugInterceptor</value> </list> </property> </object> <object id="debugInterceptor" type="AopPlay.DebugInterceptor, AopPlay"/>
This configuraiton style is similar to the autoproxy by name approach described here and is particuarly appropriate when you want to apply advice to WinForm classes.
148
The first step is to construct an object of type Spring.Aop.Framework.ProxyFactory. You can create this with a target object, as in the above example, or specify the interfaces to be proxied in an alternate constructor. You can add interceptors or advisors, and manipulate them for the life of the ProxyFactory. There are also convenience methods on ProxyFactory (inherited from AdvisedSupport) allowing you to add other advice types such as before and throws advice. AdvisedSupport is the superclass of both ProxyFactory and ProxyFactoryObject.
Note
Integrating AOP proxy creation with the IoC framework is best practice in most applications. We recommend that you externalize configuration from .NET code with AOP, as in general.
int IndexOf(IAdvisor advisor); int IndexOf(IIntroductionAdvisor advisor); bool RemoveAdvisor(IAdvisor advisor); void RemoveAdvisor(int index); bool RemoveInterceptor(IInterceptor interceptor); bool RemoveIntroduction(IIntroductionAdvisor advisor); void RemoveIntroduction(int index); void ReplaceIntroduction(int index, IIntroductionAdvisor advisor); bool ReplaceAdvisor(IAdvisor a, IAdvisor b); }
The Advisors property will return an IAdvisor for every advisor, interceptor or other advice type that has been added to the factory. If you added an IAdvisor, the returned advisor at this index will be the object that you added. If you added an interceptor or other advice type, Spring.NET will have wrapped this in an advisor with a IPointcut that always returns true. Thus if you added an IMethodInterceptor, the advisor returned for this
149
Aspect Oriented Programming with Spring.NET index will be a DefaultPointcutAdvisor returning your IMethodInterceptor and an IPointcut that matches all types and methods. The AddAdvisor() methods can be used to add any IAdvisor. Usually this will be the generic DefaultPointcutAdvisor, which can be used with any advice or pointcut (but not for introduction). By default, it's possible to add or remove advisors or interceptors even once a proxy has been created. The only restriction is that it's impossible to add or remove an introduction advisor, as existing proxies from the factory will not show the interface change. (You can obtain a new proxy from the factory to avoid this problem.) It's questionable whether it's advisable (no pun intended) to modify advice on a business object in production, although there are no doubt legitimate usage cases. However, it can be very useful in development: for example, in tests. I have sometimes found it very useful to be able to add test code in the form of an interceptor or other advice, getting inside a method invocation I want to test. (For example, the advice can get inside a transaction created for that method: for example, to run SQL to check that a database was correctly updated, before marking the transaction for roll back.) Depending on how you created the proxy, you can usually set a Frozen flag, in which case the IAdvised IsFrozen property will return true, and any attempts to modify advice through addition or removal will result in an AopConfigException. The ability to freeze the state of an advised object is useful in some cases: For example, to prevent calling code removing a security interceptor.
150
Aspect Oriented Programming with Spring.NET 13.9.1.1. ObjectNameAutoProxyCreator The ObjectNameAutoProxyCreator automatically creates AOP proxies for object with names matching literal values or wildcards. The pattern matching expressions supported are of the form "*name", "name*", and "*name*" and exact name matching, i.e. "name". The following simple classes are used to demonstrate this autoproxy functionality.
public enum Language { English = 1, Portuguese = 2, Italian = 3 }
public class HelloWorldSpeaker : IHelloWorldSpeaker { private Language language; public Language Language { set { language = value; } get { return language; } } public void SayHello() { switch (language) { case Language.English: Console.WriteLine("Hello World!"); break; case Language.Portuguese: Console.WriteLine("Oi Mundo!"); break; case Language.Italian: Console.WriteLine("Ciao Mondo!"); break; } } }
public class DebugInterceptor : IMethodInterceptor { public object Invoke(IMethodInvocation invocation) { Console.WriteLine("Before: " + invocation.Method.ToString()); object rval = invocation.Proceed(); Console.WriteLine("After: " + invocation.Method.ToString()); return rval; } }
The following XML is used to automatically create an AOP proxy and apply a Debug interceptor to object definitions whose names match "English*" and "PortugueseSpeaker".
<object id="ProxyCreator" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> <property name="ObjectNames"> <list> <value>English*</value> <value>PortugeseSpeaker</value> </list> </property>
151
As with ProxyFactoryObject, there is an InterceptorNames property rather than a list of interceptors, to allow correct behavior for prototype advisors. Named "interceptors" can be advisors or any advice type. The same advice will be applied to all matching objects. Note that if advisors are used (rather than the interceptor in the above example), the pointcuts may apply differently to different objects. Running the following simple program demonstrates the application of the AOP interceptor.
IApplicationContext ctx = ContextRegistry.GetContext(); IDictionary speakerDictionary = ctx.GetObjectsOfType(typeof(IHelloWorldSpeaker)); foreach (DictionaryEntry entry in speakerDictionary) { string name = (string)entry.Key; IHelloWorldSpeaker worldSpeaker = (IHelloWorldSpeaker)entry.Value; Console.Write(name + " says; "); worldSpeaker.SayHello(); }
13.9.1.2. DefaultAdvisorAutoProxyCreator A more general and extremely powerful auto proxy creator is DefaultAdvisorAutoProxyCreator. This will automatically apply eligible advisors in the current application context, without the need to include specific object names in the autoproxy advisor's object definition. It offers the same merit of consistent configuration and avoidance of duplication as ObjectNameAutoProxyCreator. Using this mechanism involves: Specifying a DefaultAdvisorAutoProxyCreator object definition
152
Aspect Oriented Programming with Spring.NET Specifying any number of Advisors in the same or related contexts. Note that these must be Advisors, not just interceptors or other advices. This is necessary because there must be a pointcut to evaluate, to check the eligibility of each advice to candidate object definitions. The DefaultAdvisorAutoProxyCreator will automatically evaluate the pointcut contained in each advisor, to see what (if any) advice it should apply to each object defined in the application context. This means that any number of advisors can be applied automatically to each business object. If no pointcut in any of the advisors matches any method in a business object, the object will not be proxied. The DefaultAdvisorAutoProxyCreator is very useful if you want to apply the same advice consistently to many business objects. Once the infrastructure definitions are in place, you can simply add new business objects without including specific proxy configuration. You can also drop in additional aspects very easily--for example, tracing or performance monitoring aspects--with minimal change to configuration. The following example demonstrates the use of DefaultAdvisorAutoProxyCreator. Expanding on the previous example code used to demonstrate ObjectNameAutoProxyCreator we will add a new class, SpeakerDao, that acts as a Data Access Object to find and store IHelloWorldSpeaker objects.
public interface ISpeakerDao { IList FindAll(); IHelloWorldSpeaker Save(IHelloWorldSpeaker speaker); } public class SpeakerDao : ISpeakerDao { public System.Collections.IList FindAll() { Console.WriteLine("Finding speakers..."); // just a demo...fake the retrieval. Thread.Sleep(10000); HelloWorldSpeaker speaker = new HelloWorldSpeaker(); speaker.Language = Language.Portuguese; IList list = new ArrayList(); list.Add(speaker); return list; } public IHelloWorldSpeaker Save(IHelloWorldSpeaker speaker) { Console.WriteLine("Saving speaker..."); // just a demo...not really saving... return speaker; } }
The XML configuration specifies two Advisors, that is, the combination of advice (the behavior to add) and a pointcut (where the behavior should be applied). A RegularExpressionMethodPointcutAdvisor is used as a convenience to specify the pointcut as a regular expression that matches methods names. Other pointcuts of your own creation could be used, in which case a DefaultPointcutAdvisor would be used to define the Advisor. The object definitions for these advisors, advice, and SpeakerDao object are shown below
<object id="SpeachAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop"> <property name="advice" ref="debugInterceptor"/> <property name="patterns"> <list> <value>.*Say.*</value> </list> </property>
153
</object> <object id="AdoAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop"> <property name="advice" ref="timingInterceptor"/> <property name="patterns"> <list> <value>.*Find.*</value> </list> </property> </object> // Advice <object id="debugInterceptor" type="AopPlay.DebugInterceptor, AopPlay"/> <object id="timingInterceptor" type="AopPlay.TimingInterceptor, AopPlay"/> // Speaker DAO Object - has 'FindAll' Method. <object id="speakerDao" type="AopPlay.SpeakerDao, AopPlay"/> // HelloWorldSpeaker objects as previously listed.
will apply the debug interceptor on all objects in the context that have a method that contains the text "Say" and apply the timing interceptor on objects in the context that have a method that contains the text "Find". Running the following code demonstrates this behavior. Note that the "Save" method of SpeakerDao does not have any advice applied to it.
IApplicationContext ctx = ContextRegistry.GetContext(); IDictionary speakerDictionary = ctx.GetObjectsOfType(typeof(IHelloWorldSpeaker)); foreach (DictionaryEntry entry in speakerDictionary) { string name = (string)entry.Key; IHelloWorldSpeaker worldSpeaker = (IHelloWorldSpeaker)entry.Value; Console.Write(name + " says; "); worldSpeaker.SayHello(); } ISpeakerDao dao = (ISpeakerDao)ctx.GetObject("speakerDao"); IList speakerList = dao.FindAll(); IHelloWorldSpeaker speaker = dao.Save(new HelloWorldSpeaker());
The DefaultAdvisorAutoProxyCreator offers support for filtering (using a naming convention so that only certain advisors are evaluated, allowing use of multiple, differently configured, AdvisorAutoProxyCreators in the same
154
Aspect Oriented Programming with Spring.NET factory) and ordering. Advisors can implement the Spring.Core.IOrdered interface to ensure correct ordering if this is an issue. The default is unordered. 13.9.1.3. PointcutFilteringAutoProxyCreator An AutoProxyCreator that identified objects to proxy by matching a specified IPointcut. 13.9.1.4. TypeNameAutoProxyCreator An AutoProxyCreator that identifies objects to proxy by matching their Type.FullName against a list of patterns. 13.9.1.5. AttributeAutoProxyCreator An AutoProxyCreator, that identifies objects to be proxied by checking any System.Attribute defined on a given type and that types interfaces. 13.9.1.6. AbstractFilteringAutoProxyCreator The base class for AutoProxyCreator implementations that mark objects eligible for proxying based on arbitrary criteria. 13.9.1.7. AbstractAutoProxyCreator This is the superclass of DefaultAdvisorAutoProxyCreator. You can create your own autoproxy creators by subclassing this class, in the unlikely event that advisor definitions offer insufficient customization to the behavior of the framework DefaultAdvisorAutoProxyCreator.
155
</objects>
In this example, the TestObject, which implements the interface ITestObject, is having AOP advice applied to it. The method GetDescription() is specified as a regular expression pointcut. The aop:config tag and subsequent child tag, aop:advisor, brings together the pointcut with the advice. In order to have Spring.NET recognise the aop namespace, you need to declare the namespace parser in the main Spring.NET configuration section. For convenience this is shown below. Please refer to the section titled context configuration for more extensive information..
<configuration> <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> </configSections> <spring> <parsers> <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" /> </parsers>
<context> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> ... </objects> </spring> </configuration>
156
Aspect Oriented Programming with Spring.NET If you do not specify a TargetSource, a default implementation is used that wraps a local object. The same target is returned for each invocation (as you would expect). Let's look at the standard target sources provided with Spring.NET, and how you can use them. When using a custom target source, your target will usually need to be a prototype rather than a singleton object definition. This allows Spring.NET to create a new target instance when required.
The above swap() call changes the target of the swappable object. Clients who hold a reference to that object will be unaware of the change, but will immediately start hitting the new target. Although this example doesn't add any advice--and it's not necessary to add advice to use a TargetSource--of course any TargetSource can be used in conjunction with arbitrary advice.
157
Note that the target object--"businessObjectTarget" in the example--must be a prototype. This allows the PoolingTargetSource implementation to create new instances of the target to grow the pool as necessary. See the SDK documentation for AbstractPoolingTargetSource and the concrete subclass you wish to use for information about it's properties: maxSize is the most basic, and always guaranteed to be present. In this case, "myInterceptor" is the name of an interceptor that would need to be defined in the same IoC context. However, it isn't necessary to specify interceptors to use pooling. If you want only pooling, and no other advice, don't set the interceptorNames property at all. It's possible to configure Spring.NET so as to be able to cast any pooled object to the Spring.Aop.Target.PoolingConfig interface, which exposes information about the configuration and current size of the pool through an introduction. You'll need to define an advisor like this:
<object id="poolConfigAdvisor" type="Spring.Object.Factory.Config.MethodInvokingFactoryObject, Spring.Aop"> <property name="target" ref="poolTargetSource" /> <property name="targetMethod" value="getPoolingConfigMixin" /> </object>
This advisor is obtained by calling a convenience method on the AbstractPoolingTargetSource class, hence the use of MethodInvokingFactoryObject. This advisor's name ('poolConfigAdvisor' here) must be in the list of interceptor names in the ProxyFactoryObject exposing the pooled object. The cast will look as follows:
PoolingConfig conf = (PoolingConfig) objectFactory.GetObject("businessObject"); Console.WriteLine("Max pool size is " + conf.getMaxSize());
Pooling stateless service objects is not usually necessary. We don't believe it should be the default choice, as most stateless objects are naturally threadsafe, and instance pooling is problematic if resources are cached. Simpler pooling is available using autoproxying. It's possible to set the TargetSources used by any autoproxy creator.
158
There is only one property: the name of the target object. Inheritance is used in the TargetSource implementations to ensure consistent naming. As with the pooling target source, the target object must be a prototype object definition, the singleton property of the target should be set to false.
159
14.2. Caching
Caching the return value of a method or the value of a method parameter is a common approach to increase application performance. Application performance is increased with effective use of caching since layers in the application that are closer to the user can return information within their own layer as compared to making more expensive calls to retrieve that information from a lower, and more slow, layer such as a database or a web service. Caching also can help in terms of application scalability, which is generally the more important concern. The caching support in Spring.NET consists of base cache interfaces that can be used to specify a specific storage implementation of the cache and also an aspect that determines where to apply the caching functionality and its configuration. The base cache interface that any cache implementation should implement is Spring.Caching.ICache located in Spring.Core. Two implementations are provided, Spring.Caching.AspNetCache located in Spring.Web which stores cache entries within an ASP.NET cache and a simple implementation, Spring.Caching.NonExpiringCache that stores cache entries in memory and never expires these entries. Custom implementations based on 3rd party implementations, such as Oracle Coherence, or memcached, can be used by implementing the ICache interface. The cache aspect is Spring.Aspects.Cache.CacheAspect located in Spring.Aop. It consists of three pieces of functionality, the ability to cache return values, method parameters, and explicit eviction of an item from the cache. The aspect currently relies on using attributes to specify the pointcut as well as the behavior, much like the transactional aspect. Future versions will allow for external configuration of the behavior so you can apply caching to a code base without needing to use attributes in the code. The following attributes are available CacheResult - used to cache the return value CacheResultItems - used when returning a collection as a return value CacheParameter - used to cache a method parameter InvalidateCache - used to indicate one or more cache items should be invalidated. Each CacheResult, CacheResultItems, and CacheParameter attributes define the following properties. CacheName - the name of the cache implementation to use Key - a string representing a Spring Expression Language (SpEL) expression used as the key in the cache.
160
Aspect Library Condition - a SpEL expression that should be evaluated in order to determine whether the item should be cached. TimeToLive - The amount of time an object should remain in the cache (in seconds). The InvalidateCache attribute has properties for the CacheName, the Key as well as the Condition, with the same meanings as listed previously. Each ICache implementation will have properties that are specific to a caching technology. In the case of AspNetCache, the two important properties to configure are: SlidingExperation - If this property value is set to true, every time the marked object is accessed it's TimeToLive value is reset to its original value Priority - the cache item priority controlling how likely an object is to be removed from an associated cache when the cache is being purged. TimeToLive - The amount of time an object should remain in the cache (in seconds). The values of the Priority enumeration are Low - low likelihood of deletion when cache is purged. Normal - default priority for deletion when cache is purged. High - high likelihood of deletion when cache is purged. NotRemovable - cache item not deleted when cache is purged. An important element of the applying these attributes is the use of the expression language that allows for calling context information to drive the caching actions. Here is an example taken from the Spring Air sample application of the AirportDao implementation that implements an interface with the method GetAirport(long id).
[CacheResult("AspNetCache", "'Airport.Id=' + #id", TimeToLive = "0:1:0")] public Airport GetAirport(long id) { // implementation not shown... }
The first parameter is the cache name. The second string parameter is the cache key and is a string expression that incorporates the argument passed into the method, the id. The cache key value cannot be null or an empty string (""). The method parameter names are exposed as variables to the key expression. The expression may also call out to other objects in the Spring container allowing for a more complex key algorithm to be encapsulated. The end result is that the Airport object is cached by id for 60 seconds in a cache named AspNetCache. The TimetoLive property could also have been specified on the configuration of the AspNetCache object. The configuration to enable the caching aspect is shown below
<object" id="CacheAspect" type="Spring.Aspects.Cache.CacheAspect, Spring.Aop"/> <object id="AspNetCache" type="Spring.Caching.AspNetCache, Spring.Web"> <property name="SlidingExpiration" value="true"/> <property name="Priority" value="Low"/> <property name="TimeToLive" value="00:02:00"/> </object> <!-- Apply aspects to DAOs --> <object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop">
161
Aspect Library
<property name="ObjectNames"> <list> <value>*Dao</value> </list> </property> <property name="InterceptorNames"> <list> <value>CacheAspect</value> </list> </property> </object>
in this example an ObjectNameAutoProxyCreator was used to apply the cache aspect to objects that have Dao in their name. The AspNetCache setting for TimeToLive will override the TimeToLive value set at the method level via the attribute.
What this is instructing the advice to do is the following bit of code when an ArithmeticException is thrown, throw new System.InvalidOperationException("Wrapped ArithmeticException", e), where e is the original ArithmeticException. The default message, "Wrapped ArithmethicException" is automatically appended. You may however specify the message used in the newly thrown exception as shown below
on exception name ArithmeticException wrap System.InvalidOperationException 'My Message'
Similarly, if you would rather replace the exception, that is do not nest one inside the other, you can use the following syntax
162
Aspect Library
on exception name ArithmeticException replace System.InvalidOperationException or on exception name ArithmeticException replace System.InvalidOperationException 'My Message'
Both wrap and replace are special cases of the more general translate action. An example of a translate expression is shown below
on exception name ArithmeticException translate new System.InvalidOperationException('My Message, Method Name ' + #method.Name, #e)
What we see here after the translate keyword is text that will be passed into Spring's expression language (SpEL) for evaluation. Refer to the chapter on the expression language for more details. One important feature of the expression evaluation is the availability of variables relating to the calling context when the exception was thrown. These are method - the MethodInfo object corresponding to the method that threw the exception args - the argument array to the method that threw the exception, signature is object[] target - the AOP target object instance. e - the thrown exception You can invoke methods on these variables, prefixed by a '#' in the expression. This gives you the flexibility to call special purpose constructors that can have any piece of information accessible via the above variables, or even other external data through the use of SpEL's ability to reference objects within the Spring container. You may also choose to 'swallow' the exception or to return a specific return value, for example
on exception name ArithmeticException swallow
or
Here we see that a comma delimited list of exception names can be specified. The logging is performed using the Commons.Logging library that provides an abstraction over the underlying logging implementation. Logging is currently at the debug level with a logger name of "LogExceptionHandler" The ability to specify these values will be a future enhancement and likely via a syntax resembling a constructor for the action, i.e. log(Debug,"LoggerName"). Multiple exception handling statements can be specified within the list shown above. The processing flow is on exception, the name of the exception listed in the statement is compared to the thrown exception to see if there is a match. A comma separated list of exceptions can be used to group together the same action taken for different exception names. If the action to take is logging, then the logging action is performed and the search for other matching exception names continues. For all other actions, namely translate, wrap, replace, swallow, return, once an exception handler is matched, those in the chain are no longer evaluated. Note, do not confuse this handler chain with the general advice AOP advice chain. For translate, wrap, and replace actions a SpEL
163
Aspect Library expression is created and used to instantiate a new exception (in addition to any other processing that may occur when evaluating the expression) which is then thrown. The exception handling DSL also supports the ability to provide a SpEL boolean expression to determine if the advice will apply instead of just filtering by the expression name. For example, the following is the equivalent to the first example based on exception names but compares the specific type of the exception thrown
on exception (#e is T(System.ArithmeticException)) wrap System.InvalidOperationException
The syntax use is 'on exception (SpEL boolean expression)' and inside the expression you have access to the variables of the calling context listed before, i.e. method, args, target, and e. This can be useful to implement a small amount of conditional logic, such as checking for a specific error number in an exception, i.e. (#e is T(System.Data.SqlException) && #e.Errors[0].Number in {156,170,207,208}), to catch and translate bad grammar codes in a SqlException. While the examples given above are toy examples, they could just as easily be changed to convert your application specific exceptions. If you find yourself pushing the limits of using SpEL expressions, you will likely be better off creating your own custom aspect class instead of a scripting approach. You can also configure the each of the Handlers individually based on the action keyword. For example, to configure the logging properties on the LogExceptionHandler.
<object name="logExceptionHandler" type="Spring.Aspects.Exceptions.LogExceptionHandler, Spring.Aop"> <property name="LogName" value="Cms.Session.ExceptionHandler" /> <property name="LogLevel" value="Debug"/> <property name="LogMessageOnly" value="true"/> </object> <object name="exceptionHandlingAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"> <property name="ExceptionHandlerDictionary"> <dictionary> <entry key="log" ref="logExceptionHandler"/> </dictionary> </property> <property name="ExceptionHandlers"> <list> <value>on exception name ArithmeticException,ArgumentException log 'My Message, Method Name ' + #method.Name</value> </list> </property> </object>
You can also configure ExceptionHandlerAdvice to use an instance of IExceptionHandler by specifing it as an entry in the ExceptionHandlers list. This gives you complete control over all properties of the handler but you must set ConstraintExpressionText and ActionExpressionText which are normally parsed for you from the string. To use the case of configuring the LogExceptionHandler, this approach also lets you specify advanced logging functionality, but at a cost of some additional complexity. For example setting the logging level and pass the exception into the logging subsystem
<object name="exceptionHandlingAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"> <property name="exceptionHandlers"> <list> <object type="Spring.Aspects.Exceptions.LogExceptionHandler"> <property name="LogName" value="Cms.Session.ExceptionHandler" /> <property name="ConstraintExpressionText" value="#e is T(System.Threading.ThreadAbortException)" /> <property name="ActionExpressionText" value="#log.Fatal('Request Timeout occured', #e)" /> </object> </list> </property> </object>
164
Aspect Library The configuration of the logger name, level, and weather or not to pass the thrown exception as the second argument to the log method will be supported in the DSL style in a future release.
or
on exception (SpEL boolean expression) [action] [SpEL expression]
The exception names are required as well as the action. The valid actions are log translate wrap replace return swallow execute The form of the expression depends on the action. For logging, the entire string is taken as the SpEL expression to log. Translate expects an exception to be returned from evaluation the SpEL expression. Wrap and replace are shorthand for the translate action. For wrap and replace you specify the exception name and the message to pass into the standard exception constructors (string, exception) and (string). The exception name can be a partial or fully qualified name. Spring will attempt to resolve the typename across all referenced assemblies. You may also register type aliases for use with SpEL in the standard manner with Spring.NET and those will be accessible from within the exception handling expression.
14.4. Logging
The logging advice lets you log the information on method entry, exit and thrown exception (if any). The implementation is based on the logging library, Common.Logging, that provides portability across different logging libraries. There are a number of configuration options available, listed below LogUniqueIdentifier LogExecutionTime LogMethodArguments LogReturnValue Separator LogLevel
165
Aspect Library You declare the logging advice in IoC container with the following XML fragment. Alternatively, you can use the class SimpleLoggingAdvice programatically.
<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="LogReturnValue" value="true"/> <property name="Separator" <property name="LogLevel" value=";"/> value="Info"/>
value="true"/> value="true"/>
The default values for LogUniqueIdentifier, LogExecutionTime, LogMethodArguments and LogReturnValue are false. The default separator value is ", " and the default log level is Common.Logging's LogLevel.Trace. You can set the name of the logger with the property LoggerName, for example "DataAccessLayer" for a logging advice that would be applied across the all the classes in the data access layer. That works well when using a 'category' style of logging. If you do not set the LoggerName property, then the type name of the logging advice is used as the logging name. Another approach to logging is to log based on the type of the object being called, the target type. Since often this is a proxy class with a relatively meaningless name, the property HideProxyTypeNames can be set to true to show the true target type and not the proxy type. The UseDynamicLogger property determines which ILog instance should be used to write log messages for a particular method invocation: a dynamic one for the Type getting called, or a static one for the Type of the trace interceptor. The default is to use a static logger. To further extend the functionality of the SimpleLoggingAdvice you can subclass SimpleLoggingAdvice and override the methods string GetEntryMessage(IMethodInvocation invocation, string idString) string
GetExceptionMessage(IMethodInvocation invocation, Exception e, TimeSpan
string
GetExitMessage(IMethodInvocation
invocation,
object
returnValue,
TimeSpan
The default implementation to calculate a unique identifier is to use a GUID. You can alter this behavior by overriding the method string CreateUniqueIdentifier(). The SimpleLoggingAdvice class inherits from AbstractLoggingAdvice, which has the abstract method object InvokeUnderLog(IMethodInvocation invocation, ILog log) and you can also override the method ILog GetLoggerForInvocation(IMethodInvocation invocation) to customize the logger instance used for logging. Refer to the SDK documentation for more details on subclassing AbstractLoggingAdvice. As an example of the Logging advice's output, adding the advice to the method
public int Bark(string message, int[] luckyNumbers) { return 4; }
166
Aspect Library
Exiting Bark, 5d2bad47-62cd-435b-8de7-91f12b7f433e, 30453.125 ms, return=4
The method parameters values are obtained using the ToString() method. If you would like to have an alternate implementation, say to view some values in an array, override the method string GetMethodArgumentAsString(IMethodInvocation invocation).
14.5. Retry
When making a distributed call it is often a common requirement to be able to retry the method invocation if there was an exception. Typically the exception will be due to a communication issue that is intermittent and retrying over a period of time will likely result in a successful invocation. When applying retry advice it is important to know if making two calls to the remote service will cause side effects. Generally speaking, the method being invoked should be idempotent, that is, it is safe to call multiple times. The retry advice is specified using a little language, i.e a DSL. A simple example is shown below
on exception name ArithmeticException retry 3x delay 1s
The meaning is: when an exception that has 'ArithmeticException' in its type name is thrown, retry the invocation up to 3 times and delay for 1 second between each retry event. You can also provide a SpEL (Spring Expression Language) expression that calculates the time interval to sleep between each retry event. The syntax for this is shown below
on exception name ArithmeticException retry 3x rate (1*#n + 0.5)
As with the exception handling advice, you may also specify a boolean SpEL that must evaluate to true in order for the advice to apply. For example
on exception (#e is T(System.ArithmeticException)) retry 3x delay 1s
The time specified after the delay keyword is converted to a TimeSpan object using Spring's TimeSpanConverter. This supports setting the time as an integer + time unit. Time units are (d, h, m, s, ms) representing (days, hours, minutes, seconds, and milliseconds). For example; 1d = 1day, 5h = 5 hours etc. You can not specify a string such as '1d 5h'. The value that is calculated from the expression after the rate keyword is interpreted as a number of seconds. The power of using SpEL for the rate expression is that you can easily specify some exponential retry rate (a bigger delay for each retry attempt) or call out to a custom function developed for this purpose. When using a SpEL expression for the filter condition or for the rate expression, the following variable are available method - the MethodInfo object corresponding to the method that threw the exception args - the argument array to the method that threw the exception, signature is object[] target - the AOP target object instance. e - the thrown exception You declare the advice in IoC container with the following XML fragment. Alternatively, you can use the RetryAdvice class programatically.
<object name="exceptionHandlingAdvice" type="Spring.Aspects.RetryAdvice, Spring.Aop">
167
Aspect Library
<property name="retryExpression" value="on exception name ArithmeticException retry 3x delay 1s"/> </object>
or
on exception (SpEL boolean expression) retry [number of times]x [delay|rate] [delay time| SpELrate expression]
14.6. Transactions
The transaction aspect is more fully described in the section on transaction management.
The Validated attribute takes a string name that specifies the name of the validation rule, i.e. the name of the IValidator object in the Spring application context. The Validated attribute is located in the namespace Spring.Validation of the Spring.Core assembly. The configuration of the advice is to simply define the an instance of the ParameterValidationAdvice class and apply the advice, for example based on object names using an ObjectNameAutoProxyCreator, as shown below,
<object id="validationAdvice" type="Spring.Aspects.Validation.ParameterValidationAdvice, Spring.Aop"/> <object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> <property name="ObjectNames"> <list> <value>bookingAgent</value> </list> </property>
168
Aspect Library
<property name="InterceptorNames"> <list> <value>validationAdvice</value> </list> </property> </object>
When the advised method is invoked first the validation of each method parameter is performed. If all validation succeeds, then the method body is executed. If validation fails an exception of the type ValidationException is thrown and you can retrieve errors information from its property ValidationErrors. See the SDK documentation for details.
169
170
Note
The Spring.Testing.NUnit.dll library is compiled against NUnit 2.5.1. Note that test runners integrated inside VS.NET may or may not support this version. At the time of this writing Reshaper 4.5.0 did not properly support NUnit 2.5.1. To use Resharper with NUnit 2.5.1 you need to download 4.5.1 RC2 or later. These namespaces provides NUnit and MSTest superclasses for integration testing using a Spring container. These superclasses provide the following functionality: Spring IoC container caching between test case execution.
171
Testing The pretty-much-transparent Dependency Injection of test fixture instances (this is nice). Transaction management appropriate to integration testing (this is even nicer). A number of Spring-specific inherited instance variables that are really useful when integration testing.
Implementations of this method must provide an array containing the IResource locations of XML configuration metadata used to configure the application. This will be the same, or nearly the same, as the list of configuration locations specified in App.config/Web.config or other deployment configuration. By default, once loaded, the configuration file set will be reused for each test case. Thus the setup cost will be incurred only once (per test fixture), and subsequent test execution will be much faster. In the unlikely case that a test may 'dirty' the config location, requiring reloading - for example, by changing an object definition or the state of an application object - you can call the SetDirty() method on AbstractDependencyInjectionSpringContextTests to cause the test fixture to reload the configurations and rebuild the application context before executing the next test case.
172
Testing
[TestFixture] public class HibernateTitleDaoTests : AbstractDependencyInjectionSpringContextTests // this instance will be (automatically) dependency injected private HibernateTitleDao titleDao; // a setter method to enable DI of the 'titleDao' instance variable public HibernateTitleDao HibernateTitleDao { set { titleDao = value; } } [Test] public void LoadTitle() { Title title = this.titleDao.LoadTitle(10); Assert.IsNotNull(title); } // specifies the Spring configuration to load for this test fixture protected override string[] ConfigLocations { return new String[] { "assembly://MyAssembly/MyNamespace/daos.xml" }; } }
The file referenced by the ConfigLocations method ('classpath:com/foo/daos.xml') looks like this:
<?xml version="1.0" encoding="utf-8" ?> <objects xmlns="http://www.springframework.net"> <!-- this object will be injected into the HibernateTitleDaoTests class --> <object id="titleDao" type="Spring.Samples.HibernateTitleDao, Spring.Samples"> <property name="sessionFactory" ref="sessionFactory"/> </object> <object id="sessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate"> <!-- dependencies elided for clarity --> </object> </objects>
The AbstractDependencyInjectionSpringContextTests classes uses autowire by type. Thus if you have multiple object definitions of the same type, you cannot rely on this approach for those particular object. In that case, you can use the inherited applicationContext instance variable, and explicit lookup using (for example) an explicit call to applicationContext.GetObject("titleDao"). Using AbstractDependencyInjectionSpringContextTests with MSTest is very similar.
/// Using Microsoft's Testing Framework [TestClass] public class HibernateTitleDaoTests : AbstractDependencyInjectionSpringContextTests // this instance will be (automatically) dependency injected private HibernateTitleDao titleDao; // a setter method to enable DI of the 'titleDao' instance variable public HibernateTitleDao HibernateTitleDao { set { titleDao = value; } } [Test] public void LoadTitle() { Title title = this.titleDao.LoadTitle(10); Assert.IsNotNull(title); } // specifies the Spring configuration to load for this test fixture protected override string[] ConfigLocations { return new String[] { "assembly://MyAssembly/MyNamespace/daos.xml" };
173
Testing
} }
If you don't want dependency injection applied to your test cases, simply don't declare any set properties. Alternatively, you can extend the AbstractSpringContextTests - the root of the class hierarchy in the Spring.Testing.NUnit and Spring.Testing.Microsoft namespaces. It merely contains convenience methods to load Spring contexts, and performs no Dependency Injection of the test fixture. 16.3.2.1. Field level injection If, for whatever reason, you don't fancy having setter properties in your test fixtures, Spring can (in this one case) inject dependencies into protected fields. Find below a reworking of the previous example to use field level injection (the Spring XML configuration does not need to change, merely the test fixture).
[TestFixture] public class HibernateTitleDaoTests : AbstractDependencyInjectionSpringContextTests{ public HibernateTitleDaoTests() { // switch on field level injection PopulateProtectedVariables = true; } // this instance will be (automatically) dependency injected protected HibernateTitleDao titleDao; [TestMethod] public void LoadTitle() { Title title = this.titleDao.LoadTitle(10); Assert.IsNotNull(title); } // specifies the Spring configuration to load for this test fixture protected override string[] ConfigLocations { return new String[] { "assembly://MyAssembly/MyNamespace/daos.xml" }; } }
In the case of field injection, there is no autowiring going on: the name of your protected instances variable(s) are used as the lookup object name in the configured Spring container.
Typically you will extend the subclass, AbstractTransactionalDbProviderSpringContextTests. This also requires that a DbProvider object definition - again, with any name - be present in the configurations. It creates an AdoTemplate instance variable that is useful for convenient querying, and provides handy methods to delete the contents of selected tables (remember that the transaction will roll back by default, so this is safe to do).
174
Testing If you want a transaction to commit - unusual, but occasionally useful when you want a particular test to populate the database - you can call the SetComplete() method inherited from AbstractTransactionalSpringContextTests. This will cause the transaction to commit instead of roll back. There is also convenient ability to end a transaction before the test case ends, through calling the EndTransaction() method. This will roll back the transaction by default, and commit it only if SetComplete() had previously been called. This functionality is useful if you want to test the behavior of 'disconnected' data objects, such as Hibernate-mapped objects that will be used in a web or remoting tier outside a transaction. Often, lazy loading errors are discovered only through UI testing; if you call EndTransaction() you can ensure correct operation of the UI through your NUnit test suite.
adoTemplate: inherited from AbstractTransactionalDbProviderSpringContextTests. Useful for querying to confirm state. For example, you might query before and after testing application code that creates an object and persists it using an ORM tool, to verify that the data appears in the database. (Spring will ensure that the query runs in the scope of the same transaction.) You will need to tell your ORM tool to 'flush' its changes for this to work correctly, for example using the Flush() method on NHibernate's ISession interface. Often you will provide an application-wide superclass for integration tests that provides further useful instance variables used in many tests.
175
176
17.2. Motivations
The data access technology landscape is a broad one, within the .NET BCL there are three APIs for performing transaction management, namely ADO.NET, Enterprise Services, and System.Transactions. Other data access technologies such as object relational mappers and result-set mapping libraries are also gaining in popularity and each come with their own APIs for transaction management. As such, code is often directly tied to a particular transaction API which means you must make an up-front decision which API to use in your application. Furthermore, if the need arises to change your approach, it quite often will not be a simple refactoring. Using Spring's transaction API you can keep the same API across different data access technologies. Changing the underlying transaction implementation that is used is a simple matter of configuration or a centralized programmatic change as compared to a major overhauling. Hand in hand with the variety of options available is the establishment generally agreed upon best practices for data access. Martin Fowler's book, Patterns of Enterprise Application Architecture, is an excellent source of approaches to data access that have been successful in the real world. One approach that is quite common is to introduce a data access layer into your architecture. The data access layer is concerned not only with providing some portability between different data access technologies and databases but its scope is strictly related to data access. A simple data access layer would be not much more than data access objects (DAOs) with 'Create/Retrieve/ Update/Delete' (CRUD) methods devoid of any business logic. Business logic resides in another application layer, the business service layer, in which business logic will call one or more DAOs to fulfill a higher level end-user function.
177
Transaction management In order to perform this end-user function with all-or-nothing transactional semantics, the transaction context is controlled by the business service layer (or other 'higher' layers). In such a common scenario, an important implementation detail is how to make the DAO objects aware of the 'outer' transaction started in another layer. A simplistic implementation of a DAO would perform its own connection and transaction management, but this would not allow grouping of DAO operations with the same transaction as the DAO is doing its own transaction/ resource management. As such there needs to be a means to transfer the connection/transaction pair managed in the business service layer to the DAOs. There are a variety of ways to do this, the most invasive being the explicitly pass a connection/transaction object as method arguments to your DAOs. Another way is to store the connection/transaction pair in thread local storage. In either case, if you are using ADO.NET you must invent some infrastructure code to perform this task. But wait, doesn't Enterprise Services solve this problem - and what about the functionality in the System.Transactions namespace? The answer is yes...and no. Enterprise Services lets you use the 'raw' ADO.NET API within a transaction context such that multiple DAO operations are grouped within the same transaction. The downside to Enterprise Services is that it always uses distributed (global) transactions via the Microsoft Distributed Transaction Coordinator (MS-DTC). For most applications this is overkill just to get this functionality as global transactions are significantly less performant than local ADO.NET transactions. There are similar issues with using the 'using TransactionScope' construct within the new System.Transactions namespace. The goal with TransactionScope is to define a, well - transaction scope - within a using statement. Plain ADO.NET code within that using block will then be a local ADO.NET based transaction if only a single transactional resource is accessed. However, the 'magic' of System.Transactions (and the database) is that local transactions will be promoted to distributed transactions when a second transaction resource is detected. The name that this goes by is Promotable Single Phase Enlistment (PSPE). However, there is a big caveat - opening up a second IDbConnection object to the same database with the same database string will trigger promotion from local to global transactions. As such, if your DAOs are performing their own connection management you will end up being bumped up to a distributed transaction. In order to avoid this situation for the common case of an application using a single database, you must pass around a connection object to your DAOs. It is also worth to note that many database providers (Oracle for sure) do not yet support PSPE and as such will always use a distributed transaction even if there is only a single database. Last but not least is the ability to use declarative transaction management. Not many topics in database transactionland give developers as much 'bang-for-the-buck' as declarative transactions since the noisy tedious bits of transactional API code in your application are pushed to the edges, usually in the form of class/method attributes. Only Enterprise Services offers this feature in the BCL. Spring fills the gap - it provides declarative transaction management if you are using local ADO.NET or System.Transactions (the most popular) or other data access technologies. Enterprise Services is not without it small warts as well, such as the need to separate your query/ retrieve operations from your create/update/delete operations if you want to use different isolation levels since declarative transaction metadata can only be applied at the class level. Nevertheless, all in all, Enterprise Services, in particular with the new 'Services Without Components' implementation for XP SP2/Server 2003, and hosted within the same process as your application code is as good as it gets out of the .NET box. Despite these positive points, it hasn't gained a significant mindshare in the development community. Spring's transaction support aims to relieve these 'pain-points' using the data access technologies within the BCL - and for other third party data access technologies as well. It provides declarative transaction management with a configurable means to obtain transaction option metadata - out of the box attributes and XML within Spring's IoC configuration file are supported. Finally, Spring's transaction support lets you mix data access technologies within a single transaction - for example ADO.NET and NHibernate operations.
178
Transaction management With this long winded touchy/feely motivational section behind us, lets move on to see the code.
This is primarily a 'SPI' (Service Provider Interface), although it can be used Programatically. Note that in keeping with the Spring Framework's philosophy, IPlatformTransactionManager is an interface, and can thus be easily mocked or stubbed as necessary. IPlatformTransactionManager implementations are defined like any other object in the IoC container. The following implementations are provided AdoPlatformTransactionManager - local ADO.NET based transactions ServiceDomainPlatformTransactionManager - distributed transaction manager from Enterprise Services TxScopePlatformTransactionManager - local/distributed transaction manager from System.Transactions. HibernatePlatformTransactionManager - local transaction manager for use with NHibernate or mixed ADO.NET/NHibernate data access operations. Under the covers, the following API calls are made. For the AdoPlatformTransactionManager, Transaction.Begin(), Commit(), Rollback(). ServiceDomainPlatformTransactionManager uses the 'Services without Components' update so that your objects do not need to inherit from ServicedComponent or directly call the Enterprise Services API ServiceDomain.Enter(), Leave; ContextUtil.SetAbort(). TxScopePlatformTransactionManager calls; new TransactionScope(); .Complete(), Dispose(), Transaction.Current.Rollback(). Configuration properties for each transaction manager are specific to the data access technology used. Refer to the API docs for comprehensive information but the examples should give you a good basis for getting started. The HibernatePlatformTransactionManager is described more in the following section . The method returns a ITransactionStatus object, depending on a ITransactionDefinition parameters. The returned ITransactionStatus might represent a new or existing transaction (if there was a matching transaction in the current call stack - with the implication being that a ITransactionStatus is associated with a logical thread of execution.
GetTransaction(..)
The ITransactionDefinition interface specified Isolation: the degree of isolation this transaction has from the work of other transactions. For example, can this transaction see uncommitted writes from other transactions? Propagation: normally all code executed within a transaction scope will run in that transaction. However, there are several options specifying behavior if a transactional method is executed when a transaction context already exists: for example, simply continue running in the existing transaction (the common case); or suspending the existing transaction and creating a new transaction.
179
Transaction management Timeout: how long this transaction may run before timing out (and automatically being rolled back by the underlying transaction infrastructure). Read-only status: a read-only transaction does not modify any data. Read-only transactions can be a useful optimization in some cases (such as when using NHibernate). These settings reflect standard transactional concepts. If necessary, please refer to a resource discussing transaction isolation levels and other core transaction concepts because understanding such core concepts is essential to using the Spring Framework or indeed any other transaction management solution. The ITransactionStatus interface provides a simple way for transactional code to control transaction execution and query transaction status. Regardless of whether you opt for declarative or programmatic transaction management in Spring, defining the correct IPlatformTransactionManager implementation is absolutely essential. In good Spring fashion, this important definition typically is made using via Dependency Injection. implementations normally require knowledge of the environment in which they work, ADO.NET, NHibernate, etc. The following example shows how a standard ADO.NET based IPlatformTransactionManager can be defined.
IPlatformTransactionManager
We must define a Spring IDbProvider and then use Spring's AdoPlatformTransactionManager, giving it a reference to the IDbProvider. For more information on the IDbProvider abstraction refer to the next chapter.
<objects xmlns='http://www.springframework.net' xmlns:db="http://www.springframework.net/database"> <db:provider id="DbProvider" provider="SqlServer-1.1" connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/> <object id="TransactionManager" type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> . . . other object definitions . . . </objects>
We can also use a transaction manager based on System.Transactions just as easily, as shown in the following example
<object id="TransactionManager" type="Spring.Data.Core.TxScopeTransactionManager, Spring.Data"> </object>
Similarly for the HibernateTransactionManager as shown in the section on ORM transaction management. Note that in all these cases, application code will not need to change at all since, dependency injection is a perfect companion to using the strategy pattern. We can now change how transactions are managed merely by changing configuration, even if that change means moving from local to global transactions or vice versa.
180
Transaction management
181
Transaction management Declarative rollback rules. Rollback rules can be control declaratively and allow for only specified exceptions thrown within a transactional context to trigger a rollback Spring gives you an opportunity to customize transactional behavior, using AOP. For example if you want to insert custom behavior in the case of a transaction rollback, you can. You can also add arbitrary advice, along with the transactional advice. Spring does not support propagation of transaction context across remote calls. The concept of rollback rules is important: they enable us to specify which exceptions should cause automatic roll back. We specify this declaratively, in configuration, not in code. So, although you can still call SetRollbackOnly() on the ITransactionStatus object to roll the current transaction back, most often you can specify a rule that MyApplicationException must always result in rollback. This has the significant advantage that business objects do not depend on the transaction infrastructure. For example, they typically don't need to import any Spring transaction APIs or other Spring APIs. However, to rollback the transaction programmatically when using declarative transaction management, use the utility method
TransactionInterceptor.CurrentTransactionStatus.SetRollbackOnly();
Note
Prior to Spring.NET 1.2 RC1 the API call would be
TransactionInterceptor.CurrentTransactionStatus.RollbackOnly = true;
Note
Spring AOP is covered in Chapter 13, Aspect Oriented Programming with Spring.NET Conceptually, calling a method on a transactional proxy looks like this.
182
Transaction management
Note
A QuickStart application for declarative transaction management is included in the Spring.NET distribution and is decribed here. The ITestObjectManager is a poor-mans business service layer - the implementation of which will make two DAO calls. Clearly this example is overly simplistic from the service layer perspective as there isn't any business logic at all!. The 'service' interface is shown below.
public interface ITestObjectManager { void SaveTwoTestObjects(TestObject to1, TestObject to2); void DeleteTwoTestObjects(string name1, string name2); }
183
Transaction management
TestObjectDao.Create(to1.Name, to1.Age); TestObjectDao.Create(to2.Name, to1.Age); } [Transaction] public void DeleteTwoTestObjects(string name1, string name2) { TestObjectDao.Delete(name1); TestObjectDao.Delete(name2); } }
Note the Transaction attribute on the methods. Other options such as isolation level can also be specified but in this example the default settings are used. However, please note that the mere presence of the Transaction attribute is not enough to actually turn on the transactional behavior - the Transaction attribute is simply metadata that can be consumed by something that is Transaction attribute-aware and that can use the said metadata to configure the appropriate objects with transactional behavior. The TestObjectDao property has basic create update delete and find method for the 'domain' object TestObject. TestObject in turn has simple properties like name and age.
public interface ITestObjectDao { void Create(string name, int age); void Update(TestObject to); void Delete(string name); TestObject FindByName(string name); IList FindAll(); }
The Create and Delete method implementation is shown below. Note that this uses the AdoTemplate class discussed in the following chapter. Refer to Section 17.4, Resource synchronization with transactions for information on the interaction between Spring's high level persistence integration APIs and transaction management features.
public class TestObjectDao : AdoDaoSupport, ITestObjectDao { public void Create(string name, int age) { AdoTemplate.ExecuteNonQuery(CommandType.Text, String.Format("insert into TestObjects(Age, Name) VALUES ({0}, '{1}')", age, name)); } public void Delete(string name) { AdoTemplate.ExecuteNonQuery(CommandType.Text, String.Format("delete from TestObjects where Name = '{0}'", name)); } }
The TestObjectManager is configured with the DAO objects by standard dependency injection techniques. The client code, which in this case directly asks the Spring IoC container for an instance of ITestObjectManager, will receive a transaction proxy with transaction options based on the attribute metadata. Note that typically the ITestObjectManager would be set on yet another higher level object via dependency injection, for example a web service. The client calling code is shown below
IApplicationContext ctx = new XmlApplicationContext("assembly://Spring.Data.Integration.Tests/Spring.Data/ autoDeclarativeServices.xml"); ITestObjectManager mgr = ctx["testObjectManager"] as ITestObjectManager;
184
Transaction management
TestObject to1 = new TestObject(); to1.Name = "Jack"; to1.Age = 7; TestObject to2 = new TestObject(); to2.Name = "Jill"; to2.Age = 8; mgr.SaveTwoTestObjects(to1, to2); mgr.DeleteTwoTestObjects("Jack", "Jill");
The configuration of the object definitions of the DAO and manager classes is shown below.
<objects xmlns='http://www.springframework.net' xmlns:db="http://www.springframework.net/database"> <db:provider id="DbProvider" provider="SqlServer-1.1" connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/> <object id="transactionManager" type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="testObjectDao" type="Spring.Data.TestObjectDao, Spring.Data.Integration.Tests"> <property name="AdoTemplate" ref="adoTemplate"/> </object>
<!-- The object that performs multiple data access operations --> <object id="testObjectManager" type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object>
</objects>
This is standard Spring configuration and as such provides you with the flexibility to parameterize your connection string and to easily switch implementations of your DAO objects. The following section shows how to configure the declarative transactions using Spring's transaction namespace.
185
Transaction management
</configSections> <spring> <parsers> <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /> <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" /> <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" /> </parsers> </spring> </configSections>
Instead of using the XML configuration listed at the end of the previous section (declarativeServices.xml you can use the following. Note that the schemaLocation in the objects element is needed only if you have not installed Spring's schema into the proper VS.NET 2005 location. See the chapter on VS.NET integration for more details.
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.net/tx" xmlns:db="http://www.springframework.net/database" xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/schema/objects/ spring-objects.xsd http://www.springframework.net/schema/tx http://www.springframework.net/schema/tx/spring-tx-1.1.xsd" http://www.springframework.net/schema/db http://www.springframework.net/schema/db/springdatabase.xsd"> <db:provider id="DbProvider" provider="SqlServer-1.1" connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/> <object id="transactionManager" type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="testObjectDao" type="Spring.Data.TestObjectDao, Spring.Data.Integration.Tests"> <property name="AdoTemplate" ref="adoTemplate"/> </object>
<!-- The object that performs multiple data access operations --> <object id="testObjectManager" type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object>
Tip
You can actually omit the 'transaction-manager' attribute in the <tx:attribute-driven/> tag if the object name of the IPlatformTransactionManager that you want to wire in has the name 'transactionManager'. If the PlatformTransactionManager object that you want to dependency inject has any other name, then you have to be explicit and use the 'transaction-manager' attribute as in the example above. The various optional elements of the <tx:attribute-driven/> tag are summarised in the following table
186
Required? No
Default transactionManager
Description The name of transaction manager to use. Only required if the name of the transaction manager is not transactionManager, as in the example above. Controls what type of transactional proxies are created for classes annotated with the [Transaction] attribute. If "proxy-target-type" attribute is set to "true", then class-based proxies will be created (proxy inherits from target class, however calls are still delegated to target object via composition. This allows for casting to base class. If "proxy-targettype" is "false" or if the attribute is omitted, then a pure composition based proxy is created and you can only cast the proxy to implemented interfaces. (See the section entitled Section 13.6, Proxying mechanisms for a detailed examination of the different proxy types.) Defines the order of the transaction advice that will be applied to objects annotated with [Transaction]. More on the rules related to ordering of AOP advice can be found in the AOP chapter (see section Section 13.3.2.5, Advice Ordering). Note that not specifying any ordering
proxy-target-type
No
order
No
187
Transaction management Attribute Required? Default Description will leave the decision as to what order advice is run in to the AOP subsystem.
Note
The "proxy-target-type" attribute on the <tx:attribute-driven/> element controls what type of transactional proxies are created for classes annotated with the Transaction attribute. If "proxytarget-type" attribute is set to "true", then inheritance-based proxies will be created. If "proxytarget-type" is "false" or if the attribute is omitted, then composition based proxies will be created. (See the section entitled Section 13.6, Proxying mechanisms for a detailed examination of the different proxy types.) You can also define the transactional semantics you want to apply through the use of a <tx:advice> definition. This lets you define the transaction metadata such as propagation and isolation level as well as the methods for which that metadata applies external to the code unlike the case of using the transaction attribute. The <tx:advice> definition creates an instance of a ITransactionAttributeSource during parsing time. Switching to use <tx:advice> instead of <tx:attribute-driven/> in the example would look like the following
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="Save*"/> <tx:method name="Delete*"/> </tx:attributes> </tx:advice>
This says that all methods that start with Save and Delete would have associated with them the default settings of transaction metadata. These default values are listed below.. Here is an example using other elements of the <tx:method/> definition
<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> object below) --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="Get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice>
The <tx:advice/> definition reads as ... all methods on starting with 'Get' are to execute in the context of a readonly transaction, and all other methods are to execute with the default transaction semantics. The 'transactionmanager' attribute of the <tx:advice/> tag is set to the name of the PlatformTransactionManager object that is going to actually drive the transactions (in this case the 'transactionManager' object). You can also use the AOP namespace <aop:advisor> element to tie together a pointcut and the above defined advice as shown below.
<object id="serviceOperation" type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop"> <property name="pattern" value="Spring.TxQuickStart.Services.*"/> </object> <aop:config> <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
188
Transaction management
</aop:config>
This is assuming that the service layer class, TestObjectManager, in the namespace Spring.TxQuickStart.Services. The <aop:config/> definition ensures that the transactional advice defined by the 'txAdvice' object actually executes at the appropriate points in the program. First we define a pointcut that matches any operation defined on classes in the Spring.TxQuickStart.Services (you can be more selective in your regular expression). Then we associate the pointcut with the 'txAdvice' using an advisor. In the example, the result indicates that at the execution of a 'SaveTwoTestObjects' and 'DeleteTwoTestObject', the advice defined by 'txAdvice' will be run. The various transactional settings that can be specified using the <tx:advice/> tag. The default <tx:advice/> settings are listed below and are the same as when you use the Transaction attribute. The propagation setting is TransactionPropagation.Required The isolation level is IsolationLevel.ReadCommitted The transaction is read/write The transaction timeout defaults to the default timeout of the underlying transaction system, or none if timeouts are not supported EnterpriseServicesInteropOption (.NET 2.0 only with TxScopeTransactionManager) - options between transaction created with System.Transactions and transactions created through COM+ Any exception will trigger rollback. These default settings can be changed; the various attributes of the <tx:method/> tags that are nested within <tx:advice/> and <tx:attributes/> tags are summarized below: Table 17.2. <tx:method/> settings Attribute
name
Required? Yes
Default
Description The method name(s) with which the transaction attributes are to be associated. The wildcard (*) character can be used to associate the same transaction attribute settings with a number of methods; for example, 'Get*', 'Handle*', 'On*Event', and so forth.
propagation
No
Required
The transaction propagation behavior The transaction isolation level The transaction timeout value (in seconds)
isolation
No
ReadCommitted
timeout
No
-1
189
Required? No
Default false
Description Is this transaction readonly? Interoperability options with COM+ transactions. (.NET 2.0 and TxScopeTransactionManager only) The Exception(s) that will trigger rollback; comma-delimited. For example,
EnterpriseServicesInteropOption No
None
rollback-for
No
'MyProduct.MyBusinessException,Va no-rollback-for
No
The Exception(s) that will not trigger rollback; comma-delimited. For example,
'MyProduct.MyBusinessException,Va
optional propagation setting. Required, Spring.Transaction.TransactionPropagation Supports, Mandatory, RequiresNew, NotSupported, Never, Nested
System.Data.IsolationLevel
Isolation
190
EnterpriseServicesInteropOption enumeration Options for interoperability with COM System.Transactions.EnterpriseServicesInteropOption (.NET + transactions 2.0 and TxScopeTransactionManager only) Timeout RollbackFor int (in seconds granularity) an array of Type objects the transaction timeout an optional array of exception classes that must cause rollback an optional array of exception classes that must not cause rollback
NoRollbackFor
Note that setting the TransactionPropagation to Nested will throw a NestedTransactionNotSupportedException in a case where an actual nested transaction occurs, i.e. not in the case of applying the Nested propagation but in fact no nested calls are made. This will be fixed for the Spring 1.2 release for SqlServer and Oracle which support nested transactions. Also note, that changing of isolation levels on a per-method basis is also scheduled for the Spring 1.2 release since it requires detailed command text metadata for each dbprovider. Please check the forums for news on when this feature will be introduced into the nightly builds. If you specify an exception type for 'NoRollbackFor' the action taken is to commit the work that has been done in the database up to the point where the exception occurred. The exception is still propagated out to the calling code. The ReadOnly boolean is a hint to the data access technology to enable read-only optimizations. This currently has no effect in Spring's ADO.NET framework. If you would like to enable read-only optimizations in ADO.NET this is generally done via the 'Mode=Read' or 'Mode=Read-Only" options in the connection string. Check your database provider for more information. In the case of NHibernate the flush mode is set to Never when a new Session is created for the transaction. Throwing exceptions to indicate failure and assuming success is an easier and less invasive programming model than performing the same task Programatically - ContextUtil.MyTransactionVote or TransactionScope.Complete. The rollback options are a means to influence the outcome of the transaction based on the exception type which adds an extra degree of flexibility. Having any exception trigger a rollback has similar behavior as applying the AutoComplete attribute available when using .NET Enterprise Services. The difference with AutoComplete is that using AutoComplete is also coupled to the lifetime of the ServicedComponent since it sets ContextUtil.DeactivateOnReturn to true. For a stateless DAO layer this is not an issue but it could be in other scenarios. Spring's transactional aspect does not affect the lifetime of your object.
191
Transaction management 17.5.5.1. Creating transactional proxies with ObjectNameAutoProxyCreator The ObjectNameAutoProxyCreator is useful when you would like to create transactional proxies for many objects. The definitions for the TransactionInterceptor and associated attributes is done once. When you add new objects to your configuration file that need to be proxies you only need to add them to the list of object referenced in the ObjectNameAutoProxyCreator. Here is an example showing its use. Look in the section that use ProxyFactoryObject for the declaration of the transactionInterceptor.
<object name="autoProxyCreator" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> <property name="InterceptorNames" value="transactionInterceptor"/> <property name="ObjectNames"> <list> <idref local="testObjectManager"/> </list> </property> </object>
17.5.5.2. Creating transactional proxies with DefaultAdvisorAutoProxyCreator This is not longer a common way to configure declarative transactions but is discussed in the "Classic Spring" appendiex here.
Note
As you will immediately see in the examples that follow, using the TransactionTemplate absolutely couples you to Spring's transaction infrastructure and APIs. Whether or not programmatic transaction management is suitable for your development needs is a decision that you will have to make yourself. Application code that must execute in a transaction context looks like this. You, as an application developer, will write a ITransactionCallback implementation (typically expressed as an anonymous delegate) that will contain all of the code that you need to have execute in the context of a transaction. You will then pass an instance of
192
Transaction management your custom ITransactionCallback to the Execute(..) method exposed on the TransactionTemplate. Note that the ITransactionCallback can be used to return a value:
public class SimpleService : IService { private TransactionTemplate transactionTemplate; public SimpleService(IPlatformTransactionManager transactionManager) { AssertUtils.ArgumentNotNull(transactionManager, "transactionManager"); transactionTemplate = new TransactionTemplate(transactionManager); } public object SomeServiceMethod() { return tt.Execute(delegate { UpdateOperation(userId); return ResultOfUpdateOperation2(); }); } }
This code example is specific to .NET 2.0 since it uses anonymous delegates, which provides a particularly elegant means to invoke a callback function as local variables can be referred to inside the delegate, i.e. userId. In this case the ITransactionStatus was not exposed in the delegate (delegate can infer the signature to use), but one could also obtain a reference to the ITransactionStatus instance and set the RollbackOnly property to trigger a rollback - or alternatively throw an exception. This is shown below
tt.Execute(delegate(ITransactionStatus status) { try { UpdateOperation1(); UpdateOperation2(); } catch (SomeBusinessException ex) { status.RollbackOnly = true; } return null; });
If you are using .NET 1.1 then you should provide a normal delegate reference or an instance of a class that implements the ITransactionCallback interface. This is shown below
tt.Execute(new TransactionRollbackTxCallback(amount));
public class TransactionRollbackTxCallback : ITransactionCallback { private decimal amount; public TransactionRollbackTxCallback(decimal amount) { this.amount = amount } public object DoInTransaction(ITransactionStatus status) { adoTemplate.ExecuteNonQuery(CommandType.Text, "insert into dbo.Debits (DebitAmount) VALUES (@amount)", "amount", DbType.Decimal, 0,555); // decide you need to rollback... status.RollbackOnly = true; return null; } }
Application
to use the TransactionTemplate must have access to a IPlatformTransactionManager (which will typically be supplied to the class via dependency injection). It is easy to unit test such classes with a mock or stub IPlatformTransactionManager.
classes
wishing
193
Transaction management 17.6.1.1. Specifying transaction settings Transaction settings such as the propagation mode, the isolation level, the timeout, and so forth can be set on the TransactionTemplate either programmatically or in configuration. TransactionTemplate instances by default have the default transactional settings. Find below an example of programmatically customizing the transactional settings for a specific TransactionTemplate.
public class SimpleService : IService { private TransactionTemplate transactionTemplate; public SimpleService(IPlatformTransactionManager transactionManager) { AssertUtils.ArgumentNotNull(transactionManager, "transactionManager"); transactionTemplate = new TransactionTemplate(transactionManager); // the transaction settings can be set here explicitly if so desired transactionTemplate.TransactionIsolationLevel = IsolationLevel.ReadUncommitted; transactionTemplate.TransactionTimeout = 30; // and so forth... } . . . }
Find below an example of defining a TransactionTemplate with some custom transactional settings, using Spring XML configuration. The 'sharedTransactionTemplate' can then be injected into as many services as are required.
<object id="sharedTransactionTemplate" type="Spring.Transaction.Support.TransactionTemplate, Spring.Data"> <property name="TransactionIsolationLevel" value="IsolationLevel.ReadUncommitted"/> <property name="TransactionTimeout" value="30"/> </object>
Finally, instances of the TransactionTemplate class are threadsafe, in that instances do not maintain any conversational state. TransactionTemplate instances do however maintain configuration state, so while a number of classes may choose to share a single instance of a TransactionTemplate, if a class needed to use a TransactionTemplate with different settings (for example, a different isolation level), then two distinct TransactionTemplate instances would need to be created and used.
194
Transaction management
transactionManager.Commit(status);
Note that a corresponding 'using TransactionManagerScope' class can be modeled to get similar API usage to System.Transactions TransactionScope.
can
query
the
status
of
Please refer to the SDK docs for information on other methods in this class. The ITransactionSynchronization interface is
public interface ITransactionSynchronization { // Typically used by Spring resource management code void Suspend(); void Resume(); // Transaction lifeycyle callback methods // Typically used by Spring resource management code but maybe useful in certain cases to application code void BeforeCommit( bool readOnly ); void AfterCommit(); void BeforeCompletion(); void AfterCompletion( TransactionSynchronizationStatus status ); }
The TransactionSynchronizationStatus is an enum with the values Committed, Rolledback, and Unknown.
195
196
DAO support
(Please note that the class hierarchy detailed in the above image shows only a subset of the whole, rich, DataAccessException hierarchy.) The exception translation functionality is in the namespace Spring.Data.Support and is based on the interface IAdoExceptionTranslator shown below.
public interface IAdoExceptionTranslator { DataAccessException Translate( string task, string sql, Exception exception ); }
The arguments to the translator are a task string providing a description of the task being attempted, the SQL query or update that caused the problem, and the 'raw' exception thrown by the ADO.NET data provider. The additional task and SQL arguments allow for very readable and clear error messages to be created when an exception occurs. A default implementation, ErrorCodeExceptionTranslator, is provided that uses the error codes defined for each data provider in the file dbproviders.xml. Refer to this file, an embedded resource in the Spring.Data assembly, for the exact mappings of error codes to Spring DataAccessExceptions. A common need is to modify the error codes that are map onto the exception hierarchy. There are several ways to accomplish this task. One approach is to override the error codes that are defined in assembly://Spring.Data/Spring.Data.Common/ dbproviders.xml. By default, the DbProviderFactory will look for additional metadata for the IoC container it uses internally to define and manage the DbProviders in a file named dbProviders.xml located in the root
197
DAO support runtime directory. (You can change this location, see the documentation on DbProvider for more information.) This is a standard Spring application context so all features, such as ObjectFactoryPostProcessors are available and will be automatically applied. Defining a PropertyOverrideConfigurer in this additional configuration file will allow for you to override specific property values defined in the embedded resource file. As an example, the additional dbProviders.xml file shown below will add the error code 2601 to the list of error codes that map to a DataIntegrityViolationException.
<objects xmlns='http://www.springframework.net'> <alias name='SqlServer-2.0' alias='SqlServer2005'/> <object name="appConfigPropertyOverride" type="Spring.Objects.Factory.Config.PropertyOverrideConfigurer, Spring.Core"> <property name="Properties"> <name-values> <add key="SqlServer2005.DbMetadata.ErrorCodes.DataIntegrityViolationCodes" value="544,2601,2627,8114,8115"/> </name-values> </property> </object> </objects>
The reason to define the alias is that PropertyOverrideConfigurer assumes a period (.) as the separator to pick out the object name but the names of the objects in dbProviders.xml have periods in them (i.e. SqlServer-2.0 or System.Data.SqlClient). Creating an alias that has no periods in the name is a workaround. Another subclass way mappings of error codes to ErrorCodeExceptionTranslator and override the method, to customize the exceptions is to
DataAccessException
This will be called before referencing the metadata to perform exception translation. The vendor specific error code provided as a method argument has already been parsed out of the raw ADO.NET exception. If you create your own specific subclass, then you should set the property ExceptionTranslator on AdoTemplate and HibernateTemplate/ HibernateTransactionManager to refer to your custom implementation (unless you are using autowiring). The third way is to write an implementation of IAdoExceptionTranslator and set the property FallbackTranslator'on ErrorCodeExceptionTranslator. In this case you are responsible for parsing our the vendor specific error code from the raw ADO.NET exception. As with the case of subclassing ErrorCodeExceptionTranslator, you will need to refer to this custom exception translator when using AdoTemplate or HibernateTemplate/HibernateTransactionManager. The ordering of the exception translation processing is as follows. The method TranslateException is called first, then the standard exception translation logic, then the FallbackTranslator. Note that you can use this API directly in your own Spring independent data layer. If you are using Spring's ADO.NET abstraction class, AdoTemplate, or HibernateTemplate, the converted exceptions will be thrown automatically. Somewhere in between these two cases is using Spring's declarative transaction management features in .NET 2.0 with the raw ADO.NET APIs and using IAdoExceptionTranslator in your exception handling layer (which might be implemented in AOP using Spring's exception translation aspect). Some of the more common data access exceptions are described here. Please refer to the API documentation for more details. Table 18.1. Common DataAccessExceptions Exception BadSqlGrammarException Description Exception thrown when SQL specified is invalid.
198
DAO support Exception DataIntegrityViolationException Description Exception thrown when an attempt to insert or update data results in violation of an integrity constraint. For example, inserting a duplicate key. Exception thrown when the underling resource denied a permission to access a specific element, such as a specific database table. Exception thrown when a resource fails completely, for example, if we can't connect to a database. Exception thrown when a concurrency error occurs. OptimisticLockingFailureException and PessimisticLockingFailureException are subclasses. This is a useful exception to catch and to retry the transaction again. See Spring's Retry Aspect for an AOP based solution. Exception thrown when there an optimistic locking failure occurs. The subclass ObjectOptimisticLockingFailureException can be used to examine the Type and the IDof the object that failed the optimistic locking. Exception thrown when a pessimistic locking failure occures. Subclasses of this exception are CannotAcquireLockException, CannotSerializeTransactionException, and DeadlockLoserDataAccessException. Exception thrown when a lock can not be acquired, for example during an update, i..e a select for update Exception thrown when a transaction can not be serialized.
PermissionDeniedDataAccessException
DataAccessResourceFailureException
ConcurrentyFailureException
OptimisticLockingFailureException
PessimisticLockingFailure
CannotAcquireLockException
CannotSerializeTransactionException
199
DAO support to subclasses. Can alternatively be initialized directly via a HibernateTemplate, to reuse the latter's settings like SessionFactory, flush mode, exception translator, etc. This is contained in a download separate from the main Spring.NET distribution.
SessionFactory
200
201
DbProvider
ExtractError is used to return an error string for translation into a DAO exception. On .NET 1.1 the method IsDataAccessException is used to determine if the thrown exception is related to data access since in .NET 1.1 there isn't a common base class for database exceptions. CreateParameterName is used to create the string for parameters used in a CommandText object while CreateParameterNameForCollection is used to create the string for a IDataParameter.ParameterName, typically contained inside a IDataParameterCollection. The class DbProviderFactory creates IDbProvider instances given a provider name. The connection string property will be used to set the IDbConnection returned by the factory if present. The provider names, and corresponding database, currently configured are listed below. SqlServer-1.1 - Microsoft SQL Server, provider V1.0.5000.0 in framework .NET V1.1 SqlServer-2.0 (aliased to System.Data.SqlClient) - Microsoft SQL Server, provider V2.0.0.0 in framework .NET V2.0 SqlServerCe-3.1 - Microsoft SQL Server Compact Edition, provider V9.0.242.0 SqlServerCe-3.5.1 (aliased to System.Data.SqlServerCe) - Microsoft SQL Server Compact Edition, provider V3.5.1.0 OleDb-1.1 - OleDb, provider V1.0.5000.0 in framework .NET V1.1 OleDb-2.0 (aliased to System.Data.OleDb) - OleDb, provider V2.0.0.0 in framework .NET V2.0 OracleClient-2.0 (aliased to System.Data.OracleClient) - Oracle, Microsoft provider V2.0.0.0 OracleODP-2.0 (aliased to System.DataAccess.Client) - Oracle, Oracle provider V2.102.2.20 (Oracle 10g) OracleODP-11-2.0 - Oracle, Oracle provider V2.111.7.20 (Oracle 11g) MySql - MySQL, MySQL provider 1.0.10.1 MySql-1.0.9 - MySQL, MySQL provider 1.0.9 MySql-5.0 - MySQL, MySQL provider 5.0.7.0 MySql-5.0.8.1 - MySQL, MySQL provider 5.0.8.1 MySql-5.1 - MySQL, MySQL provider 5.1.2.2 MySql-5.1.4 - MySQL, MySQL provider 5.1.2.2 MySql-5.2.3 - MySQL, MySQL provider 5.2.3.0 MySql-6.1.3 - MySQL, MySQL provider 6.1.3.0 MySql-6.2.2 (aliased to MySql.Data.MySqlClient) - MySQL, MySQL provider 6.2.2.0 Npgsql-1.0 - Postgresql provider 1.0.0.0 (and 1.0.0.1 - were build with same version info) Npgsql-2.0-beta1 - Postgresql provider 1.98.1.0 beta 1
202
DbProvider Npgsql-2.0 - Postgresql provider 2.0.0.0 DB2-9.0.0-1.1 - IBM DB2 Data Provider 9.0.0 for .NET Framework 1.1 DB2-9.0.0-2.0 (aliased to IBM.Data.DB2) - IBM DB2 Data Provider 9.0.0 for .NET Framework 2.0 DB2-9.1.0-1.1 - IBM DB2 Data Provider 9.1.0 for .NET Framework 1.1 DB2-9.1.0.2 (aliased to IBM.Data.DB2.9.1.0) - IBM DB2 Data Provider 9.1.0 for .NET Framework 2.0 iDB2-10.0.0.0 - IBM iSeries DB2 Data Provider 10.0.0.0 for .NET Framework 2.0 SQLite-1.0.43 - SQLite provider 1.0.43 for .NET Framework 2.0 SQLite-1.0.44 - SQLite provider 1.0.44 for .NET Framework 2.0 SQLite-1.0.47 - SQLite provider 1.0.47 for .NET Framework 2.0 SQLite-1.0.56 - SQLite provider 1.0.56 for .NET Framework 2.0 SQLite-1.0.65 (aliased to System.Data.SQLite) - SQLite provider 1.0.65 for .NET Framework 2.0 SQLite-1.0.65 - SQLite provider 1.0.66 for .NET Framework 2.0 SQLite-1.0.72 - SQLite provider 1.0.72 for .NET Framework 2.0 from http://sqlite.phxsoftware.com/
Note
The default parameter prefix used in SQLite is : and not @, please write your SQL accordingly or define a provider definition for SQLite.
Firebird-2.1 (aliased to Firebird-2.1) - Firebird Server, provider V2.1.0.0 in framework .NET V2.0 SybaseAse-12 - Sybase ASE provider for ASE 12.x SybaseAse-15 - Sybase ASE provider for ASE 15.x SybaseAse-AdoNet2 - Sybase ADO.NET 2.0 provider for ASE 12.x and 15.x Odbc-1.1 - ODBC provider V1.0.5000.0 in framework .NET V1.1 Odbc-2.0 - ODBC provider V2.0.0.0 in framework .NET V2 Cache-2.0.0.1 (aliased to InterSystems.Data.CacheClient) - Cache provider Version 2.0.0.1 in framework .NET V2 IfxOdbc - Informix, ODBC provider in framework .NET V2 IfxSQLI-3.0.0.2 - Informix, old native provider IfxDRDA-9.0.0.2 - Informix, IBM.Data.DB2 9.7
Note
If your exact version of the database provider is not listed, you can pick the general provider name, i.e. MySql.Data.MySqlClient, and then perform an assembly redirect in App.config. This will often be sufficient to upgrade to newer versions. As shown below
203
DbProvider
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Npgsql" publicKeyToken="5d8b90d52f46fda7" culture="neutral"/> <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535 newVersion="2.0.0.0"/> </dependentAssembly> </assemblyBinding> </runtime>
The default definitions of the providers are contained in the assembly resource assembly://Spring.Data/ Spring.Data.Common/dbproviders.xml. If the provider you want to use is not provided "out of the box" you can provide additional definitions. To do this follow the format of object definitions defined in the previously mentioned assembly resource. From Spring 1.3.1 an on you can specify the additional Spring IResource location where additional providers are defined within Spring's XML configuration file. See the next section for an example. Alternatively, you can set the public static property DBPROVIDER_ADDITIONAL_RESOURCE_NAME in DbProviderFactory to a Spring resource location. The default value is file://dbProviders.xml. (That isn't a typo, there is a difference in case with the name of the embedded resource). It may happen that the version number of an assembly you have downloaded is different than the one listed above. If it is a point release, i.e. the API hasn't changed in anyway that is material to your application, you should add an assembly redirect of the form shown below.
<dependentAssembly> <assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" culture="neutral"/> <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="1.0.10.1"/> </dependentAssembly>
This redirects any reference to an older version of the assembly MySql.Data to the version 1.0.10.1.
204
DbProvider
</objects>
If you need to register an additional IDbProvider defintions from your own configuration file, set the attribute 'additonalDbProviders' to the IResource location of those definitions. Examples of the format for additional provider definitions can be found within the Spring.Data assembly, location assembly://Spring.Data/ Spring.Data.Common/dbproviders.xml. Open it up in Visual Studio or Reflector to see the contents of the dbproviders.xml file.
<objects xmlns='http://www.springframework.net' xmlns:db="http://www.springframework.net/database"> <db:additionalProviders resource="assembly://MyAssembly/MyAssembly.MyNamespace/AdditionalProviders.xml"/> <db:provider id="DbProvider" provider="System.Data.SqlClient" connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/>
</objects>
A custom namespace should be registered in the main application configuration file to use this syntax. This configuration, only for the parsers, is shown below. Additional section handlers are needed to specify the rest of the Spring configuration locations as described in previous chapters.
<configuration> <configSections> <sectionGroup name="spring"> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" /> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /> </parsers> </spring> </configuration>
205
DbProvider
<spring> <context> <resource uri="Aspects.xml" /> <resource uri="Services.xml" /> <resource uri="Dao.xml" /> </context> </spring> <!-- These properties are referenced in Dao.xml --> <databaseSettings> <add key="db.datasource" value="(local)" /> <add key="db.user" value="springqa" /> <add key="db.password" value="springqa" /> <add key="db.database" value="Northwind" /> </databaseSettings> </configuration>
Please refer to the Section Section 5.9.2.1, Example: The PropertyPlaceholderConfigurer for more information.
Note
These provider implementations do not take into account usage with NHibernate. NHibernate scopes a SessionFactory, where second level caching is managed, to each connection. This forum thread [http://forum.springframework.net/showthread.php?t=4462], contains an implementation of the class LocalDelegatingSessionFactoryObject that will create multiple SessionFactories for each database connection.
19.5.1. UserCredentialsDbProvider
This UserCredentialsDbProvider will allow you to change the username and password of a database connection at runtime. The API contains the properties Username and Password which are used as the default strings representing the user and password in the connection string. You can then change the value of these properties in the connection string by calling the method SetCredentialsForCurrentThread and fall back to the default values by calling the method RemoveCredentialsFromCurrentThread. You call the SetCredentialsForCurrentThread method at runtime, before any data access occurs, to determine which database user should be used for the current
206
DbProvider user-case. Which user to select is up to you. You may retrieve the user information from an HTTP session for example. Example configuration and usage is shown below
<object id="DbProvider" type="Spring.Data.Common.UserCredentialsDbProvider, Spring.Data"> <property name="TargetDbProvider" ref="targetDbProvider"/> <property name="Username" value="User ID=defaultName"/> <property name="Password" value="Password=defaultPass"/> </object> <db:provider id="targetDbProvider" provider="SqlServer-2.0" connectionString="Data Source=MARKT60\SQL2005;Database=Spring;Trusted_Connection=False"/>
If you use dependency injection to configure a class with a property of the type IDbProvider, you will need to downcast to the subtype or you can change your class to have a property of the type UserCredentialsDbProvider instead of IDbProvider.
userCredentialsDbProvider.SetCredentialsForCurrentThread("User ID=springqa", "Password=springqa");
UserCredentialsDbProvider's has a base class, DelegatingDbProvider, and is intended for you to use in your
own implementations that delegate calls to a target IDbProvider instance. This class in meant to be subclassed with subclasses overriding only those methods, such as CreateConnection(), that should not simply delegate to the target IDbProvider.
19.5.2. MultiDelegatingDbProvider
There are use-cases in which there will need to be a runtime selection of the database to connect to among many possible candidates. This is often the case where the same schema is installed in separate databases for different clients. The MultiDelegatingDbProvider implements the IDbProvider interface and provides an abstraction to the multiple databases and can be used in DAO layer such that the DAO layer is unaware of the switching between databases. MultiDelegatingDbProvider does its job by looking into thread local storage. This storage location stores the name of the dbProvider that is to be used for processing the request. is configured using the dictionary property TargetDbProviders. The key of this dictionary contains the name of a dbProvider and its value is a dbProvider object. You can also provide this dictionary as a constructor argument. The property DefaultDbProvider can be set with the name of the DbProvider to use if no provider name is found in thread local storage
MultiDelegatingDbProvider
During request processing, once you have determined which target dbProvider should be use, in this example database1ProviderName, you should execute the following code is you are using Spring 1.2 M1 or later
// Spring 1.3.0 or later MultiDelegatingDbProvider.CurrentDbProviderName = "database1ProviderName" // Spring 1.2 M1 or later LogicalThreadContext.SetData(MultiDelegatingDbProvider.CURRENT_DBPROVIDER_SLOTNAME, "database1ProviderName")
Note
If you do not change the name of the IDbProvider stored in thread local storage during request processing, say in the web tier where a user is identified, then you will always refer to the default
207
DbProvider provider if the property DefaultDbProvider has been set. If the DefaultDbProvider property has not been set than an InvalidDataAccessApiUsageException will be thrown. Here is a sample configuration to build up an object definition for MultiDelegatingDbProvider.
<db:provider id="CreditAndDebitsDbProvider" 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=Credits;User ID=springqa; Password=springqa"/> <object id="dbProviderDictionary" type="Spring.Collections.SynchronizedHashtable, Spring.Core"> <property name="['DbProvider1']" ref="CreditAndDebitsDbProvider"/> <property name="['DbProvider2']" ref="CreditDbProvider"/> </object> <object id="DbProvider" type="Spring.Data.MultiDelegatingDbProvider, Spring.Data"> <property name="TargetDbProviders" ref="dbProviderDictionary"/> <property name="DefaultDbProvider" value="CreditDbProvider"/> </object>
As seen above, MultidelegatingDbProvider works via a thread local storage mechansims. If you prefer to place the logic to switch databases in a single location, within a single class, then create a subclass MultiDelegatingDbProvider and override the method GetTargetProvider. You can then select which provider to return based on your own implementation that does not involve thread local storage.
Note
This class is not recommended for usage with NHibernate. NHibernate usage typically involves caches that are scoped at the level of the SessionFactory. If you switch the database that hibernate is pointing to and do not also managed switching the cache, then the cache will end up with results from two different databases - which of course you don't want to have. The helper class contained in this post [http://forum.springframework.net/showthread.php?p=11234#post11234] may help you if you when using NHibernate with multiple databases.
208
209
20.2. Motivations
There are a variety of motivations to create a higher level ADO.NET persistence API. Encapsulation of common 'boiler plate' tasks when coding directly against the ADO.NET API. For example here is a list of the tasks typically required to be coded for processing a result set query. Note that the code needed when using Spring's ADO.NET framework is in italics. 1. Define connection parameters 2. Open the connection 3. Specify the command type and text 4. Prepare and execute the statement 5. Set up the loop to iterate through the results (if any) 6. Do the work for each iteration 7. Process any exception 8. Display or rollback on warnings 9. Handle transactions 10.Close the connection Spring takes care of the low-level tasks and lets you focus on specifying the SQL and doing the real work of extracting data. This standard boiler plate pattern is encapsulated in a class, AdoTemplate. The name 'Template' is used because if you look at the typical code workflow for the above listing, you would essentially like to 'template' it, that is stick in the code that is doing the real work in the midst of the resource, transaction, exception management. Another very important motivation is to provide an easy means to group multiple ADO.NET operations within a single transaction while at the same time adhering to a DAO style design in which transactions are initiated outside the DAOs, typically in a business service layer. Using the 'raw' ADO.NET API to implement this design often results in explicitly passing around of a Transaction/Connection pair to DAO objects. This infrastructure task distracts from the main database task at hand and is frequently done in an ad-hoc manner. Integrating with Spring's transaction management features provides an elegant means to achieve this common design goal. There are many other benefits to integration with Spring's transaction management features, see Chapter 17, Transaction management for more information. Provider Independent Code: In .NET 1.1 writing provider independent code was difficult for a variety of reasons. The most prominent was the lack of a lack of a central factory for creating interface based references to the core ADO.NET classes such as IDbConnection, IDbCommand, DbParameter etc. In addition, the APIs exposed by many of these interfaces were minimal or incomplete - making for tedious code that would otherwise be more easily developed with provider specific subclasses. Lastly, there was no common base class for data access exceptions across the providers. .NET 2.0 made many changes for the better in that regard across all these areas of concern - and Spring only plugs smaller holes in that regard to help in the portability of your data access code. Resource Management: The 'using' block is the heart of elegant resource management in .NET from the API perspective. However, despite its elegance, writing 2-3 nested using statements for each data access method also
210
Data access using ADO.NET starts to be tedious, which introduces the risk of forgetting to do the right thing all the time in terms of both direct coding and 'cut-n-paste' errors. Spring centralizes this resource management in one spot so you never forget or make a mistake and rely on it always being done correctly. Parameter management: Frequently much of data access code is related to creating appropriate parameters. To alleviate this boiler plate code Spring provides a parameter 'builder' class that allows for succinct creation of parameter collections. Also, for the case of stored procedures, parameters can be derived from the database itself which reduces parameter creation code to just one line. Frequently result set data is converted into objects. Spring provides a simple framework to organize that mapping task and allows you to reuse mapping artifacts across your application. Exceptions: The standard course of action when an exception is thrown from ADO.NET code is to look up the error code and then re-run the application to set a break point where the exception occurred so as to see what the command text and data values were that caused the exception. Spring provides exceptions translation from these error codes (across database vendors) to a Data Access Object exception hierarchy. This allows you to quickly understand the category of the error that occurred and also the 'bad' data which lead to the exception. Warnings: A common means to extract warning from the database, and to optionally treat those warnings as a reason to rollback is not directly supported with the new System.Data.Common API Portability: Where possible, increase the portability of code across database provider in the higher level API. The need adding of a parameter prefix, i.e. @ for SqlServer or ':' for oracle is one such example of an area where a higher level API can offer some help in making your code more portable. Note that Spring's ADO.NET framework is just 'slightly' above the raw API. It does not try to compete with other higher level persistence abstractions such as result set mappers (iBATIS.NET) or other ORM tools (NHibernate). (Apologies if your favorite is left out of that short list). As always, pick and choose the appropriate level of abstraction for the task at hand. As a side note, Spring does offer integration with higher level persistence abstractions (currently NHibernate) providing such features as integration with Spring's transaction management features as well as mixing orm/ado.net operations within the same transaction.
One of the classic creational patterns in the GoF Design Patterns book addresses this situation directly, the Abstract Factory pattern. This approach was applied in the .NET BCL with the introduction of the DbProviderFactory class which contains various factory methods that create the various objects used in ADO.NET programming. In addition, .NET 2.0 introduced new abstract base classes that all ADO.NET providers must inherit from. These base classes provide more core functionality and uniformity across the various providers as compared to the original ADO.NET interfaces. Spring's database provider abstraction has a similar API to that of .ADO.NET 2.0's DbProviderFactory. The central interface is IDbProvider and it has factory methods that are analogous to those in the DbProviderFactory
211
Data access using ADO.NET class except that they return references to the base ADO.NET interfaces. Note that in keeping with the Spring Framework's philosophy, IDbProvider is an interface, and can thus be easily mocked or stubbed as necessary. Another key element of this interface is the ConnectionString property that specifies the specific runtime information necessary to connect to the provider. The interface also has a IDbMetadata property that contains minimal database metadata information needed to support the functionality in rest of the Spring ADO.NET framework. It is unlikely you will need to use the DatabaseMetadata class directly in your application. For more information on configuring a Spring database provider refer to Chapter 19, DbProvider
Please refer to the Chapter 19, DbProvider for information on how to create a IDbProvider in Spring's XML configuration file.
20.4. Namespaces
The ADO.NET framework consists of a few namespaces, namely Spring.Data, Spring.Data.Generic, Spring.Data.Common, Spring.Data.Support, and Spring.Data.Object. The Spring.Data namespace contains the majority of the classes and interfaces you will deal with on a day to day basis. The Spring.Data.Generic namespaces add generic versions of some classes and interfaces and you will also likely deal with this on a day to day basis if you are using .NET 2.0 The Spring.Data.Common namespaces contains Spring's DbProvider abstraction in addition to utility classes for parameter creation. The Spring.Data.Object namespaces contains classes that represent RDBMS queries, updates, and stored procedures as thread safe, reusable objects. Finally the Spring.Data.Support namespace is where you find the IAdoExceptionTransactor translation functionality and some utility classes.
212
Data access using ADO.NET Generally speaking, experience has shown that the AdoTemplate approach reads very cleanly when looking at DAO method implementation as you can generally see all the details of what is going on as compared to the object based approach. The object based approach however, offers some advantages when calling stored procedures since it acts as a cache of derived stored procedure arguments and can be invoked passing a variable length argument list to the 'execute' method. As always, take a look at both approaches and use the approach that provides you with the most benefit for a particular situation.
is a thread-safe class and as such a single instance can be used for all data access operations in you applications DAOs. AdoTemplate implements an IAdoOperations interface. Although the IAdoOperations interface is more commonly used for testing scenarios you may prefer to code against it instead of the direct class instance.
AdoTemplate
If you are using the generic version of AdoTemplate you can access the non-generic version via the property ClassicAdoTemplate. The following two sections show basic usage of the AdoTemplate 'Execute' API for .NET 1.1 and 2.0.
213
The DbCommand that is passed into the anonymous delegate is already has it Connection property set to the corresponding value of the dbProvider instance used to create the template. Furthermore, the Transaction property of the DbCommand is set based on the transactional calling context of the code as based on the use of Spring's transaction management features. Also note the feature of anonymous delegates to access the variable 'postalCode' which is defined 'outside' the anonymous delegate implementation. The use of anonymous delegates is a powerful approach since it allows you to write compact data access code. If you find that your callback implementation is getting very long, it may improve code clarity to use an interface based version of the callback function, i.e. an ICommandCallback shown below. As you can see, only the most relevant portions of the data access task at hand need to be coded. (Note that in this simple example you would be better off using AdoTemplate's ExecuteScalar method directly. This method is described in the following sections). As mentioned before, the typical usage scenario for the Execute callback would involve downcasting the passed in DbCommand object to access specific provider API features. There is also an interface based version of the execute method. The signatures for the delegate and interface are shown below
public delegate T CommandDelegate<T>(DbCommand command);
While the delegate version offers the most compact syntax, the interface version allows for reuse. The corresponding method signatures on Spring.Data.Generic.AdoTemplate are shown below
public class AdoTemplate : AdoAccessor, IAdoOperations { ... T Execute<T>(ICommandCallback action); T Execute<T>(CommandDelegate<T> del); ... }
While it is common for .NET 2.0 ADO.NET provider implementations to inherit from the base class System.Data.Common.DbCommand, that is not a requirement. To accommodate the few that don't, which as of this writing are the latest Oracle (ODP) provider, Postgres, and DB2 for iSeries, two additional execute methods are provided. The only difference is the use of callback and delegate implementations that have IDbCommand and not DbCommand as callback arguments. The following listing shows these methods on AdoTemplate.
public class AdoTemplate : AdoAccessor, IAdoOperations { ... T Execute<T>(IDbCommandCallback action); T Execute<T>(IDbCommandDelegate<T> del);
214
... }
where the signatures for the delegate and interface are shown below
public delegate T IDbCommandDelegate<T>(IDbCommand command);
Internally the AdoTemplate implementation delegates to implementations of IDbCommandCallback so that the 'lowest common denominator' API is used to have maximum portability. If you accidentally call Execute<T>(ICommandCallback action)and the command does not inherit from DbCommand, an InvalidDataAccessApiUsageException will be thrown. Depending on how portable you would like your code to be, you can choose among the two callback styles. The one based on DbCommand has the advantage of access to the more user friendly DbParameter class as compared to IDbParameter obtained from IDbCommand.
215
Note that in this example, one could more easily use AdoTemplate's ExecuteScalar method. The Execute method has interface and delegate overloads. The signatures for the delegate and interface are shown below
public delegate object CommandDelegate(IDbCommand command); public interface ICommandCallback { object DoInCommand(IDbCommand command); }
Note that you have to cast to the appropriate object type returned from the execute method.
216
Data access using ADO.NET QueryWithRowCallback - Execute a query calling an implementation of IRowCallback for each row in the result set. QueryWithRowCallbackDelegate - Same as QueryWithRowCallback but calling a RowCallbackDelegate for each row. QueryWithRowMapper - Execute a query mapping a result set on a row by row basis with an implementation of the IRowMapper interface. QueryWithRowMapperDelegate - Same as QueryWithRowMapper but using a RowMapperDelegate to perform result set row to object mapping. Mapping result set to a single object QueryForObject - Execute a query mapping the result set to an object using a IRowMapper. Exception is thrown if the query does not return exactly one object. Query with a callback to create the DbCommand object. These are generally used by the framework itself to support other functionality, such as in the Spring.Data.Objects namespace. QueryWithCommandCreator - Execute a query with a callback to IDbCommandCreator to create a IDbCommand object and using either a IRowMapper or IResultSetExtractor to map the result set to an object. One variation lets multiple result set 'processors' be specified to act on multiple result sets and return output parameters. DataTable and DataSet operations DataTableCreate - Create and Fill DataTables DataTableCreateWithParameters - Create and Fill DataTables using a parameter collection. DataTableFill - Fill a pre-existing DataTable. DataTableFillWithParameters - Fill a pre-existing DataTable using parameter collection. DataTableUpdate - Update the database using the provided DataTable, insert, update, delete SQL. DataTableUpdateWithCommandBuilder - Update the database using the provided DataTable, select SQL, and parameters. DataSetCreate - Create and Fill DataSets DataSetCreateWithParameters - Create and Fill DataTables using a parameter collection. DataSetFill - Fill a pre-existing DataSet DataSetFillWithParameters - Fill a pre-existing DataTable using parameter collection. DataSetUpdate - Update the database using the provided DataSet, insert, update, delete SQL. DataSetUpdateWithCommandBuilder - Update the database using the provided DataSet, select SQL, and parameters..
Note
These methods are not currently in the generic version of AdoTemplate but accessible through the property ClassicAdoTemplate.
217
Data access using ADO.NET Parameter Creation utility methods DeriveParameters - Derive the parameter collection for stored procedures. In turn each method typically has four overloads, one with no parameters and three for providing parameters. Aside from the DataTable/DataSet operations, the three parameter overloads are of the form shown below MethodName(CommandType cmdType, string cmdText, CallbackInterfaceOrDelegate, parameter setting arguments) The CallbackInterfaceOrDelegate is one of the three types listed previously. The parameters setting arguments are of the form MethodName( ... string parameterName, Enum dbType, int size, object parameterValue) MethodName( ... IDbParameters parameters) MethodName( ... ICommandSetter commandSetter) The first overload is a convenience method when you only have one parameter to set. The database enumeration is the base class 'Enum' allowing you to pass in any of the provider specific enumerations as well as the common DbType enumeration. This is a trade off of type-safety with provider portability. (Note generic version could be improved to provide type safety...). The second overload contains a collection of parameters. The data type is Spring's IDbParameters collection class discussed in the following section. The third overload is a callback interface allowing you to set the parameters (or other properties) of the IDbCommand passed to you by the framework directly. If you are using .NET 2.0 the delegate versions of the methods are very useful since very compact definitions of database operations can be created that reference variables local to the DAO method. This removes some of the tedium in passing parameters around with interface based versions of the callback functions since they need to be passed into the constructor of the implementing class. The general guideline is to use the delegate when available for functionality that does not need to be shared across multiple DAO classes or methods and use interface based version to reuse the implementation in multiple places. The .NET 2.0 versions make use of generics where appropriate and therefore enhance type-safety.
218
Data access using ADO.NET basis for a mapping strategy that will map DBNull values to default values based on the standard IDataReader interface. See the section custom IDataReader implementations for more information. CommandTimeout - Gets or sets the command timeout for IDbCommands that this AdoTemplate executes. Default is 0, indicating to use the database provider's default.
It is possible to provide a wrapper around the standard .NET provider interfaces such that you can use the plain ADO.NET API in conjunction with Spring's transaction management features. If you are using ServiceDomainPlatformTransactionManager or TxScopePlatformTransactionManager then you can retrieve the currently executing transaction object via the standard .NET APIs.
219
20.9.1. IDbParametersBuilder
Instead of creating a parameter on one line of code, then setting its type on another and size on another, a builder and parameter interface, IDbParametersBuilder and IDbParameter respectfully, are provided so that this declaration process can be condensed. The IDbParameter support chaining calls to its methods, in effect a simple language-constrained domain specific language, to be fancy about it. Here is an example of it in use.
IDbParametersBuilder builder = CreateDbParametersBuilder(); builder.Create().Name("Country").Type(DbType.String).Size(15).Value(country); builder.Create().Name("City").Type(DbType.String).Size(15).Value(city);
// now get the IDbParameters collection for use in passing to AdoTemplate methods.
Please note that IDbParameters and IDbParameter are not part of the BCL, but part of the Spring.Data.Common namespace. The IDbParameters collection is a frequent argument to the overloaded methods of AdoTemplate. The parameter prefix, i.e. '@' in Sql Server, is not required to be added to the parameter name. The DbProvider is aware of this metadata and AdoTemplate will add it automatically if required before execution. An additional feature of the IDbParametersBuilder is to create a Spring FactoryObject that creates IDbParameters for use in the XML configuration file of the IoC container. By leveraging Spring's expression evaluation language, the above lines of code can be taken as text from the XML configuration file and executed. As a result you can externalize your parameter definitions from your code. In combination with abstract object definitions and importing of configuration files your increase the chances of having one code base support multiple database providers just by a change in configuration files.
20.9.2. IDbParameters
This class is similar to the parameter collection class you find in provider specific implementations of IDataParameterCollection. It contains a variety of convenience methods to build up a collection of parameters. Here is an abbreviated listing of the common convenience methods. int Add(object parameterValue) void AddRange(Array values) IDbDataParameter AddWithValue(string name, object parameterValue) IDbDataParameter Add(string name, Enum parameterType) IDbDataParameter AddOut(string name, Enum parameterType) IDbDataParameter AddReturn(string name, Enum parameterType) void DeriveParameters(string storedProcedureName) Here a simple usage example
// inside method has has local variable country and city... IDbParameters parameters = CreateDbParameters();
220
The parameter prefix, i.e. '@' in Sql Server, is not required to be added to the parameter name. The DbProvider is aware of this metadata and AdoTemplate will add it automatically if required before execution.
All of AdoTemplates callback interfaces/delegates that have an IDataReader as an argument are wrapped with a IDataReaderWrapper if the AdoTemplate has been configured with one via its DataReaderWrapperType property. Your implementation should support a zero-arg constructor. Frequently you will use a common mapper for DBNull across your application so only one instance of AdoTemplate and IDataReaderWrapper in required. If you need to use multiple null mapping strategies you will need to create multiple instances of AdoTemplate and configure them appropriately in the DAO objects.
221
20.11.1. ExecuteNonQuery
ExecuteNonQuery is used to perform create, update, and delete operations. It has four overloads listed below reflecting different ways to set the parameters. An example of using this method is shown below
public void CreateCredit(float creditAmount) { AdoTemplate.ExecuteNonQuery(CommandType.Text, String.Format("insert into Credits(creditAmount) VALUES ({0})", creditAmount)); }
20.11.2. ExecuteScalar
An example of using this method is shown below
int iCount = (int)adoTemplate.ExecuteScalar(CommandType.Text, "SELECT COUNT(*) FROM TestObjects");
20.12.1. ResultSetExtractor
The ResultSetExtractor gives you control to iterate over the IDataReader returned from the query. You are responsible for iterating through all the result sets and returning a corresponding result object. Implementations of IResultSetExtractor are typically stateless and therefore reusable as long as the implementation doesn't access stateful resources. The framework will close the IDataReader for you.
222
Data access using ADO.NET The interface and delegate signature for ResutSetExtractors is shown below for the generic version in the Spring.Data.Generic namespace
public interface IResultSetExtractor<T> { T ExtractData(IDataReader reader); } public delegate T ResultSetExtractorDelegate<T>(IDataReader reader);
Here is an example taken from the Spring.DataQuickStart. It is a method in a DAO class that inherits from AdoDaoSupport, which has a convenience method 'CreateDbParametersBuilder()'.
public virtual IList<string> GetCustomerNameByCountryAndCityWithParamsBuilder(string country, string city) { IDbParametersBuilder builder = CreateDbParametersBuilder(); builder.Create().Name("Country").Type(DbType.String).Size(15).Value(country); builder.Create().Name("City").Type(DbType.String).Size(15).Value(city); return AdoTemplate.QueryWithResultSetExtractor(CommandType.Text, customerByCountryAndCityCommandText, new CustomerNameResultSetExtractor<List<string>>(), builder.GetParameters()); }
Internally the implementation of the QueryWithRowCallback and QueryWithRowMapper methods are specializations of the general ResultSetExtractor. For example, the QueryWithRowMapper implementation iterates through the result set, calling the callback method 'MapRow' for each row and collecting the results in an IList. If you have a specific case that is not covered by the QueryWithXXX methods you can subclass AdoTemplate and follow the same implementation pattern to create a new QueryWithXXX method to suit your needs.
20.12.2. RowCallback
The RowCallback is usually a stateful object itself or populates another stateful object that is accessible to the calling code. Here is a sample take from the Data QuickStart
223
The PostalCodeRowCallback builds up state which is then retrieved via the property PostalCodeMultimap. The Callback implementation is shown below
internal class PostalCodeRowCallback : IRowCallback { private IDictionary<string, IList<string>> postalCodeMultimap = new Dictionary<string, IList<string>>(); public IDictionary<string, IList<string>> PostalCodeMultimap { get { return postalCodeMultimap; } } public void ProcessRow(IDataReader reader) { string contactName = reader.GetString(0); string postalCode = reader.GetString(1); IList<string> contactNameList; if (postalCodeMultimap.ContainsKey(postalCode)) { contactNameList = postalCodeMultimap[postalCode]; } else { postalCodeMultimap.Add(postalCode, contactNameList = new List<string>()); } contactNameList.Add(contactName); } }
20.12.3. RowMapper
The RowMapper lets you focus on just the logic to map a row of your result set to an object. The creation of a IList to store the results and iterating through the IDataReader is handled by the framework. Here is a simple example taken from the Data QuickStart application
public class RowMapperDao : AdoDaoSupport { private string cmdText = "select Address, City, CompanyName, ContactName, " + "ContactTitle, Country, Fax, CustomerID, Phone, PostalCode, " + "Region from Customers";
224
You may also pass in a delegate, which is particularly convenient if the mapping logic is short and you need to access local variables within the mapping logic.
public virtual IList<Customer> GetCustomersWithDelegate() { return AdoTemplate.QueryWithRowMapperDelegate<Customer>(CommandType.Text, cmdText, delegate(IDataReader dataReader, int rowNum) { Customer customer = new Customer(); customer.Address = dataReader.GetString(0); customer.City = dataReader.GetString(1); customer.CompanyName = dataReader.GetString(2); customer.ContactName = dataReader.GetString(3); customer.ContactTitle = dataReader.GetString(4); customer.Country = dataReader.GetString(5); customer.Fax = dataReader.GetString(6); customer.Id = dataReader.GetString(7); customer.Phone = dataReader.GetString(8); customer.PostalCode = dataReader.GetString(9); customer.Region = dataReader.GetString(10); return customer; }); }
225
family of methods is listed below. object QueryWithCommandCreator(IDbCommandCreator cc, IResultSetExtractor rse) void QueryWithCommandCreator(IDbCommandCreator cc, IRowCallback rowCallback) IList QueryWithCommandCreator(IDbCommandCreator cc, IRowMapper rowMapper) There is also the same methods with an additional collecting parameter to obtain any output parameters. These are object QueryWithCommandCreator(IDbCommandCreator cc, IResultSetExtractor rse, IDictionary
returnedParameters)
IList
QueryWithCommandCreator(IDbCommandCreator
cc,
IRowMapper
rowMapper,
IDictionary
returnedParameters)
The created IDbCommand object is used when performing the QueryWithCommandCreator method. To process multiple result sets specify a list of named result set processors,( i.e. IResultSetExtractor, IRowCallback, or IRowMapper). This method is shown below IDictionary QueryWithCommandCreator(IDbCommandCreator cc, IList namedResultSetProcessors) The list must contain objects of the type Spring.Data.Support.NamedResultSetProcessor. This is the class responsible for associating a name with a result set processor. The constructors are listed below.
public class NamedResultSetProcessor {
public NamedResultSetProcessor(string name, IRowMapper rowMapper) { ... } public NamedResultSetProcessor(string name, IRowCallback rowcallback) { ... }
The results of the RowMapper or ResultSetExtractor are retrieved by name from the dictionary that is returned. RowCallbacks, being stateless, only have the placeholder text, "ResultSet returned was processed by an IRowCallback" as a value for the name of the RowCallback used as a key. Output and InputOutput parameters can be retrieved by name. If this parameter name is null, then the index of the parameter prefixed with the letter 'P' is a key name, i.e P2, P3, etc. The namespace Spring.Data.Objects.Generic contains generic versions of these methods. These are listed below T QueryWithCommandCreator<T>(IDbCommandCreator cc, IResultSetExtractor<T> rse)
226
Data access using ADO.NET IList<T> QueryWithCommandCreator<T>(IDbCommandCreator cc, IRowMapper<T> rowMapper) and overloads that have an additional collecting parameter to obtain any output parameters. T QueryWithCommandCreator<T>(IDbCommandCreator cc, IResultSetExtractor<T> rse, IDictionary
returnedParameters)
IList<T>
QueryWithCommandCreator<T>(IDbCommandCreator
cc,
IRowMapper<T>
rowMapper,
IDictionary returnedParameters)
When processing multiple result sets you can specify up to two type safe result set processors. IDictionary QueryWithCommandCreator<T>(IDbCommandCreator cc, IList namedResultSetProcessors) IDictionary
QueryWithCommandCreator<T,U>(IDbCommandCreator cc, IList
namedResultSetProcessors)
The list of result set processors contains either objects of the type Spring.Data.Generic.NamedResultSetProcessor<T> or Spring.Data.NamedResultSetProcessor. The generic result set processors, NamedResultSetProcessor<T>, is used to process the first result set in the case of using QueryWithCommandCreator<T> and to process the first and second result set in the case of using QueryWithCommandCreator<T,U>. Additional Spring.Data.NamedResultSetProcessors that are listed can be used to process additional result sets. If you specify a RowCallback with NamedResultSetProcessor<T>, you still need to specify a type parameter (say string) because the RowCallback processor does not return any object. It is up to subclasses of RowCallback to collect state due to processing the result set which is later queried.
The passed in IDbDataAdapter will have its SelectCommand property created and set with its Connection and Transaction values based on the calling transaction context. The return value is the result of processing or null. There are type-safe versions of this method in Spring.Data.Generic.AdoTemplate
227
Data access using ADO.NET T Execute<T>(IDataAdapterCallback<T> dataAdapterCallback) - Execute ADO.NET operations on a IDbDataAdapter object using an interface based callback. T Execute<T>(DataAdapterDelegate<T> del) - Execute ADO.NET operations on a IDbDataAdapter object using an delegate based callback. Where IDataAdapterCallback<T> and DataAdapterDelegate<T> are defined as
public interface IDataAdapterCallback<T> { T DoInDataAdapter(IDbDataAdapter dataAdapter); } public delegate T DataAdapterDelegate<T>(IDbDataAdapter dataAdapter);
20.13.1. DataTables
DataTable operations are available on the class Spring.Data.Core.AdoTemplate. If you are using the generic version, Spring.Data.Generic.AdoTemplate, you can access these methods through the property ClassicAdoTemplate, which returns the non-generic version of AdoTemplate. DataTable operations available fall into the general family of methods with 3-5 overloads per method. DataTableCreate - Create and Fill DataTables DataTableCreateWithParameters - Create and Fill DataTables using a parameter collection. DataTableFill - Fill a pre-existing DataTable. DataTableFillWithParameters - Fill a pre-existing DataTable using a parameter collection. DataTableUpdate - Update the database using the provided DataTable, insert, update, delete SQL. DataTableUpdateWithCommandBuilder - Update the database using the provided DataTable, select SQL, and parameters.
20.13.2. DataSets
DataSet operations are available on the class Spring.Data.Core.AdoTemplate. If you are using the generic version, Spring.Data.Generic.AdoTemplate, you can access these methods through the property ClassicAdoTemplate, which returns the non-generic version of AdoTemplate. DataSet operations available fall into the following family of methods with 3-5 overloads per method. DataSetCreate - Create and Fill DataSets DataSetCreateWithParameters - Create and Fill DataTables using a parameter collection. DataSetFill - Fill a pre-existing DataSet DataSetFillWithParameters - Fill a pre-existing DataTable using parameter collection. DataSetUpdate - Update the database using the provided DataSet, insert, update, delete SQL. DataSetUpdateWithCommandBuilder - Update the database using the provided DataSet, select SQL, and parameters. The following code snippets demonstrate the basic functionality of these methods using the Northwind database. See the SDK documentation for more details on other overloaded methods.
228
public void DemoDataSetCreate() { DataSet customerDataSet = AdoTemplate.DataSetCreate(CommandType.Text, selectAll); // customerDataSet has a table named 'Table' with 91 rows customerDataSet = AdoTemplate.DataSetCreate(CommandType.Text, selectAll, new string[] { "Customers" }); // customerDataSet has a table named 'Customers' with 91 rows }
public void DemoDataSetCreateWithParameters() { string selectLike = @"select Address, City, CompanyName, ContactName, " + "ContactTitle, Country, Fax, CustomerID, Phone, PostalCode, " + "Region from Customers where ContactName like @ContactName"; DbParameters dbParameters = CreateDbParameters(); dbParameters.Add("ContactName", DbType.String).Value = "M%'; DataSet customerLikeMDataSet = AdoTemplate.DataSetCreateWithParams(CommandType.Text, selectLike, dbParameters); // customerLikeMDataSet has a table named 'Table' with 12 rows } public void DemoDataSetFill() { DataSet dataSet = new DataSet(); dataSet.Locale = CultureInfo.InvariantCulture; AdoTemplate.DataSetFill(dataSet, CommandType.Text, selectAll); }
Updating a DataSet can be done using a CommandBuilder, automatically created from the specified select command and select parameters, or by explicitly specifying the insert, update, delete commands and parameters. Below is an example, refer to the SDK documentation for additional overloads
public class DataSetDemo : AdoDaoSupport { private string selectAll = @"select Address, City, CompanyName, ContactName, " + "ContactTitle, Country, Fax, CustomerID, Phone, PostalCode, " + "Region from Customers"; public void DemoDataSetUpdateWithCommandBuilder() { DataSet dataSet = new DataSet(); dataSet.Locale = CultureInfo.InvariantCulture; AdoTemplate.DataSetFill(dataSet, CommandType.Text, selectAll, new string[]{ "Customers" } ); AddAndEditRow(dataSet);. AdoTemplate.DataSetUpdateWithCommandBuilder(dataSet, CommandType.Text, selectAll, null, "Customers"); } public void DemoDataSetUpdateWithoutCommandBuilder() { DataSet dataSet = new DataSet(); dataSet.Locale = CultureInfo.InvariantCulture; AdoTemplate.DataSetFill(dataSet, CommandType.Text, selectAll, new string[]{ "Customers" } ); AddAndEditRow(dataSet);. string insertSql = @"INSERT Customers (CustomerID, CompanyName) VALUES (@CustomerId, @CompanyName)"; IDbParameters insertParams = CreateDbParameters(); insertParams.Add("CustomerId", DbType.String, 0, "CustomerId"); //.Value = "NewID";
229
In the case of needing to set parameter SourceColumn or SourceVersion properties it may be more convenient to use IDbParameterBuilder.
printGroupMappingDataSet = AdoTemplate.Execute(delegate(IDbCommand command) { TypedDataSetUtils.ApplyConnectionAndTx(adapter, command); adapter.Fill(printGroupMappingDataSet.PrintGroupMapping); return printGroupMappingDataSet; }) as PrintGroupMappingDataSet; return printGroupMappingDataSet; }
230
Data access using ADO.NET This DAO method may be combined with other DAO operations inside a transactional context and they will all share the same connection/transaction objects. There are two overloads of the method ApplyConnectionAndTx which differ in the second method argument, one takes an IDbCommand and the other IDbProvider. These are listed below
public static void ApplyConnectionAndTx(object typedDataSetAdapter, IDbCommand sourceCommand) public static void ApplyConnectionAndTx(object typedDataSetAdapter, IDbProvider dbProvider)
The method that takes IDbCommand is a convenience if you will be using AdoTemplate callback's as the passed in command object will already have its connection and transaction properties set based on the current transactional context. The method that takes an IDbProvider is convenient to use when you have data access logic that is not contained within a single callback method but is instead spead among multiple classes. In this case passing the transactionally aware IDbCommand object can be intrusive on the method signatures. Instead you can pass in an instance of IDbProvider that can be obtained via standard dependency injection techniques or via a service locator style lookup.
Note
There is a view borne from experience acquired in the field amongst some of the Spring developers that the various RDBMS operation classes described below (with the exception of the StoredProcedure class) can often be replaced with straight AdoTemplate calls... often it is simpler to use and plain easier to read a DAO method that simply calls a method on a AdoTemplate direct (as opposed to encapsulating a query as a full-blown class). It must be stressed however that this is just a view... if you feel that you are getting measurable value from using the RDBMS operation classes, feel free to continue using these classes.
20.15.1. AdoQuery
is a reusable, threadsafe class that encapsulates an SQL query. Subclasses must implement the NewRowMapper(..) method to provide a IRowMapper instance that can create one object per row obtained from iterating over the IDataReader that is created during the execution of the query. The AdoQuery class is rarely used directly since the MappingAdoQuery subclass provides a much more convenient implementation for mapping rows to .NET classes. Another implementations that extends AdoQuery is MappingadoQueryWithParameters (See SDK docs for details).
AdoQuery
The AdoNonQuery class encapsulates an IDbCommand 's ExecuteNonQuery method functionality. Like the AdoQuery object, an AdoNonQuery object is reusable, and like all AdoOperation classes, an AdoNonQuery can have parameters and is defined in SQL. This class provides two execute methods IDictionary ExecuteNonQuery(params object[] inParameterValues) IDictionary ExecuteNonQueryByNamedParam(IDictionary inParams)
231
Data access using ADO.NET This class is concrete. Although it can be subclassed (for example to add a custom update method) it can easily be parameterized by setting SQL and declaring parameters. An example of an AdoQuery subclass to encapsulate an insert statement for a 'TestObject' (consisting only name and age columns) is shown below
public class CreateTestObjectNonQuery : AdoNonQuery { private static string sql = "insert into TestObjects(Age,Name) values (@Age,@Name)"; public CreateTestObjectNonQuery(IDbProvider dbProvider) : base(dbProvider, sql) { DeclaredParameters.Add("Age", DbType.Int32); DeclaredParameters.Add("Name", SqlDbType.NVarChar, 16); Compile(); } public void Create(string name, int age) { ExecuteNonQuery(name, age); } }
20.15.2. MappingAdoQuery
is a reusable query in which concrete subclasses must implement the abstract MapRow(..) method to convert each row of the supplied IDataReader into an object. Find below a brief example of a custom query that maps the data from a relation to an instance of the Customer class.
MappingAdoQuery
public class TestObjectQuery : MappingAdoQuery { private static string sql = "select TestObjectNo, Age, Name from TestObjects"; public TestObjectQuery(IDbProvider dbProvider) : base(dbProvider, sql) { CommandType = CommandType.Text; } protected override object MapRow(IDataReader reader, int num) { TestObject to = new TestObject(); to.ObjectNumber = reader.GetInt32(0); to.Age = reader.GetInt32(1); to.Name = reader.GetString(2); return to; } }
20.15.3. AdoNonQuery
The AdoNonQuery class encapsulates an IDbCommand 's ExecuteNonQuery method functionality. Like the AdoQuery object, an AdoNonQuery object is reusable, and like all AdoOperation classes, an AdoNonQuery can have parameters and is defined in SQL. This class provides two execute methods IDictionary ExecuteNonQuery(params object[] inParameterValues) IDictionary ExecuteNonQueryByNamedParam(IDictionary inParams) This class is concrete. Although it can be subclassed (for example to add a custom update method) it can easily be parameterized by setting SQL and declaring parameters.
public class CreateTestObjectNonQuery : AdoNonQuery
232
233
The 'DeriveParameters' method saves you the trouble of having to declare each parameter explicitly. When using DeriveParameters is it often common to use the Query method that takes a variable length list of arguments. This assumes additional knowledge on the order of the stored procedure arguments. If you do not want to follow this loose shorthand convention, you can call the method QueryByNamesParameters instead passing in a IDictionary of parameter key/value pairs.
Note
If you would like to have the return value of the stored procedure included in the returned dictionary, pass in true as a method parameter to DeriveParameters(). The StoredProcedure class is threadsafe once 'compiled', an act which is usually done in the constructor. This sets up the cache of database parameters that can be used on each call to Query or QueryByNamedParam. The implementation of IRowMapper that is used to extract the business objects is 'registered' with the class and then later retrieved by name as a fictional output parameter. You may also register IRowCallback and IResultSetExtractor callback interfaces via the AddRowCallback and AddResultSetExtractor methods. The generic version of StoredProcedure is in the namespace Spring.Data.Objects.Generic. It allows you to define up to two generic type parameters that will be used to process result sets returned from the stored procedure. An example is shown below
public class CustOrdersDetailStoredProc : StoredProcedure { private static string procedureName = "CustOrdersDetail"; public CustOrdersDetailStoredProc(IDbProvider dbProvider) : base(dbProvider, procedureName) { DeriveParameters(); AddRowMapper("orderDetailRowMapper", new OrderDetailRowMapper<OrderDetails>() ); Compile(); } public virtual List<OrderDetails> GetOrderDetails(int orderid) { IDictionary outParams = Query<OrderDetails>(orderid); return outParams["orderDetailRowMapper"] as List<OrderDetails>; } }
You can find ready to run code demonstrating the StoredProcedure class in the example 'Data Access' that is part of the Spring.NET distribution.
234
235
Object Relational Mapping (ORM) data access code you use to do O/R mapping. This is useful for data access that's not suitable for O/R mapping which still needs to share common transactions with ORM operations. The NHibernate Northwind example in the Spring distribution shows a NHibernate implementation of a persistence-technology agnostic DAO interfaces. (In the upcoming RC1 release the SpringAir example will demonstrate an ADO.NET and NHibernate based implementation of technology agnostic DAO interfaces.) The NHibernate Northwind example serves as a working sample application that illustrates the use of NHibernate in a Spring web application. It also leverages declarative transaction demarcation with different transaction strategies. Both NHibernate 1.0 and NHibernate 1.2 are supported. Differences relate to the use of generics and new features such as contextual sessions. For information on the latter, refer to the section Implementing DAOs based on the plain NHibernate API. The NHibernate 1.0 support is in the assembly Spring.Data.NHibernate and the 1.2 support is in the assembly Spring.Data.NHibernate12 At the moment the only ORM supported in NHibernate, but others can be integrated with Spring (in as much as makes sense) to offer the same value proposition.
21.2. NHibernate
We will start with a coverage of NHibernate in a Spring environment, using it to demonstrate the approach that Spring takes towards integrating O/R mappers. This section will cover many issues in detail and show different variations of DAO implementations and transaction demarcations. Most of these patterns can be directly translated to all other supported O/R mapping tools. The following discussion focuses on Hibernate 1.0.4, the major differences with NHibernate 1.2 being the ability to participate in Spring transaction/session management via the normal NHibernate API instead of the 'template' approach. Spring supports both NHibernate 1.0 and NHibernate 1.2 via separate .dlls with the same internal namespace.
236
Object Relational Mapping (ORM) data access many important objects are plain CLR objects: data access templates, data access objects (that use the templates), transaction managers, business services (that use the data access objects and transaction managers), ASP.NET web pages (that use the business services),and so on.
The important property of HibernateTransactionManager are the references to the DbProvider and the Hibernate ISessionFactory. For more information on the DbProvider, refer to the chapter DbProvider and the following section on SessionFactory set up. The second strategy is to use the class Spring.Data.TxScopeTransactionManager that uses .NET 2.0 System.Transaction namespace and its corresponding TransactionScope API. This is preferred when you are using multiple transactional resources, such as multiple databases. Note that due to changes in the manner in which NHibernate manages its own transactions introduced in NHibernate 2.1.2 (and later), when using NHibernate 2.1.2 (and later) in the context of the .NET System.Transaction class and its TransactionScope API Spring.NET users are advised to use the Spring.Data.HibernateTxScopeTransactionManager class which is specifically designed to manage the new NHibernate transcational model when used in the context of the System.Transaction API. The API of the Spring.Data.HibernateTxScopeTransactionManager class is functionally equivalent to that of the Spring.Data.HibernateTransactionManager class but it is designed to cooperate with the .NET System.Transaction TransactionScope API for NHibernate 2.1.2 (and later). All of these strategies associate one Hibernate Session for the scope of the transaction (scope in the general demarcation sense, not System.Transaction sense). If there is no transaction then a new Session will be opened for each operation. The exception to this rule is when using the OpenSessionInViewModule in a web application in single session mode (see Section 21.2.8, Web Session Management). In this case the session will be created on the start of the web request and closed on the end of the request. Note that the session's flush mode will be set to FlushMode.NEVER at the start of the request. If a non-readonly transaction is performed, then during the scope
237
Object Relational Mapping (ORM) data access of that transaction processing the flush mode will be changed to AUTO, and then set back to NEVER at the end of the transaction scope so that any changes to objects associated with the session during rendering will not be persisted back to the database when the session is closed at the end of the web request.
<!-- Property placeholder configurer for database settings --> <object type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core"> <property name="ConfigSections" value="databaseSettings"/> </object> <!-- Database and NHibernate Configuration --> <db:provider id="DbProvider" provider="SqlServer-1.1" connectionString="Integrated Security=false; Data Source=(local);Integrated Security=true;Database=Northwin;User ID=springqa;Password=springqa;"/>
<object id="MySessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate"> <property name="DbProvider" ref="DbProvider"/> <property name="MappingAssemblies"> <list> <value>Spring.Northwind.Dao.NHibernate</value> </list> </property> <property name="HibernateProperties"> <dictionary> <entry key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/> <entry key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect"/> <entry key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/> </dictionary> </property> </object> </objects>
Many of the properties on LocalSessionFactoryObject are those you will commonly configure, for example the property MappingAssemblies specifies a list of assemblies to seach for hibernate mapping files. The property HibernateProperies are the familiar NHibernate properties used to set typical options such as dialect and driver class. The location of NHibernate mapping information can also be specified using Spring's IResource abstraction via the property MappingResources. The IResource abstraction supports opening an input stream from assemblies, file system, and http(s) based on a Uri syntax. You can also leverage the extensibility of IResource and thereby allow NHibernate to obtain its configuration information from locations such as a database or LDAP.For other
238
Object Relational Mapping (ORM) data access properties you can configure them as you normal using the file hibernate.cfg.xml and refer to it via the property ConfigFileNames. This property is a string array so multiple configuration files are supported. There are other properties in LocalSessionFactoryObject that relate to the integration of Spring with NHibernate. The property ExposeTransactionAwareSessionFactory is discussed below and allows you to use Spring's declarative transaction demarcation functionality with the standard NHibernate API (as compared to using HibernateTemplate). The property DbProvider is used to infer two NHibernate configurations options. Infer the connection string, typically done via the hibernate property "hibernate.connection.connection_string". Delegate to the DbProvider itself as the NHibernate connection provider instead of listing it via property hibernate.connection.provider via HibernateProperties. If you specify both the property hibernate.connection.provider and DbProvider (as shown above) the configuration of the property hibernate.connection.provider is used and a warning level message is logged. If you use Spring's DbProvider as the NHibernate connection provider then you can take advantage of IDbProvider implementations that will let you change the connection string at runtime such as UserCredentialsDbProvider and MultiDelegatingDbProvider.
Note
UserCredentialsDbProvider and MultiDelegatingDbProvider only change the connection string at runtime based on values in thread local storage and do not clear out the Hibernate cache that is unique to each ISessionFactory instance. As such, they are only useful for selecting at runtime a single database instance. Cleaning up an existing session factory when switching to a new database is left to user code. 21.2.3.1. Creating a new SessionFactory per Connection String with DelegatingLocalSessionFactory Object Beginning with Spring.NET 1.3.1 there is direct support for creating a new session factory per connection string (assuming the same mapping files can be used across all databases connections). To support this functionality, DelegatingLocalSessionFactoryObject subclasses LocalSessionFactoryObject and overrides the method ISessionFactory NewSessionFactory(Configuration config) so that it returns an implementation of ISessionFactory that selects among multiple instances based on values in thread local storage, much like the implementation of MultiDelegatingDbProvider. Note that due to variations in the NHibernate project's ISessionFactory API, this approach is only supported under NHibernate 2.1.2 and NHibernate 3.0 21.2.3.2. Using FluentNHibernate to configure mappings with LocalSessionFactoryObject Direct support for configuration of NHibernate mapping files using FluentNHibernate will be included in a future release. Until then, to see how you can extend LocalSessionFactoryObject to suppport using FluentNHibernate follow the instructions on Benny Michielson's blog post here [http://www.bennymichielsen.be/post/2009/01/04/ Using-Fluent-NHibernate-in-SpringNet.aspx]. 21.2.3.3. Spring's IByteCodeProvider implementation Introduced in Hibernate 2.1 is support for dependency injection of hibernate managed objects [http:// fabiomaulo.blogspot.com/2009/05/nhibernate-ioc-integration.html] via the IBytecodeProvider extension point. As of Spring 1.3 provides Spring.Data.NHibernate.Bytecode.BytecodeProvider as the default IBytecodeProvider implementation when using LocalSessionFactory object to configure an ISessionFactory. To use a different
239
Object Relational Mapping (ORM) data access IBytecodeProvider configure it via the standard the Hibernate means, using App.confg or Web.config via the element <bytecode-provider type="..."/> inside the <hibernate-configuration> section or progammatically by setting Environment.BytecodeProvider.
public class HibernateCustomerDao : ICustomerDao { private ISessionFactory sessionFactory; public ISessionFactory SessionFactory { set { sessionFactory = value; } } public Customer SaveOrUpdate(Customer customer) { sessionFactory.GetCurrentSession().SaveOrUpdate(customer); return customer; } }
The above DAO follows the Dependency Injection pattern: it fits nicely into a Spring IoC container, just like it would if coded against Spring's HibernateTemplate. Of course, such a DAO can also be set up in plain C# (for example, in unit tests): simply instantiate it and call SessionFactory property with the desired factory reference. As a Spring object definition, it would look as follows:
<objects> <object id="CustomerDao" type="Spring.Northwind.Dao.NHibernate.HibernateCustomerDao, Spring.Northwind.Dao.NHibernate"> <property name="sessionFactory" ref="MySessionFactory"/> </object> </objects>
The SessionFactory configuration to support this programming model can be done two ways, both via configuration of Spring's LocalSessionFactoryObject. You can enable the use of Spring's implementation of the NHibernate extension interface, ICurrentSessionContext, by setting the property 'ExposeTransactionAwareSessionFactory' to true on LocalSessionFactoryObject. This is just a short-cut for setting the NHibernate property current_session_context_class with the name of the implementation class to use.
240
Object Relational Mapping (ORM) data access The first way is shown below
<object id="sessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate12"> <property name="ExposeTransactionAwareSessionFactory" value="true" /> <!-- other configuration settings omitted --> </object>
The main advantage of this DAO style is that it depends on the Hibernate API only; no import of any Spring class is required. This is of course appealing from a non-invasiveness perspective, and will no doubt feel more natural to Hibernate developers. 21.2.4.1. Exception Translation However, the DAO implemenation as shown throws plain HibernateException which means that callers can only treat exceptions as generally fatal - unless they want to depend on Hibernate's own exception hierarchy. Catching specific causes such as an optimistic locking failure is not possible without tying the caller to the implementation strategy. This trade off might be acceptable to applications that are strongly Hibernate-based and/or do not need any special exception treatment. As an alternative you can use Spring's exception translation advice to convert the NHibernate exception to Spring's DataAccessException hierarchy. Spring offers a solution allowing exception translation to be applied transparently through the [Repository] attribute:
[Repository] public class HibernateCustomerDao : ICustomerDao { // class body here }
241
The postprocessor will automatically look for all exception translators (implementations of the IPersistenceExceptionTranslator interface) and advise all object marked with the [Repository] attribute so that the discovered translators can intercept and apply the appropriate translation on the thrown exceptions. Spring's LocalSessionFactory object implements the IPersistenceExceptionTranslator interface and performs the same exception translation as was done when using HibernateTemplate. The [Repository] attribute is definedin the Spring.Data assembly, however it is used as a 'marker' attribute, and you can provide your own if you would like to avoid coupling your DAO implementation to a Spring attribute. This is done by setting PersistenceExceptionTranslationPostProcessor's property RepositoryAttributeType to your own attribute type.
Note
In summary: DAOs can be implemented based on the plain Hibernate 1.2/2.0 API, while still being able to participate in Spring-managed transactions and exception translation.
Note that with the new transaction namespace, you can replace the importing of DeclarativeServicesAttributeDriven.xml with the following single line, <tx:attribute-driven/> that more clearly expresses the intent as compared to the contents of DeclarativeServicesAttributeDriven.xml.
<objects xmlns="http://www.springframework.net" xmlns:tx="http://www.springframework.net/schema/tx">
242
<tx:attribute-driven/>
</objects>
The placement of the transaction attribute in the service layer method is shown below.
public class FulfillmentService : IFulfillmentService { // fields and properties for dao object omitted, see above
[Transaction(ReadOnly=false)] public void ProcessCustomer(string customerId) { //Find all orders for customer Customer customer = CustomerDao.FindById(customerId); foreach (Order order in customer.Orders) { //Validate Order Validate(order); //Ship with external shipping service ShippingService.ShipOrder(order); //Update shipping date order.ShippedDate = DateTime.Now; //Update shipment date OrderDao.SaveOrUpdate(order); //Other operations...Decrease product quantity... etc } } }
If you prefer to not use attribute to demarcate your transaction boundaries, you can import a configuration file with the following XML instead of using <tx:attribute-driven/>
<object id="TxProxyConfigurationTemplate" abstract="true" type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data"> <property name="PlatformTransactionManager" ref="HibernateTransactionManager"/> <property name="TransactionAttributes"> <name-values> <!-- Add common methods across your services here --> <add key="Process*" value="PROPAGATION_REQUIRED"/> </name-values> </property> </object>
243
Object Relational Mapping (ORM) data access Refer to the documentation on Spring Transaction management for configuration of other features, such as rollback rules.
</objects>
public class FulfillmentService : IFulfillmentService private TransactionTemplate transactionTemplate; private IProductDao productDao; private ICustomerDao customerDao; private IOrderDao orderDao; private IShippingService shippingService;
public TransactionManager TransactionManager { set { transactionTemplate = new TransactionTemplate(value); } public void ProcessCustomer(string customerId) { tt.Execute(delegate(ITransactionStatus status) { //Find all orders for customer Customer customer = CustomerDao.FindById(customerId); foreach (Order order in customer.Orders) { //Validate Order Validate(order); //Ship with external shipping service ShippingService.ShipOrder(order); //Update shipping date order.ShippedDate = DateTime.Now;
244
distributed
can export the ADO.NET Transaction used by Hibernate to plain ADO.NET access code, for a specific DbProvider. (matching connection string). This allows for high-level transaction demarcation with mixed Hibernate/ADO.NET data access!
HibernateTransactionManager
You can configure which SessionFactory the OpenSessionInViewModule will use by setting 'global' application key-value pairs as shown below. (this will change in future releases)
<appSettings> <add key="Spring.Data.NHibernate.Support.OpenSessionInViewModule.SessionFactoryObjectName" value="SessionFactory"/ > </appSettings>
245
Object Relational Mapping (ORM) data access The default behavior of the module is that a single session is currently used for the life of the request. Refer to the earlier section on Transaction Management in this chapter for more information on how sessions are managed in the OpenSessionInViewModule. You can also configure in the application setting the EntityInterceptorObjectName using the key Spring.Data.NHibernate.Support.OpenSessionInViewModule.EntityInterceptorObjectName and if SingleSession mode is used via the key Spring.Data.NHibernate.Support.OpenSessionInViewModule.SingleSession. If SingleSession is set to false, referred to as 'deferred close mode', then each transaction scope will use a new Session and kept open until the end of the web request. This has the drawback that the first level cache is not reused across transactions and that objects are required to be unique across all sessions. Problems can arise if the same object is associated with more than one hibernate session.
Important
By default, OSIV applies FlushMode.NEVER on every session it creates. This is because if OSIV flushed pending changes during "EndRequest" and an error occurs, all response has already been sent to the client. There would be no way of telling the client about the error. By default this means you MUST explicitly demarcate transaction boundaries around nonreadonly statements when using OSIV. For configuring transactions see Section 21.2.5, Declarative transaction demarcation or the Spring.Data.NHibernate.Northwind example application.
Refer to the API documentation for information on overloaded constructor. At the end of the using block the session is automatically closed. All transactions within the scope use the same session, if you are using Spring's HibernateTemplate or using Spring's implementation of NHibernate 1.2's ICurrentSessionContext interface. See other sections in this chapter for further information on those usage scenarios.
246
247
248
249
Note
Support for ASP.NET MVC is planned for Spring.NET 2.0. This chapter describes the Spring.NET Web Framework in detail. The framework is not an all-or-nothing solution. For example, you can choose to use only dependency injection and bi-directional data binding. You can adopt the web framework incrementally, addressing problems areas in your current web application with a specific feature. The Spring.NET distribution ships with a Web Quick Start application and a complete reference application, SpringAir. The Web QuickStart is the best way to learn each Spring.NET Web Framework (also referred to in this document as Spring.Web) feature, by following simple examples. The SpringAir reference application has a Spring.Web-enabled frontend that uses many best practices for Spring.NET web applications, so refer to it as you are reading this (reference) material (see Chapter 41, SpringAir - Reference Application).
250
Spring.NET Web Framework to an appropriate page after an action is executed. This usage often leads to hard-coded target URLs in the Page, which is never a good thing. Result mapping solves this problem by allowing application developers to specify aliases for action results that map to target URLs based on information in an external configuration file that can easily be edited. See Result Mapping. Standard localization support is also limited in versions of ASP.NET prior to ASP.NET 2.0. Even though Visual Studio 2003 generates a local resource file for each ASP.NET Page and user control, those resources are never used by the ASP.NET infrastructure. This means that application developers have to deal directly with resource managers whenever they need access to localized resources, which in the opinion of the Spring.NET team should not be the case. Spring.Web adds comprehensive support for localization using both local resource files and global resources that are configured within and for a Spring.NET container. See Localization and Message Sources. In addition to the aforementioned core features, Spring.Web ships with lesser features that might be useful to many application developers. Some of these additional features include back-ports of ASP.NET 2.0 features that can be used with ASP.NET 1.1, such as Master Page support. See Master Pages in ASP.NET 1.1 . To implement some features, the Spring.NET team had to extend (as in the object-oriented sense) the standard ASP.NET Page and UserControl classes. This means that in order to take advantage of the full feature stack of Spring.Web (most notably bidirectional data binding, localization and result mapping), your code-behind classes must extend Spring.Web specific base classes such as Spring.Web.UI.Page. However, powerful features such as dependency injection for ASP.NET Pages, controls, and providers can be leveraged without having to extend Spring.Web-specific base classes. By taking advantage of some of the more useful features offered by Spring.Web, you will be coupling the presentation tier of your application(s) to Spring.Web. The choice of whether or not this is appropriate is, of course, left to you.
Note
If you are using the solution templates that ship with Spring.NET this configuration will be done for you automatically whent he solution is created.
251
This snippet of standard ASP.NET configuration is only required in the root directory of each Spring.Web web application (that is, in the Web.config file present in the top level virtual directory of an ASP.NET web application). The above XML configuration snippet directs the ASP.NET infrastructure to use Spring.NET's page factory, which in turn creates instances of the appropriate .aspx Page, possibly injects dependencies into that Page (as required), and then forwards the handling of the request to the Page. After the Spring.Web page factory is configured, you also need to define a root application context by adding a Spring.NET configuration section to that same Web.config file. The final configuration file should resemble the following; your exact configuration may vary in particulars.
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web"/> </sectionGroup> </configSections> <spring> <context> <resource uri="~/Config/CommonObjects.xml"/> <resource uri="~/Config/CommonPages.xml"/> <!-- TEST CONFIGURATION --> <!-<resource uri="~/Config/Test/Services.xml"/> <resource uri="~/Config/Test/Dao.xml"/> --> <!-- PRODUCTION CONFIGURATION --> <resource uri="~/Config/Production/Services.xml"/> <resource uri="~/Config/Production/Dao.xml"/> </context> </spring> <system.web> <httpHandlers> <add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/> </httpHandlers> <httpModules> <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/> </httpModules> </system.web> </configuration>
Notes about the preceding configuration: Define a custom configuration section handler for the <context> element. If you use Spring.NET for many applications on the same web server, it might be easier to move the whole definition of the Spring.NET section group to your machine.config file.
252
Spring.NET Web Framework The custom configuration section handler is of the type Spring.Context.Support.WebContextHandler which in turn instantiates an IoC container of the type Spring.Context.Support.WebApplicationContext. This ensures that all features provided by Spring.Web, such as request and session-scoped object definitions, are handled properly. Within the <spring> element, define a root context element. Next, specify resource locations that contain the object definitions that are used within the web application (such as service or business tier objects) as child elements within the <context> element. Object definition resources can be fully-qualified paths or URLs, or non-qualified, as in the example above. Non-qualified resources are loaded using the default resource type for the context, which for the WebApplicationContext is the WebResource type. The object definition resources do not have to be the same resource type (for example, all file://, all http://, all assembly://, and so on). This means that you can load some object definitions from resources embedded directly within application assemblies (assembly://) while continuing to load other object definitions from web resources that can be more easily edited. 22.3.1.1. Configuration for IIS 7.0 on Windows Server 2008 and Windows Vista There is some configuration that is specific to using IIS7, the appropriate code snippit to place in web.config shown below.
<system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules> <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/> </modules> <handlers> <add name="SpringPageHandler" verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/> <add name="SpringContextMonitor" verb="*" path="ContextMonitor.ashx" type="Spring.Web.Support.ContextMonitor, Spring.Web"/> </handlers> </system.webServer>
253
Spring.NET Web Framework relying on an external resource containing object definitions. This is easily accomplished by creating a component Web.config similar to the following one:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="spring"> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <context type="Spring.Context.Support.WebApplicationContext, Spring.Web"> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> <object type="MyPage.aspx" parent="basePage"> <property name="MyRootService" ref="myServiceDefinedInRootContext"/> <property name="MyLocalService" ref="myServiceDefinedLocally"/> <property name="Results"> <!-- ... --> </property> </object> <object id="myServiceDefinedLocally" type="MyCompany.MyProject.Services.MyServiceImpl, MyAssembly"/> </objects> </spring> </configuration>
The <context/> element seen above (contained within the <spring/> element) simply tells the Spring.NET infrastructure code to load (its) object definitions from the spring/objects section of the web.config configuration file. If Spring.NET is used for multiple applications on the same server, you can avoid the need to specify the <configSections/> element as shown in the previous example, by moving the configuration handler definition for the <objects> element to a higher level (root) Web.config file, or even to the level of the machine.config file. This component-level context can reference definitions from its parent context(s). If a referenced object definition is not found in the current context, Spring.NET searches all ancestor contexts in the context hierarchy until it finds the object definition (or ultimately fails and throws an exception).
254
<object type="Login.aspx"> <property name="Authenticator" ref="authenticationService"/> </object> <object type="Default.aspx" parent="basePage"/> </objects>
The preceding example contains three definitions: An abstract definition for the base page from which many other pages in the application will inherit. In this case, the definition simply specifies which page is to be referenced as the master page, but it typically also configures localization-related dependencies and root folders for images, scripts, and CSS stylesheets. A login page that neither inherits from the base page nor references the master page. This page shows how to inject a service object dependency into a page instance (the authenticationService is defined elsewhere). A default application page that, in this case, simply inherits from the base page in order to inherit the master page dependency, but apart from that it does not need any additional dependency injection configuration. The configuration of ASP.NET pages differs from the configuration of other .NET classes in the value passed to the type attribute. As can be seen in the above configuration snippet, the type name is actually the path to the .aspx file for the Page, relative to its directory context. When configuring other .NET classes one would specify at minimum the fully qualified type name and the partial assembly name. In the case of the above example, those definitions are in the root context ,so Login.aspx and Default.aspx files also must be located in the root of the web application's virtual directory. The master page is defined using an absolute path because it could conceivably be referenced from child contexts that are defined within subdirectories of the web application. The definitions for the Login and Default pages do not specify either of the id and name attributes, in marked contrast to typical object definitions in Spring.NET, where the id or name attributes are usually mandatory (although not always, as in the case of inner object definitions). In the case of Spring.Web manged Page instances, one typically wants to use the name of the .aspx file name as the identifier. If an id is not specified, the Spring.Web infrastructure will simply use the name of the .aspx file as the object identifier (minus any leading path information, and minus the file extension too). Nothing prevents an application developer from specifying an id or name value explicitly; explicit naming can be useful when, for example, one wants to expose the same page multiple times using a slightly different configuration, such as Add / Edit pages. To use abstract object definitions and have your page inherit from them, use the name attribute instead of the id attribute on the abstract object definition.
255
Note
In either case, be sure to mark the object definition as abstract (by adding abstract="true" to the attribute list of the <object/> element).
normally; for example, a module of the type HtmlCommentAppenderModule, taken from the Web Quick Start, appends additional comments into the http response. It is registered as follows:
<httpModules> <add name="HtmlCommentAppender" type="HtmlCommentAppenderModule"/> </httpModules>
To configure this module, you use naming conventions to identify the module name with configuration instructions in the Spring configuration file. The ModuleTemplates property of HttpApplicationConfigurer is a dictionary that takes as a key the name of the HTTP module, in this case HtmlCommentAppender, and the Spring object definition that describes how to perform dependency injection. The object definition is in the standard <object/> style that you are used to normally when configuring an object with Spring. An example is shown below. HttpApplicationConfigurer' that configures the HtmlCommentAppender's AppendText property.
<object name="HttpApplicationConfigurer" type="Spring.Context.Support.HttpApplicationConfigurer, Spring.Web"> <property name="ModuleTemplates"> <dictionary> <entry key="HtmlCommentAppender"> <!-- this name must match the module name --> <object> <!-- select "view source" in your browser on any page to see the appended html comment --> <property name="AppendText" value="My configured comment!" /> </object> </entry> </dictionary> </property> </object>
You can see this example in action in the Web Quick Start.
Spring's MappingHandlerFactory serves a layer of indirection so that you can configure multiple handler mappings with Spring. You do this by configuring a IDictionary HandlerMap property on the class
256
Spring.NET Web Framework MappingHandlerFactoryConfigurer. The dictionary key is a regular expression that matches the request URL, and the value is a reference to the name of a Spring managed instance of an IHttpHandler or IHttpHandlerFactory . The Spring managed instance is configured via dependency injection using the standard <object/> XML configuraiton schema. The configuration of MappingHandlerFactoryConfigurer is shown:
<objects xmlns="http://www.springframework.net"> <!-- configures the global GenericHandlerFactory instance --> <object name="mappingHandlerFactoryConfigurer" type="Spring.Web.Support.MappingHandlerFactoryConfigurer, Spring.Web"> <property name="HandlerMap"> <dictionary> <!-- map any request ending with *.whatever to NoOpHandler --> <entry key="\.whatever$" value="myCustomHandler" /> <entry key="\.ashx$" value="standardHandlerFactory" /> </dictionary> </property> </object> <object name="standardHandlerFactory" type="Spring.Web.Support.DefaultHandlerFactory, Spring.Web" /> <!-- defines a standard singleton that will handle *.whatever requests --> <object name="myCustomHandler" type="MyCustomHttpHandler, App_Code"> <property name="MessageText" value="This text is injected via Spring" /> </object> <!-used for configuring ~/DemoHandler.ashx custom handler note, that this is an abstract definition because 'type' is not specified --> <object name="DemoHandler.ashx"> <property name="OutputText"> <value>This text is injected via Spring</value> </property> </object> </objects>
Spring's DefaultHandlerFactory uses the .NET class System.Web.UI.SimpleHandlerFactory to create handler instances and configures each instance by using an object definition whose name matches the request URL's filename. The abstract object definition of DemoHandler.ashx is an example of this approach. You can also configure standard classes that implement the IHttpHandler interface as demonstrated in the example above for the class MyCustomHttpHandler. Refer to the Web Quick Start application too see this in action.
257
The name of the provider must match the name of the object in the Spring configuration that will serve as the actual provider implementation. Configurable versions of the providers are found in ASP.NET so that you can use the full functionality of Spring to configure these standard provider implementations, by using property placeholders, and so on. The providers are: ConfigurableActiveDirectoryMembershipProvider ConfigurableSqlMembershipProvider ConfigurableSqlProfileProvider ConfigurableSqlRoleProvider ConfigurableXmlSiteMapProvider This example configuration taken from the Web Quick Start application sets the description property and connection string.
<object id="mySqlMembershipProvider" type="Spring.Web.Providers.ConfigurableSqlMembershipProvider"> <property name="connectionStringName" value="MyLocalSQLServer" /> <property name="parameters"> <name-values> <add key="description" value="membershipprovider description" /> </name-values> </property> </object>
Your own custom providers of course will contain additional configuration specific to your implementation.
258
A Spring server control, Panel, provides an easier way to turn off dependency injection for parts of your page:
<spring:Panel runat="server" suppressDependencyInjection="true" renderContainerTag="false"> .. put your heavy controls here - they won't be touched by DI </spring:Panel>
By wrapping the performance-sensitive parts of your page within this panel, you can easily turn off DI by setting the attribute suppressDependencyInjection to true. By default <spring:Panel/> will not render a container tag (<div>, <span>, and so on). You can modify this behavior by setting the attribute renderContainerTag accordingly.
Possible values for the scope attribute are application, session, and request. Application scope is the default, and is used for all objects with an undefined scope attribute. This scope creates a single instance of an object for the duration of the IIS application, so that the objects works exactly like the standard singleton objects in non-web applications. Session scope defines objects so that an instance is created for each HttpSession. This scope is ideal for objects such as user profile, shopping cart, and so on that you want bound to a single user. Request scope creates one instance per HTTP request. Unlike calls to prototype objects, calls to IApplicationContext.GetObject return the same instance of the request-scoped object during a single HTTP request. This allows you, for example, to inject the same request-scoped object into multiple pages and then use server-side transfer to move from one page to another. As all the pages are executed within the single HTTP request in this case, they share the same instance of the injected object. Objects can only reference other objects that are in the same or broader scope. This means that application-scoped objects can only reference other application-scoped objects, session-scoped objects can reference both session and application-scoped objects, and request-scoped objects can reference other request-, session-, or applicationscoped objects. Also, prototype objects (including all ASP.NET web pages defined within Spring.NET context) can reference singleton objects from any scope, as well as other prototype objects.
259
In the preceding code, the master page defines the overall layout for the page, in addition to four content placeholders that other pages can override. The master page can also include default content within the placeholder that will be displayed if a derived page does not override the placeholder. A page that uses this master page (Child.aspx) might look like this:
<%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> < %@ Page language="c#" Codebehind="Child.aspx.cs" AutoEventWireup="false" Inherits="ArtFair.Web.UI.Forms.Child" %> <html> <body> <spring:Content id="leftSidebarContent" contentPlaceholderId="leftSidebar" runat="server"> <!-- left sidebar content --> </spring:Content> <spring:Content id="mainContent" contentPlaceholderId="main" runat="server"> <!-- main area content --> </spring:Content> </body> </html>
The <spring:Content/> control in the example uses the contentPlaceholderId attribute (property) to specify exactly which placeholder from the master page is to be overridden. Because this particular page does not define content elements for the head and title placeholders, the content elements are defined by the default content supplied in the master page. Both the ContentPlaceHolder and Content controls can contain any valid ASP.NET markup: HTML, standard ASP.NET controls, user controls, and so on.
Tip
Technically, the <html> and <body> tags from the previous example are not strictly necessary because they are already defined in the master page. However, if these tags are omitted, Visual Studio 2003
260
Spring.NET Web Framework complains about a schema, and IntelliSense does not work. So it is much easier to work in the HTML view if those tags are included. They are ignored when the page is rendered.
This approach allows application developers to change the master page for a number of pages within a web application. You can still override the master page on a per context or per page basis by creating a new abstract page definition within a child context, or by specifying the MasterPageFile property directly.
261
Ignore for the moment the fact that none of the label controls have text defined; defining label controls is described later when we discuss localization in Spring.NET. For the purposes of the current discussion, a number of input controls are defined: tripMode radio button group, leavingFromAirportCode and goingToAirportCode dropdown lists, as well as two Spring.NET Calendar controls, departureDate and returnDate. Take a look at the model to which you bind the form:
namespace SpringAir.Domain { [Serializable] public class Trip { // fields private TripMode mode; private TripPoint startingFrom; private TripPoint returningFrom; // constructors public Trip() { this.mode = TripMode.RoundTrip; this.startingFrom = new TripPoint(); this.returningFrom = new TripPoint(); } public Trip(TripMode mode, TripPoint startingFrom, TripPoint returningFrom)
262
As you can see, Trip class uses the TripPoint class to represent departure and return, which are exposed as StartingFrom and ReturningFrom properties. It also uses TripMode enumeration to specify whether the trip is one way or return trip, which is exposed as Mode property. Here is the code-behind class that ties everything together:
public class TripForm : Spring.Web.UI.Page {
263
Note the following about the three preceding pieces of code: 1. When the page is initially loaded (IsPostback == false), the InitializeModel() method is called, which initializes the trip object by creating a new instance and setting its properties to desired values. Right before the page is rendered, the SaveModel() method is invoked, and the value it returns is stored within the HTTP session. On each postback, the LoadModel() method is called, and the value returned by the previous call to SaveModel is passed to SaveModel as an argument. In this particular case the implementation is very simple because our whole model is just the trip object. As such, SaveModel() simply returns the trip object, and LoadModel() casts the SaveModel() argument to Trip and assigns it to the trip field within the page. In more complex scenarios, the SaveModel() method will typically return a dictionary that contains your model objects. Those values will be read from the dictionary within the LoadModel() method.
264
Spring.NET Web Framework 2. InitializeDataBindings method defines the binding rules for all of the five input controls on the form. The controls are represented by the variables tripMode, leavingFromAirportCode, goingToAirportCode, departueDate, and returnDate. The binding rules are created by invoking the AddBinding method on the BindingManager exposed by the page. The AddBinding method is heavily overloaded and it allows you to specify a binding direction and a formatter to use in addition to the source and target binding expressions that are used above. These optional parameters are discussed later in this chapter. For now, focus on the source and target expressions. The Spring.NET data binding framework uses Spring.NET Expression Language to define binding expressions. In most cases, as in the example above, both source and target expression will evaluate to a property or a field within one of the controls or a data model. This is always the case when you are setting a bidirectional binding, as both binding expressions need to be "settable". The InitializeDataBindings method is executed only once per page type. Basically, all binding expressions are parsed the first time the page is instantiated, and are then cached and used by all instances of that same page type that are created at a later time. This is done for performance reasons, as data binding expression parsing on every postback is unnecessary and would add a significant overhead to the overall page processing time. 3. Notice that the SearchForFlights event handler has no dependencies on the view elements. It simply uses the injected bookingAgent service and a trip object in order to obtain a list of suggested flights. Furthermore, if you make any modifications to the trip object within your event handler, bound controls are updated accordingly just before the page is rendered.
Note
The lack of view elements in the event handler accomplishes one of the major goals we set out to achieve, allowing developers to remove view element references from the page event handlers and decouple controller-type methods from the view.
The BindSourceToTarget method is used to extract and copy bound values from the source object to the target object, and BindTargetToSource does the opposite. Both method names and parameter types are generic because
265
Spring.NET Web Framework the data binding framework can be used to bind any two objects. Using it to bind web forms to model objects is just one of its possible uses, although a very common one and tightly integrated into the Spring.NET Web Framework. The ValidationErrors parameter requires further explanation. Although the data binding framework is not in any way coupled to the data validation framework, they are in some ways related. For example, while the data validation framework is best suited to validate the populated model according to the business rules, the data binding framework is in a better position to validate data types during the binding process. However, regardless of where specific validation is performed, all error messages should be presented to the user in a consistent manner. In order to accomplish this, Spring.NET Web Framework passes the same ValidationErrors instance to binding methods and to any validators that might be executed within your event handlers. This process ensures that all error messages are stored together and are displayed consistently to the end user, using Spring.NET validation error controls. The last method in the IBinding interface, SetErrorMessage, enables you to specify the resource id of the error message to be displayed in case of binding error, as well as a list of strings, that server as identifiers to tag error messages for the purposes of linking specific error messages to locations in the page markup. We wil see an example of the SetErrorMessage usage in a later section. The IBindingContainer interface extends the IBinding interface and adds the following members:
public interface IBindingContainer : IBinding { bool HasBindings { get; } IBinding IBinding IBinding IBinding IBinding } AddBinding(IBinding binding); AddBinding(string sourceExpression, AddBinding(string sourceExpression, AddBinding(string sourceExpression, AddBinding(string sourceExpression, IFormatter formatter);
targetExpression); targetExpression, BindingDirection direction); targetExpression, IFormatter formatter); targetExpression, BindingDirection direction,
The IBindingContainer interface has several overloaded AddBinding methods. AddBinding(IBinding binding) is the most generic one, as it can be used to add any binding type to the container. The other four are convenience methods that provide a simple way to add the most commonly used implementation of the IBinding interface, SimpleExpressionBinding. The SimpleExpressionBinding was used under the covers in the example at the beginning of this section to bind our web form to a Trip instance when calling methods on the property BindingManager. Note that the BindingManager property is of the type IBindingContainer. SimpleExpressionBinding uses Spring.NET Expression Language (SpEL) to extract and to set values within source and target objects. In the TripForm example, the configuration of the BindingManager shows the basic usage of how SpEL can be used to specify a sourceExpression and targetExpression arguments. This code section is repeated below
protected override void InitializeDataBindings() { BindingManager.AddBinding("tripMode.Value", "Trip.Mode"); BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.AirportCode"); BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.AirportCode"); BindingManager.AddBinding("departureDate.SelectedDate", "Trip.StartingFrom.Date"); BindingManager.AddBinding("returnDate.SelectedDate", "Trip.ReturningFrom.Date"); }
In this case, the first argument is a sourceExpression evaluated in the context of the page itself. The sourceExpression 'tripMode.Value' represents the value in the HTML control and the targetExpression "Trip.Mode" represents the value it will be mapped onto whent the page is rendered. When the post-back happens values from in "Trip.Mode" get placed back into the HTML control "tripMode.Value". This is a common case in which bi-directional data mapping is symmetric in terms of the sourceExpression and targetExpression for
266
Spring.NET Web Framework both the initial rendering of the page and when the post-back occurs. There other overloaded methods that take BindingDirection and IFormatter arguments are discussed in the next section. 22.7.1.1. Binding direction The direction argument determines whether the binding is bidirectional or unidirectional. By default, all data bindings are bidirectional unless the direction argument is set to either BindingDirection.SourceToTarget or BindingDirection.TargetToSource. If one of these values is specified, binding is evaluated only when the appropriate BindDirection method is invoked, and is completely ignored in the other direction. This configuration is very useful when you want to bind some information from the model into non-input controls, such as labels. However, unidirectional data bindings are also useful when your form does not have a simple one-to-one mapping to a presentation model. In the earlier trip form example, the presentation model was intentionally designed to allow for simple one-to-one mappings. For the sake of discussion, let's add the Airport class and modify our TripPoint class as follows:
namespace SpringAir.Domain { [Serializable] public class TripPoint { // fields private Airport airport; private DateTime date; // constructors public TripPoint() {} public TripPoint(Airport airport, DateTime date) { this.airport = airport; this.date = date; } // properties public Airport Airport { get { return this.airport; } set { this.airport = value; } } public DateTime Date { get { return this.date; } set { this.date = value; } } } [Serializable] public class Airport { // fields private string code; private string name; // properties public string Code { get { return this.code; } set { this.code = value; } } public string Name { get { return this.name; } set { this.name = value; }
267
Instead of the string property AirportCode, our TripPoint class now exposes an Airport property of type Airport, which is defined in the preceding example. What was formerly a simple string-to-string binding, with the airport code selected in a dropdown being copied directly into the TripPoint.AirportCode property and vice versa, now becomes a not-so-simple string-to-Airport binding. So let's see how we can solve this mismatch problem of converting a string to an Airport instance and an Airport instance to a string. Binding from the model to the control, namely the Airport to the string, is still very straightforward. You set up one-way bindings from the model to controls: The Model-To-Control is represented more generally by the enumeration, BindingDirection.TargetToSource.
protected override void InitializeDataBindings() { BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.Airport.Code", BindingDirection.TargetToSource); BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.Airport.Code", BindingDirection.TargetToSource); ... }
You
code value from the Trip.StartingFrom.Airport.Code instead of Trip.StartingFrom.AirportCode since now the Code in encapsulated inside the Airport class. Unfortunately, binding from the control to the model the same way won't work, we need a way to create an Airport instance from a string. Instead, you need to find an instance of the Airport class based on the airport code and set the TripPoint.Airport property to it. Fortunately, Spring.NET data binding makes this simple, especially because you already have airportDao object defined in the Spring context (see SpringAir Spring context configuration file for details.). The AirportDao has a GetAirport(string airportCode) finder method. You set up data bindings from source to target (control-to-model) that will invoke this finder method when the page is submitted and the binding infrastructure maps the sourceExpression onto the targetExpression.evaluating the source expression. Our complete set of bindings for these two drop-down lists will then look like this:
protected override void InitializeDataBindings() { BindingManager.AddBinding("@(airportDao).GetAirport(leavingFromAirportCode.SelectedValue)", "Trip.StartingFrom.Airport", BindingDirection.SourceToTarget); BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.Airport.Code", BindingDirection.TargetToSource);
extract
the
airport
By using a pair of bindings for each control, one for each direction and using SpEL's feature to reference objects defined in the Spring context, you can resolve this data binding issue. 22.7.1.2. formatter argument The last overloaded methods of IBindingContainer we need to discuss are those that take a IFormatter argument. is an argument to the AddBinding method. This argument allows you to specify a formatter that you use to parse string value from the input control before it is bound to the model, and to format strongly typed model value before the model is bound to the control.
268
Spring.NET Web Framework You typically use one of the formatters provided in the Spring.Globalization.Formatters namespace, but if your requirements cannot be satisfied by a standard formatter, you can write your own by implementing a simple IFormatter interface:
public interface IFormatter { string Format(object value); object Parse(string value); }
Standard formatters provided with Spring.NET are: CurrencyFormatter, DateTimeFormatter, FloatFormatter, IntegerFormatter, NumberFormatter and PercentFormatter, which are sufficient for most usage scenarios. 22.7.1.3. Type conversion Because the data binding framework uses the same expression evaluation engine as the Spring.NET IoC container, it uses any registered type converters to perform data binding. Many type converters are included with Spring.NET (take a look at the classes in Spring.Objects.TypeConverters namespace) and are automatically registered for you, but you can implement your own custom converters and register them by using standard Spring.NET type converter registration mechanisms. 22.7.1.4. Data binding events Spring.Web's base Page class adds two events to the standard .NET page lifecycle: DataBound and DataUnbound. You can register for an DataUnbound event which will be fired after the data model is updated with values from the controls. Specifically, in terms of the Page lifecycle, it is fired right after the Load event and only on postbacks, because it not make sense to update the data model with the controls' initial values. The DataBound event is fired after controls are updated with values from the data model. This event occurs right before the PreRender event. The fact that the data model is updated immediately after the Load event and that controls are updated right before the PreRender event means that your event handlers can work with a correctly updated data model, as they execute after the Load event, and that any changes you make to the data model within event handlers are reflected in the controls immediately afterwards, as the controls are updated prior to the actual rendering. 22.7.1.5. Rendering binding errors If errors occur in the databinding (for example, in trying to bind a string 'hello' to an integer property on the model), you can specify how those fundamental binding errors should be rendered. The following snippet is from the Web Quick Start 'RobustEmployeeInfo' example:
[Default.aspx.cs] protected override void InitializeDataBindings() { // collect txtId.Text binding errors in "id.errors" collection BindingManager.AddBinding("txtId.Text", "Employee.Id").SetErrorMessage("ID has to be an integer", "id.errors"); ... [Default.aspx] ... <asp:TextBox ID="txtId" runat="server" /> <!-- output validation errors from "id.errors" collection --> <spring:ValidationError Provider="id.errors" runat="server" /> ...
269
Spring.NET Web Framework The SetErrorMessage specifies the message text or resource id of the error message to be displayed. This is followed by a a variable length list of strings that serve to as a means to assign a friendly name to associate with this error should it occur. The same 'tag', or error provider name, can be used across different calls to 'AddBinding'. This is commonly the case if you want to present several errors together in the page. In the preceding example, the 'tag' or error provider name is "id.errors" will be rendered in Spring's ValidationError User Control, for example as shown below in this fragment of page markup. Validation controls are discussed more extensively in this section.
<td> <asp:TextBox ID="txtId" runat="server" EnableViewState="false" /> <spring:ValidationError ID="errId" Provider="id.errors" runat="server" /><!-- read msg from "id.error" provider --> </td>
22.7.1.6. HttpRequestListBindingContainer HttpRequestListBindingContainer extracts posted raw values from the request and populates the specified IList by creating objects of the type specified and populating each object according to the requestBindings collection. Please check out the Web Quick Start sample's demo of HttpRequestListBindingContainer. Below is an exerpt from that example showing how to use a HttpRequestListBindingContainer.
protected override void InitializeDataBindings() { // HttpRequestListBindingContainer unbinds specified values from Request -> Productlist HttpRequestListBindingContainer requestBindings = new HttpRequestListBindingContainer("sku,name,quantity,price", "Products", typeof(ProductInfo)); requestBindings.AddBinding("sku", "Sku"); requestBindings.AddBinding("name", "Name"); requestBindings.AddBinding("quantity", "Quantity", quantityFormatter); requestBindings.AddBinding("price", "Price", priceFormatter); BindingManager.AddBinding(requestBindings); }
Note
Because browsers do not send the values of unchecked checkboxes, you cannot use HttpRequestListBindingContainer with <input type="checkbox" > html controls.
270
Using DataBindingPanel, you can specify the binding information directly on the control declaration. The following attributes are recognized by a DataBindingPanel: BindingTarget corresponds to the target expression used in IBindingContainer.AddBinding(). BindingSource corresponds to the source expression used in IBindingContainer.AddBinding(). For standard controls you don't need to specify the source expression. If you are binding to some custom control, of course you must specific this attribute. BindingDirection is one of the values of the BindingDirection enumeration. BindingFormatter is the object name of a custom formatter. The formatter instance is obtained by a call to IApplicationContext.GetObject() each time it is needed. BindingType is the type of a completely customized binding. Note that a custom binding type must implement the following constructor signature:
ctor(string source,string target, BindingDirection, IFormatter)
Note
The Visual Studio Web Form Editor complains about binding attributes because it does not recognize them. You can safely ignore those warnings.
271
Spring.NET Web Framework Every .aspx page in an ASP.NET project has a resource file associated with it, but those resources are never used by the current ASP.NET infrastructure). ASP.NET 2.0 changes this and allow application developers to use local resources for pages. In the meantime, the Spring.NET team built in to Spring.Web support for using local pages resources, thus allowing ASP.NET 1.1 application developers to using ASP.NET 2.0-like page resources. Spring.Web supports several different approaches to localization within a web application, which can be mixed and matched as appropriate. You can use push and pull mechanisms, as well as globally defined resources when a local resource cannot be found. Spring.Web also supports user culture management and image localization, which are described in later sections.
Tip
For introductory information covering ASP.NET globalization and localization, see Globalization Architecture for ASP.NET and Localization Practices for ASP.NET 2.0 by Michele Leroux Bustamante.
local resource file. Future releases of Spring.NET may provide other localizers that read resources from an XML file or even from a flat text file that contains resource name-value pairs that allow application developers to store resources within the files in a web application instead of as embedded resources in an assembly. Of course, if an application developer prefers to store such resources in a database, the developer can write a custom ILocalizer implementation that loads a list of resources to apply from a database. You typically configure the localizer to be used within an abstract base definition for those pages that require localization:
<object id="localizer" type="Spring.Globalization.Localizers.ResourceSetLocalizer, Spring.Core"/> <object name="basePage" abstract="true"> <description> Pages that reference this definition as their parent (see examples below) will automatically inherit following properties. </description> <property name="Localizer" ref="localizer"/> </object>
Of course, nothing prevents an application developer from defining a different localizer for each page in the application; in any case, one can always override the localizer defined in a base (page) definition. Alternatively, if one does want any resources to be applied automatically one can completely omit the localizer definition.
272
Spring.NET Web Framework One last thing to note is that Spring.NET UserControl instances will (by default) inherit the localizer and other localization settings from the page that they are contained within, but one can similarly also override that behavior using explicit dependency injection.
In the preceding .aspx code, none of the Label or Button controls have had a value assigned to the Text property. The values of the Text property for these controls are stored in the local resource file (of the page) using the following convention to identify the resource (string).
$this.controlId.propertyName
273
For more information on configuring localizers see Section 22.8.1, Working with localizers
In some cases it makes sense to apply a resource that is defined globally as opposed to locally. In this example, it makes better sense to define values for the Save and Cancel buttons globally as they will probably be used throughout the application. Spring Framework (Version 1.3.2) 274
Spring.NET Web Framework The above example demonstrates how one can achieve that by defining a resource redirection expression as the value of a local resource by prefixing a global resource name with the following string.
$messageSource.
In the preceding example, this string tells the localizer to use the save and cancel portions of the resource key as lookup keys to retrieve the actual values from a global message source. You need to define a resource redirect only once, typically in the invariant resource file. Any lookup for a resource redirect falls back to the invariant culture, and results in a global message source lookup using the correct culture. Global resources are (on a per-context basis) defined as a plain vanilla object definition using the reserved name of messageSource, which you can add to your Spring.NET configuration file:
<object id="messageSource" type="Spring.Context.Support.ResourceSetMessageSource, Spring.Core"> <property name="ResourceManagers"> <list> <value>MyApp.Web.Resources.Strings, MyApp.Web</value> </list> </property> </object>
See the SpringAir example application for more. The global resources are cached within the Spring.NET IApplicationContext and are accessible through the Spring.NET IMessageSource interface. The Spring.Web Page and UserControl classes have a reference to their owning IApplicationContext and its associated IMessageSource. As such, they automatically redirect resource lookups to a global message source if a local resource cannot be found. Currently, the ResourceSetMessageSource is the only message source implementation that ships with Spring.NET.
275
The GetMessage method is available within both the Spring.Web.UI.Page and Spring.Web.UI.UserControl classes, and it falls back automatically to a global message source lookup if a local resource is not found.
Once an appropriate folder hierarchy is in place, you put the localized images in the appropriate directories and make sure that different translations of the same image have the same image name within the folders. To place a localized image on a page, you use the <spring:LocalizedImage>:
<%@ Page language="c#" Codebehind="StandardTemplate.aspx.cs" AutoEventWireup="false" Inherits="SpringAir.Web.StandardTemplate" %> <%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <html> <body> <spring:LocalizedImage id="logoImage" imageName="spring-air-logo.jpg" borderWidth="0" runat="server" /> </body> </html>
This control will find the most specific directory that contains an image with the specified name using standard localization fallback rules and the user's culture. For example, if the user's culture is 'en-US', the localizer will look for the spring-air-logo.jpg file in Images/en-US, then in Images/en and finally, if the image file has still not been found, in the root Images directory (which for all practical purposes serves as an invariant culture folder).
property
276
Several useful implementations of ICultureResolver ship as part of Spring.Web, so it is unlikely that application developers need to implement their own culture resolver. However, you do need to implement your own culture resolver, the resulting implementation should be fairly straightforward as you need to implement only two methods. The following sections discuss each available implementation of the ICultureResolver interface. 22.8.6.1. DefaultWebCultureResolver
DefaultWebCultureResolver, the default culture resolver implementation, is used if you do not specify a culture
resolver for a page, or if you inject a DefaultWebCultureResolver into a page definition explicitly. The latter case (explicit injection) is sometimes useful because you can specify a culture that should always be used, by defining the DefaultCulture property on the resolver. The DefaultWebCultureResolver looks first at the DefaultCulture property and return its value if said property value is not null. If it is null, the DefaultWebCultureResolver falls back to request header inspection. If no 'Accept-Lang' request headers are present , the resolver returns the UI culture of the currently executing thread. 22.8.6.2. RequestCultureResolver The RequestCultureResolver resolver operates similar to the DefaultWebCultureResolver, except that it always checks request headers first, and only then falls back to the value of the DefaultCulture property or the culture code of the current thread. 22.8.6.3. SessionCultureResolver The SessionCultureResolver resolver looks for culture information in the user's session and returns the information if it finds it. If not, SessionCultureResolver falls back to the behavior of the DefaultWebCultureResolver. 22.8.6.4. CookieCultureResolver This resolver looks for culture information in a cookie, and return it if it finds one. If not, it falls back to the behavior of the DefaultWebCultureResolver.
Warning
CookieCultureResolver does not work if your application uses localhost as the server URL, which
is a typical setting in a development environment. To work around this limitation, use SessionCultureResolver during development and switch to CookieCultureResolver before you deploy the application in a production. This is easily accomplished in Spring.Web (simply change the config file) but is something that you should be aware of.
277
Spring.NET Web Framework You also can write a custom ICultureResolver that persists culture information in a database, as part of a user's profile.
<object id="cultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web" />
Once that requirement is satisfied, you set the UserCulture property to a new CultureInfo object before the page is rendered. In the following .aspx example, two link buttons can be used to change the user's culture. In the code-behind, this is all one need do to set the new culture. A code snippet for the code-behind file (UserRegistration.aspx.cs) is shown below.
protected override void OnInit(EventArgs e) { InitializeComponent(); this.english.Command += new CommandEventHandler(this.SetLanguage); this.serbian.Command += new CommandEventHandler(this.SetLanguage); base.OnInit(e); } private void SetLanguage(object sender, CommandEventArgs e) { this.UserCulture = new CultureInfo((string) e.CommandArgument); }
278
The only property for which you must supply a value for each result is the TargetPage property. The value of the Mode property can be Transfer, TransferNoPreserve, or Redirect, and defaults to Transfer if none is specified. TransferNoPreserve issues a server-side transfer with 'preserveForm=false', so that QueryString and Form data are not preserved. If your target page requires parameters, you can define them with the Parameters dictionary property. You specify literal values or object navigation expressions for such parameter values. An expression is evaluated in the context of the page in which the result is being referenced. In the preceding example, any page that uses the homePageResult needs to expose a UserInfo property on the page class itself.
Note
In Spring.NET 1.1.0 and earlier, the prefix indicated an object navigation expression in the Parameters dictionary property was the dollar sign, for example, ${UserInfo.FullName}.This convention conflicted with the prefix used to perform property replacement, the dollar sign, as described in the section PropertyPlaceholderConfigurer. As a workaround you can differentiate the prefix and suffix used in PropertyPlaceholderConfigurer, for example prefix = $${ and suffix = }. In Spring. NET 1.1.1, a new prefix character, the percent sign (i.e.%{UserInfo.FullName}.) can be used in the Parameters dictionary to avoid this conflict , so you can keep the familiar NAnt style PropertyPlaceholderConfigurer defaults. Parameters are handled differently depending on the result mode. For redirect results, every parameter is converted to a string, then URL encoded, and finally appended to a redirect query string. Parameters for transfer results are added to the HttpContext.Items collection before the request is transferred to the target page. Transfers are more flexible because any object can be passed as a parameter between pages. They are also more efficient because they don't require a round-trip to the client and back to the server, so transfer mode is recommended as the preferred result mode (it is also the current default).
Tip
If you need to customize how a redirect request is generated, for example, to encrypt the request parameters, subclass the Request object and override one or more protected methods, for example string BuildUrl( string resolvedPath, IDictionary resolvedParameters ). See the API documentation for additional information. The preceding example shows independent result object definitions, which are useful for global results such as a home- and login- page. Result definitions are only used by one page should be simply embedded within the definition of a page, either as inner object definitions or using a special shortcut notation for defining a result definition:
<object type="~/UI/Forms/UserRegistration.aspx" parent="basePage"> <property name="UserManager"> <ref object="userManager"/> </property> <property name="Results">
279
The short notation for the result must adhere to the following format...
[<mode>:]<targetPage>[?param1,param2,...,paramN]
Possible values for the mode value referred to in the preceding notation snippet: redirect: calls Response.Redirect(string) redirectNoAbort: calls Response.Redirect(string, false) transfer: calls Server.Transfer(string) TransferNoPreserve: calls Server.Transfer(string, false) These values correspond to the values of the ResultMode enumeration. A comma separates parameters instead of an ampersand; this avoids laborious ampersand escaping within an XML object definition. The use of the ampersand character is still supported if required, but you then have to specify the ampersand character using the well known & entity reference. After you define your results, you can use them within the event handlers of your pages (UserRegistration.apsx.cs):
private void SaveUser(object sender, EventArgs e) { UserManager.SaveUser(UserInfo); SetResult("userSaved"); } public void Cancel(object sender, EventArgs e) { SetResult("cancel"); } protected override void OnInit(EventArgs e) { InitializeComponent(); this.saveButton.Click += new EventHandler(this.SaveUser); this.cancelButton.Click += new EventHandler(this.Cancel); base.OnInit(e); }
You can further refactor the preceding example and use defined constants, which is advisable when a logical result name such as "home" is likely to be referenced by many pages.
The interface IResultFactory is responsible for creating an IResult object from these two pieces:
280
You use a ResultFactoryRegistry to associate a given resultmode string with an IResultFactory implementation:
class MySpecialResultLogic : IResult { ... } class MySpecialResultLogicFactory : IResultFactory { IResult Create( string mode, string expression ) { /* ... convert 'expression' into MySpecialResultLogic */ } } // register with global factory ResultFactoryRegistry.RegisterResultFactory( "mySpecialMode", new MySpecialResultLogicFactory );
281
The preceding example above shows how you typically set-up a <head> section within a master page template to be able to change the title value and to add additional elements to the <head> section from the child pages using <spring:ContentPlaceholder> controls. However, only the <spring:Head> declaration is required in order for Spring.NET Register* scripts to work properly.
282
Spring.NET Web Framework validation framework. ValidationError is used to display field-level validation errors. Please refer to the section ASP.NET usage tips in the chapter on the Validation Framework more information.
283
All that one needs to do in order to use the WebServiceExporter is: 1. Configure the Web.config file of your ASP.NET AJAX application as a Spring.Web application.
<sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web"/> </sectionGroup>
<spring>
284
ASP.NET AJAX
<context> <resource uri="~/Spring.config"/> </context> </spring>
2. Register the HTTP handler and the Spring HttpModule under the system.web section.
<httpHandlers> <remove verb="*" path="*.asmx"/> <add verb="*" path="*.asmx" validate="false" type="Spring.Web.Script.Services.ScriptHandlerFactory, Spring.Web.Extensions"/> <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/> </httpHandlers> <httpModules> <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="SpringModule" type="Spring.Context.Support.WebSupportModule, Spring.Web"/> </httpModules>
3. Register the HTTP handler and the Spring HttpModule under system.webServer section.
<modules> <add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="SpringModule" type="Spring.Context.Support.WebSupportModule, Spring.Web"/> </modules> <handlers> <remove name="WebServiceHandlerFactory-Integrated" /> <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="Spring.Web.Script.Services.ScriptHandlerFactory, Spring.Web.Extensions"/> <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handler System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </handlers>
You can find a full Web.config file in the example that comes with this integration.
285
Chapter 24. Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 2.0
24.1. Introduction to Spring.NET ASP.NET MVC Infrastructure
The Spring.NET for ASP.NET MVC Infrastructure increases your productivity when you write ASP.NET MVC 2.0 applications by making the full power of the Spring.NET framework available to your MVC projects. Highlights of the Spring.NET for ASP.NET MVC Infrastructure (also referred to in this document as Spring.Web.Mvc) are: Dependency Injection of Controllers and ActionFilters. ASP.NET MVC 2.0 provides two primary extensbility points for Dependency Injection: Controllers and ActionFilters. Spring.Web.Mvc makes it extremely simple to inject dependencies into either your MVC Controllers or ActionFilters. Simply register your Controllers and ActionFilters with the context using any one of the typical object definition approaches supported by Spring.NET and the Spring.Web.Mvc infrastructure will ensure these objects are assembled correctly when the ASP.NET MVC run-time has need of them. Web object scopes. Just as with the Spring.NET Web Infrastructure for ASP.NET Webforms, Spring.Web.Mvc objects can be defined at the application, session, or request scope. This capability makes it easy to inject, for example, a session scoped shopping cart, into your controllers without any lower level programming. The Spring.NET distribution ships with a Web.Mvc Quick Start application. The Web.Mvc QuickStart is the best way to see how to integrate Spring.Web.Mvc into your own ASP.NET MVC applications.
Note that the SpringMvcApplication class is abstract so that developers may only use it indirectly as a superclass of their own global application class in the Global.asax of their ASP.NET MVC applications. After the Global.asax is modified as indicated above, you also need to define a root application context by adding a Spring.NET configuration section to your Web.config file. The final configuration file should resemble the
286
Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 2.0 following; your exact configuration may vary in particulars and the following snippet illustrates only the Springspecfic entries and excludes the remainder of the content (typically) required by ASP.NET MVC.
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.MvcContextHandler, Spring.Web.Mvc"/> </sectionGroup> </configSections> <spring> <context> <resource <resource <resource <resource </context> </spring> </configuration>
Notes about the preceding configuration: Define a custom configuration section handler for the <context> element. If you use Spring.NET for many applications on the same web server, it might be easier to move the whole definition of the Spring.NET section group to your machine.config file. The custom configuration section handler is of the type Spring.Context.Support.MvcContextHandler which in turn instantiates an IoC container of the type Spring.Context.Support.MvcApplicationContext. This ensures that all features provided by Spring.Web.Mvc, such as request and session-scoped object definitions, are handled properly. Within the <spring> element, define a root context element. Next, specify resource locations that contain the object definitions that are used within the web application (such as service or business tier objects) as child elements within the <context> element. Object definition resources can be fully-qualified paths or URLs, or non-qualified, as in the example above. Non-qualified resources are loaded using the default resource type for the context, which for the MvcApplicationContext is the WebResource type. The object definition resources do not have to be the same resource type (for example, all file://, all http://, all assembly://, and so on). This means that you can load some object definitions from resources embedded directly within application assemblies (assembly://) while continuing to load other object definitions from web resources that can be more easily edited.
287
Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 2.0 24.2.2.1. Application_Start(object sender, EventArgs e) This method is provided by the Microsoft base HttpApplication class and is overridden in the SpringMvcApplication base class to be responsible for invoking the RegisterRoutes() and RegisterAreas() methods. If you choose to override the Application_Start() implementation of the SpringMvcApplication class in your own implementation, ensure that you either call base.Application_Start() or explicitly invoke both the RegisterRoutes() and RegisterAreas() methods within your within your override of this method. 24.2.2.2. ConfigureApplicationContext() This method is invoked by the SpringMvcApplication class after it has been configured with all of its object definitions and other settings (as detailed in Configuration of a ASP.NET MVC Application) but immediately prior to its being handed off to the ASP.NET MVC infrastructure for its use. Overridding this method provides you with your last possible moment to make any additional modifications to the IApplicationContext before it is put into service for the ASP.NET MVC framework's use. In the SpringMvcApplication base class, this method is a no-op and thus does nothing. It exists only to provide an extensibility point for developers wishing to interact with the IApplicationContext at this point in the application startup/context configuration lifecycle. 24.2.2.3. RegisterSpringControllerFactory() This method is responsible for registering the SpringControllerFactory with the ASP.NET MVC framework, in effect telling ASP.NET MVC "please use the SpringControllerFactory to create Controllers." This is the manner in which the Spring.NET container is subsequently invoked to satisfy dependencies on Controllers when they are instantiated by ASP.NET MVC in response to an Http Request. Generally, there should be little need for the developer to override this method, but if you do you must ensure that your either invoke the base implementation of RegisterSpringControllerFactory() from within your implementation or that you explicitly register the SpringControllerFactory with the ASP.NET MVC infrastructure yourself from witihin this method (or elsewhere at the appropriate time). 24.2.2.4. RegisterRoutes(RouteCollection routes) This method is responsible for registering ASP.NET MVC Routes during application startup and is automatically invoked from within the Application_Start() method in the SpringMvcApplication base class. The provided implementation of this method in the SpringMvcApplication class merely registers the same Default route as is present in any new ASP.NET MVC project that Visual Studio creates (e.g., "{controller}/{action}/{id}"). As such it is expected that most developers will override this method and provide their own implementation wherein they will register their own routes. Unless you desire to retain the out-of-the-box Default routing configuration of "{controller}/{action}/{id}" it is not necessary for developers to call the RegisterRoutes() method of the SpringMvcApplication base class from within their own overrides of this method. 24.2.2.5. RegisterAreas() This method is responsible for registering ASP.NET MVC Areas during application startup and is automatically invoked from within the Application_Start() method in the SpringMvcApplication base class. The provided implementation of this method in the SpringMvcApplication class merely invokes AreaRegistration.RegisterAllAreas() in the ASP.NET MVC framework. As such, it is not common to have to override this method as provided in the SpringMvcApplication base class unless you desire more fine-grained control over registering areas. If you choose to override this method in your own derived class, you are assuming the responsibility of registering all Areas with the ASP.NET MVC runtime.
288
Possible values for the scope attribute are application, session, and request. Application scope is the default, and is used for all objects with an undefined scope attribute. This scope creates a single instance of an object for the duration of the IIS application, so that the objects works exactly like the standard singleton objects in non-web applications. Session scope defines objects so that an instance is created for each HttpSession. This scope is ideal for objects such as user profile, shopping cart, and so on that you want bound to a single user. Request scope creates one instance per HTTP request. Unlike calls to prototype objects, calls to IApplicationContext.GetObject return the same instance of the request-scoped object during a single HTTP request. This allows you, for example, to inject the same request-scoped object into multiple pages and then use server-side transfer to move from one page to another. As all the pages are executed within the single HTTP request in this case, they share the same instance of the injected object. Objects can only reference other objects that are in the same or broader scope. This means that application-scoped objects can only reference other application-scoped objects, session-scoped objects can reference both session and application-scoped objects, and request-scoped objects can reference other request-, session-, or applicationscoped objects. Also, prototype objects (including all ASP.NET web pages defined within Spring.NET context) can reference singleton objects from any scope, as well as other prototype objects.
289
Chapter 25. Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 3.0
25.1. Introduction to Spring.NET ASP.NET MVC Infrastructure
The Spring.NET for ASP.NET MVC Infrastructure increases your productivity when you write ASP.NET MVC 3.0 applications by making the full power of the Spring.NET framework available to your MVC projects. Highlights of the Spring.NET for ASP.NET MVC Infrastructure for ASP.NET MVC 3.0 (also referred to in this document as Spring.Web.Mvc) are: Spring.NET-specific Implementation of IDependencyResolver for Injection of Controllers, ActionFilters, and all other types requested by the ASP.NET MVC 3.0 runtime. ASP.NET MVC 3.0 finally centralizes the previous multiple extensbility points for Dependency Injection into a single, central responsibility: any imlementation of the IDependencyResolver interface. Spring.Web.Mvc makes it extremely simple to inject dependencies of any type into your MVC applications. Simply register your Controllers, ActionFilters, etc. with the context using any one of the typical object definition approaches supported by Spring.NET and the Spring.Web.Mvc infrastructure will ensure these objects are assembled correctly when the ASP.NET MVC run-time has need of them. Web object scopes. Just as with the Spring.NET Web Infrastructure for ASP.NET Webforms, Spring.Web.Mvc objects can be defined at the application, session, or request scope. This capability makes it easy to inject, for example, a session scoped shopping cart, into your controllers without any lower level programming. The Spring.NET distribution ships with a Web.Mvc Quick Start application. The Web.Mvc QuickStart is the best way to see how to integrate Spring.Web.Mvc into your own ASP.NET MVC applications.
Note that the SpringMvcApplication class is abstract so that developers may only use it indirectly as a superclass of their own global application class in the Global.asax of their ASP.NET MVC applications.
290
Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 3.0 After the Global.asax is modified as indicated above, you also need to define a root application context by adding a Spring.NET configuration section to your Web.config file. The final configuration file should resemble the following; your exact configuration may vary in particulars and the following snippet illustrates only the Springspecfic entries and excludes the remainder of the content (typically) required by ASP.NET MVC.
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.MvcContextHandler, Spring.Web.Mvc"/> </sectionGroup> </configSections> <spring> <context> <resource <resource <resource <resource </context> </spring> </configuration>
Notes about the preceding configuration: Define a custom configuration section handler for the <context> element. If you use Spring.NET for many applications on the same web server, it might be easier to move the whole definition of the Spring.NET section group to your machine.config file. The custom configuration section handler is of the type Spring.Context.Support.MvcContextHandler which in turn instantiates an IoC container of the type Spring.Context.Support.MvcApplicationContext. This ensures that all features provided by Spring.Web.Mvc, such as request and session-scoped object definitions, are handled properly. Within the <spring> element, define a root context element. Next, specify resource locations that contain the object definitions that are used within the web application (such as service or business tier objects) as child elements within the <context> element. Object definition resources can be fully-qualified paths or URLs, or non-qualified, as in the example above. Non-qualified resources are loaded using the default resource type for the context, which for the MvcApplicationContext is the WebResource type. The object definition resources do not have to be the same resource type (for example, all file://, all http://, all assembly://, and so on). This means that you can load some object definitions from resources embedded directly within application assemblies (assembly://) while continuing to load other object definitions from web resources that can be more easily edited.
291
Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 3.0 25.2.2.1. Application_BeginRequest(object sender, EventArgs e) This method is provided by the Microsoft base HttpApplication class and is overridden in the SpringMvcApplication base class to be responsible for invoking the BuildDependencyResolver and RegisterDependencyResolver methods. If you choose to override the Application_BeginRequest implementation of the SpringMvcApplication class in your own implementation, ensure that you either call base.Application_BeginRequest or explicitly invoke both the BuildDependencyResolver and RegisterDependencyResolver methods within your override of this method. 25.2.2.2. ConfigureApplicationContext() This method is invoked by the SpringMvcApplication class after it has been configured with all of its object definitions and other settings (as detailed in Configuration of a ASP.NET MVC Application) but immediately prior to its being handed off to the ASP.NET MVC infrastructure for its use. Overridding this method provides you with your last possible moment to make any additional modifications to the IApplicationContext before it is put into service for the ASP.NET MVC framework's use. In the SpringMvcApplication base class, this method is a no-op and thus does nothing. It exists only to provide an extensibility point for developers wishing to interact with the IApplicationContext at this point in the application startup/context configuration lifecycle. 25.2.2.3. BuildDependencyResolver() This method is responsible for assembling and returning the Spring.NET-specific implementation of the ASP.NET MVC framework's IDependencyResolver interface. The provided implementation of this method In the SpringMvcApplication class returns an instance of the SpringMvcDependencyResolver class wired up to use the configured IApplicationContext. For fine-grained control of the manner in which the IDependencyResolver implementation is constructed, this method may be overriddem in a class of your own derived from SpringMvcApplication. 25.2.2.4. RegisterDependencyResolver(IDependencyResolver resolver) This method is responsible for registering the SpringDependencyResolver with the ASP.NET MVC framework, in effect telling ASP.NET MVC "please use this SpringDependencyResolver to create objects when ASP.NET MVC requests them". This is the manner in which the Spring.NET container is subsequently invoked to satisfy dependencies on Controllers, ActionFilters, and other object types when they are instantiated by ASP.NET MVC in response to an Http Request. Generally, there should be little need for the developer to override this method, but if you do you must ensure that your either invoke the base implementation of RegisterDependencyResolver from within your implementation or that you explicitly register the SpringMvcDependencyResolver with the ASP.NET MVC infrastructure yourself from witihin this method (or elsewhere at the appropriate time).
Possible values for the scope attribute are application, session, and request. Application scope is the default, and is used for all objects with an undefined scope attribute. This scope creates a single instance of an object for the duration of the IIS application, so that the objects works exactly like the standard singleton objects in non-web applications. Session scope defines objects so that an instance is created for each HttpSession. This scope is ideal for objects such as user profile, shopping cart, and so on that you want bound to a single user.
292
Spring.NET ASP.NET MVC Infrastructure for ASP.NET MVC 3.0 Request scope creates one instance per HTTP request. Unlike calls to prototype objects, calls to IApplicationContext.GetObject return the same instance of the request-scoped object during a single HTTP request. This allows you, for example, to inject the same request-scoped object into multiple pages and then use server-side transfer to move from one page to another. As all the pages are executed within the single HTTP request in this case, they share the same instance of the injected object. Objects can only reference other objects that are in the same or broader scope. This means that application-scoped objects can only reference other application-scoped objects, session-scoped objects can reference both session and application-scoped objects, and request-scoped objects can reference other request-, session-, or applicationscoped objects. Also, prototype objects (including all ASP.NET web pages defined within Spring.NET context) can reference singleton objects from any scope, as well as other prototype objects.
293
294
Spring implements this exporter functionality by creating a proxy at runtime that meets the implementation requirements of a specific distributed technology. In the case of .NET Remoting the proxy will inherit from MarshalByRef, for EnterpriseServices it will inherit from ServicedComponent and for aspx web services, WebMethod attributes will be added to methods. Client side functionality is often implemented by a thin layer over the client access mechanism of the underlying distributed technology, though in some cases such as client
295
Introduction to Spring Services side access to web services, you have the option to create a proxy on the fly from the .wsdl definition, much like you would have done using the command line tools. The common implementation theme for you as a provider of these service objects is to implement an interface. This is generally considered a best practice in its own right, you will see most pure WCF examples following this practice, and also lends itself to a straightforward approach to unit testing business functionality as stub or mock implementations may be defined for testing purposes. The assembly Spring.Services.dll contains support for .NET Remoting, Enterprise Services and ASMX Web Services. Support for WCF services is planned for Spring 1.2 and is currently in the CVS repository if you care to take an early look.
296
The class AdvancedMBRCalculator used above inherits from MarshalByRefObject. If your design calls for configuring a singleton SAO, or using a non-default constructor, you can use the Spring IoC container to create the SAO instance, configure it, and register it with the .NET remoting infrastructure. The SaoExporter class performs this task and most importantly, will automatically create a proxy class that inherits from MarshalbyRefObject if your business object does not already do so. The following XML taken from the Remoting QuickStart demonstrates its usage to an SAO Singleton object
297
.NET Remoting This XML fragment shows how an existing object "singletonCalculator" defined in the Spring context is exposed under the url-path name "RemotedSaoSingletonCalculator". (The fully qualified url is tcp:// localhost:8005/RemotedSaoSingleCallCalculator using the standard .NET channel configuration shown further below.) AdvancedCalculator class implements the business interface IAdvancedCalculator. The current proxy implementation requires that your business objects implement an interface. The interfaces' methods will be the ones exposed in the generated .NET remoting proxy. The initial memory of the calculator is set to 217 via the constructor. The class AdvancedCalculator does not inherit from MarshalByRefObject. Also note that the exporter sets the lifetime of the SAO Singleton to infinite so that the singleton will not be garbage collected after 5 minutes (the .NET default lease time). If you would like to vary the lifetime properties, they are InitialLeaseTime, RenewOnCallTime, and SponsorshipTimeout. A custom schema is provided to make the object declaration even easier and with intellisense support for the attributes. This is shown below
<objects xmlns="http://www.springframework.net" xmlns:r="http://www.springframework.net/remoting"> <r:saoExporter targetName="singletonCalculator" serviceName="RemotedSaoSingletonCalculator" /> ... other object definitions </objects>
Refer to the end of this chapter for more information on Spring's .NET custom schema.
Note that we change the singleton attribute of the plain CLR object as configured by Spring in the <object> definition and not an attribute on the SaoExporter. The object referred to in the TargetName parameter can be an AOP proxy to a business object. For example, if we were to apply some simple logging advice to the singleton calculator, the following standard AOP configuration is used to create the target for the SaoExporter
<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 name="saoSingletonCalculatorWeaved" type="Spring.Remoting.SaoExporter, Spring.Services"> <property name="TargetName" value="singletonCalculatorWeaved" /> <property name="ServiceName" value="RemotedSaoSingletonCalculatorWeaved" /> </object>
298
.NET Remoting
Note
As generally required with a .NET Remoting application, the arguments to your service methods should be Serializable.
A console application that will host this Remoted object needs to initialize the .NET Remoting infrastructure with a call to RemotingConfiguration (since we are using the .config file for channel registration) and then start the Spring application context. This is shown below
RemotingConfiguration.Configure("RemoteApp.exe.config"); IApplicationContext ctx = ContextRegistry.GetContext(); Console.Out.WriteLine("Server listening..."); Console.ReadLine();
You can also put in the configuration file an instance of the object Spring.Remoting.RemotingConfigurer to make the RemotingConfiguration call show above on your behalf during initialization of the IoC container. The RemotingConfigurer implements the IObjectFactoryPostProcessor interface, which gets called after all object definitions have been loaded but before they have been instantiated, (SeeSection 5.9.2, Customizing configuration metadata with ObjectFactoryPostProcessors for more information). The RemotingConfigurer has two properties you can configure. Filename, that specifies the filename to load the .NET remoting configuration from (if null the default file name is used) and EnsureSecurity which makes sure the channel in encrypted (available only on .NET 2.0). As a convenience, the custom Spring remoting schema can be used to define an instance of this class as shown below, taken from the Remoting QuickStart
<objects xmlns="http://www.springframework.net" xmlns:r="http://www.springframework.net/remoting"> <r:configurer filename="Spring.Calculator.RemoteApp.exe.config" /> </objects>
The ReadLine prevents the console application from exiting. You can refer to the code in RemoteApp in the Remoting QuickStart to see this code in action.
299
.NET Remoting
void Application_Start(object sender, EventArgs e) { // Code that runs on application startup // Ensure Spring has loaded configuration registering context Spring.Context.IApplicationContext ctx = new Spring.Context.Support.XmlApplicationContext( HttpContext.Current.Server.MapPath("Spring.Config")); Spring.Context.Support.ContextRegistry.RegisterContext(ctx); }
In this example, the Spring configuration file is named Spring.Config. Inside Web.config you add a standard <system.runtime.remoting> section. Note that you do not need to specify the port number of your channels as they will use the port number of your web site. Ambiguous results have been reported if you do specify the port number. Also, in order for IIS to recognize the remoting request, you should add the suffix '.rem' or '.soap' to the target name of your exported remote object so that the correct IIS handler can be invoked.
To obtain a reference to a SAO proxy within the IoC container, you can use the object factory SaoFactoryObject in the Spring configuration file. The following XML taken from the Remoting QuickStart demonstrates its usage.
<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>
The ServiceInterface property specifies the type of proxy to create while the ServiceUrl property creates a proxy bound to the specified server and published object name. Other objects in the IoC container that depend on an implementation of the interface ICalculator can now refer to the object "calculatorService", thereby using a remote implementation of this interface. The exposure of dependencies among objects within the IoC container lets you easily switch the implementation of ICalculator. By using the IoC container changing the application to use a local instead of remote implementation is a configuration file change, not a code change. By promoting interface based programing, the ability to switch implementation makes it easier to unit test the client application, since unit testing can be done with a mock
300
.NET Remoting implementation of the interface. Similarly, development of the client can proceed independent of the server implementation. This increases productivity when there are separate client and server development teams. The two teams agree on interfaces before starting development. The client team can quickly create a simple, but functional implementation and then integrate with the server implementation when it is ready.
To export this as a CAO object we can declare the CaoExporter object directly in the server's XML configuration file, as shown below
<object id="caoCalculator" type="Spring.Remoting.CaoExporter, Spring.Services"> <property name="TargetName" value="prototypeCalculator" /> <property name="Infinite" value="false" /> <property name="InitialLeaseTime" value="2m" /> <property name="RenewOnCallTime" value="1m" /> </object>
Note the property 'TargetName' is set to the name, not the reference, of the non-singleton declaration of the 'AdvancedCalculator' class.
301
.NET Remoting Alternatively, you can use the remoting schema and declare the CAO object as shown below
<r:caoExporter targetName="prototypeCalculator" infinite="false"> <r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m" /> </r:caoExporter>
If this declaration is unfamiliar to you, please refer to Chapter 13, Aspect Oriented Programming with Spring.NET for more information. The CAO exporter then references with the name 'prototypeCalculatorWeaved' as shown below.
<r:caoExporter targetName="prototypeCalculatorWeaved" infinite="false"> <r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m" /> </r:caoExporter>
This definition corresponds to the exported calculator from the previous section. The property 'RemoteTargetName' identifies the object on the server side. Using this approach the client can obtain an reference though standard DI techniques to a remote object that implements the IAdvancedCalculator interface. (As always, that doesn't mean the client should treat the object as if it was an in-process object). Alternatively, you can use the Remoting schema to shorten this definition and provide intellisense code completion
<r:caoFactory id="calculatorService" remoteTargetName="prototypeCalculator" serviceUrl="tcp://localhost:8005" />
302
.NET Remoting
303
304
And the corresponding object definition for it in the application context config file:
<object id="userManager" type="MyApp.Services.SimpleUserManager"> <property name="UserDao" ref="userDao"/> </object>
Let's say that we want to expose user manager as a serviced component so we can leverage its support for transactions. First we need to export our service using the exporter ServicedComponentExporter as shown below
<object id="MyApp.EnterpriseServices.UserManager" type="Spring.Enterprise.ServicedComponentExporter, Spring.Services"> <property name="TargetName" value="userManager"/> <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>
The exporter defined above will create a composition proxy for our SimpleUserManager class that extends ServicedComponent and delegates method calls to SimpleUserManager instance. It will also adorn the proxy class with a TransactionAtribute and all methods with an AutoCompleteAttribute. The next thing we need to do is configure an exporter for the COM+ application that will host our new component:
<object id="MyComponentExporter" type="Spring.Enterprise.EnterpriseServicesExporter, Spring.Services"> <property name="ApplicationName" value="My COM+ Application"/>
305
This exporter will put all proxy classes for the specified list of components into the specified assembly, sign the assembly, and register 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.
You can then inject this instance of the IUserManager into a client class and use it just like you would use original SimpleUserManager implementation. As you can see, by coding your services as plain CLR objects, against well defined service interfaces, you gain easy pluggability for your service implementation though this configuration, while keeping the core business logic in a technology agnostic POCO, i.e. Plain Old CLR Object.
306
29.2. Server-side
One thing that the Spring.NET team didn't like much is that we had to have all these .asmx files lying around when all said files did was specify which class to instantiate to handle web service requests. Second, the Spring.NET team also wanted to be able to use the Spring.NET IoC container to inject dependencies into our web service instances. Typically, a web service will rely on other objects, service objects for example, so being able to configure which service object implementation to use is very useful. Last, but not least, the Spring.NET team did not like the fact that creating a web service is an implementation task. Most (although not all) services are best implemented as normal classes that use coarse-grained service interfaces, and the decision as to whether a particular service should be exposed as a remote object, web service, or even an enterprise (COM+) component, should only be a matter of configuration, and not implementation. An example using the web service exporter can be found in quickstart example named 'calculator'. More information can be found here 'Web Services example'.
307
Web Services
{ return "Hello World!"; } } }
This is just a standard class that has methods decorated with the WebMethod attribute and (at the class-level) the WebService attribute. Application developers can create this web service within Visual Studio just like any other class. All that one need to do in order to publish this web service is: 1. Register the Spring.Web.Services.WebServiceFactoryHandler as the HTTP handler for *.asmx requests within one's web.config file.
<system.web> <httpHandlers> <add verb="*" path="*.asmx" type="Spring.Web.Services.WebServiceHandlerFactory, Spring.Web"/> </httpHandlers> </system.web>
Of course, one can register any other extension as well, but typically there is no need as Spring.NET's handler factory will behave exactly the same as a standard handler factory if said handler factory cannot find the object definition for the specified service name. In that case the handler factory will simply look for an .asmx file. If you are using IIS7 the following configuration is needed
<system.webServer> <validation validateIntegratedModeConfiguration="false"/> <handlers> <add name="SpringWebServiceSupport" verb="*" path="*.asmx" type="Spring.Web.Services.WebServiceHandlerFactory, Spring.Web"/> </handlers> </system.webServer>
Note that one is not absolutely required to make the web service object definition abstract (via the abstract="true" attribute), but this is a recommended best practice in order to avoid creating an unnecessary instance of the service. Because the .NET infrastructure creates instances of the target service object internally for each request, all Spring.NET needs to provide is the System.Type of the service class, which can be retrieved from the object definition even if it is marked as abstract. That's pretty much it as we can access this web service using the value specified for the name attribute of the object definition as the service name:
http://localhost/MyWebApp/HelloWorld.asmx
308
Web Services throughout the application to configure objects, but that resorted to the service locator approach when dealing with web services. Ideally, one should be able to define a property for the message within one's web service class and have Spring.NET inject the message value into it:
namespace MyApp.Services { public interface IHelloWorld { string HelloWorld(); } [WebService(Namespace="http://myCompany/services")] public class HelloWorldService : IHelloWorld { private string message; public string Message { set { message = value; } } [WebMethod] public string HelloWorld() { return this.message; } } }
The problem with standard Spring.NET DI usage in this case is that Spring.NET does not control the instantiation of the web service. This happens deep in the internals of the .NET framework, thus making it quite difficult to plug in the code that will perform the configuration. The solution is to create a dynamic server-side proxy that will wrap the web service and configure it. That way, the .NET framework gets a reference to a proxy type from Spring.NET and instantiates it. The proxy then asks a Spring.NET application context for the actual web service instance that will process requests. This export the web service explicitly using the Spring.Web.Services.WebServiceExporter class; in the specific case of this example, one must also not forget to configure the Message property for said service:
<object id="HelloWorld" type="MyApp.Services.HelloWorldService, MyApp"> <property name="Message" value="Hello, World!"/> </object> <object id="HelloWorldExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web"> <property name="TargetName" value="HelloWorld"/> </object>
proxying
requires
that
one
The WebServiceExporter copies the existing web service and method attribute values to the proxy implementation (if indeed any are defined). Please note however that existing values can be overridden by setting properties on the WebServiceExporter.
Interface Requirements
In order to support some advanced usage scenarios, such as the ability to expose an AOP proxy as a web service (allowing the addition of AOP advices to web service methods), Spring.NET requires those objects that need to be exported as web services to implement a (service) interface.
309
Web Services Only methods that belong to an interface will be exported by the WebServiceExporter.
Based on the configuration above, Spring.NET will generate a web service proxy for all the interfaces implemented by a target and add attributes as necessary. This accomplishes the same goal while at the same time moving web service metadata from implementation class to configuration, which allows one to export pretty much any class as a web service. The WebServiceExporter also has a TypeAttributes IList property for applying attributes at the type level.
310
Web Services
Note
The attribute to confirms to the WSI basic profile 1.1 is not added by default. This will be added in a future release. In the meantime use the TypeAttributes IList property to add [WebServiceBinding(ConformsTo=WsiProfiles.BasicProfile1_1)] to the generated proxy. One can also export only certain interfaces that a service class implements by setting the Interfaces property of the WebServiceExporter.
That's it as every call to the methods of the exported web service will be intercepted by the target AOP proxy, which in turn will apply the configured debugging and timing advice to it.
311
Web Services
29.3. Client-side
On the client side, the main objection the Spring.NET team has is that client code becomes tied to a proxy class, and not to a service interface. Unless you make the proxy class implement the service interface manually, as described by Juval Lowy in his book "Programming .NET Components", application code will be less flexible and it becomes very difficult to plug in different service implementation in the case when one decides to use a new and improved web service implementation or a local service instead of a web service. The goal for Spring.NET's web services support is to enable the easy generation of client-side proxies that implement a specific service interface.
In order to be able to reference a web service endpoint through this interface, you need to add a definition similar to the example shown below to your client's application context:
<object id="HelloWorld" type="Spring.Web.Services.WebServiceProxyFactory, Spring.Services"> <property name="ProxyType" value="MyCompany.WebServices.HelloWorld, MyClientApp"/> <property name="ServiceInterface" value="MyCompany.Services.IHelloWorld, MyServices"/> </object>
What is important to notice is that the underlying implementation class for the web service does not have to implement the same IHelloWorld service interface... so long as matching methods with compliant signatures exist (a kind of duck typing), Spring.NET will be able to create a proxy and delegate method calls appropriately. If a matching method cannot be found, the Spring.NET infrastructure code will throw an exception. That said, if you control both the client and the server it is probably a good idea to make sure that the web service class on the server implements the service interface, especially if you plan on exporting it using Spring.NET's WebServiceExporter, which requires an interface in order to work.
312
Web Services
<object id="calculatorService" type="Spring.Web.Services.WebServiceProxyFactory, Spring.Services"> <property name="ServiceUri" value="http://myServer/Calculator/calculatorService.asmx"/> <!--<property name="ServiceUri" value="file://~/calculatorService.wsdl"/>--> <property name="ServiceInterface" value="Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract"/> <!-- Dependency injection on Factory's product : the proxy instance of type SoapHttpClientProtocol --> <property name="ProductTemplate"> <object> <property name="Timeout" value="10000" /> <!-- 10s --> </object> </property> </object>
One use-case where this proxy is very useful is when dealing with typed data sets through a web service. Leaving the pros and cons of this approach aside, the current behavior of the proxy generator in .NET is to create wrapper types for the typed dataset. This not only pollutes the solution with extraneous classes but also results in multiple wrapper types being created, one for each web service that uses the typed dataset. This can quickly get confusing. The proxy created by Spring allows you to reference you typed datasets directly, avoiding the above mentioned issues.
also
For an example of how using SOAP headers for authentication using the WebServiceExporter and WebServiceProxyFactory, refer to this solution on our wiki.
313
Note
An alternative implementation approach that uses extensibility points in WCF to delegate to Spring to create and configure your WCF service was tried but proved to be limited in its range of applicability. This approach was first taken (afaik) by Oran Dennison on his blog [http:// orand.blogspot.com/2006/10/wcf-service-dependency-injection.html] and several other folks on the web since then. The issue in using this approach is that if the service is configured to be a singleton, for example using [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] then the invocation of the IInstanceProvider is short-circuited. See the notes on the MSDN class documentation here [http://msdn.microsoft.com/en-us/library/ system.servicemodel.dispatcher.iinstanceprovider.aspx]. While this would be the preferred approach, no acceptable work around was found.
The implementation for the methods is fairly obvious but an additional property, SleepInSeconds, is present. This is the property we will configure via dependency injection. Here is a partial listing of the implementation
public class CalculatorService : ICalculator
314
// }
To configure this object with Spring, provide the XML configuration metadata as shown below as you would with any Spring managed object.
<object id="calculator" singleton="false" type="Spring.WcfQuickStart.CalculatorService, Spring.WcfQuickStart.ServerApp"> <property name="SleepInSeconds" value="1"/> </object>
Note
The object must be declared as a 'prototype' object, i.e. not a singleton, in order to interact correctly with WCF instancing. To application define an instance of a Spring.ServiceModel.Activation.ServiceHostFactoryObject and set is property TargetName to the id value of the previously defined service type. ServiceHostFactoryObject is a Spring IFactoryObject implementation. (See here for more information on IFactoryObjects and their interaction with the container.) The ServiceHostFactoryObject will create an instance of Spring.ServiceModel.Activation.SpringServiceHost that will be the ServiceHost instance associated with your service type. This configuration for this step is shown below.
<object id="calculatorServiceHost" type="Spring.ServiceModel.Activation.ServiceHostFactoryObject, Spring.Services"> <property name="TargetName" value="calculator" /> </object>
host
this
service
type
in
standalone
Additional service configuration can be done declaratively in the standard App.config file as shown below
<system.serviceModel> <services> <service name="calculator" behaviorConfiguration="DefaultBehavior"> <host> ... </host> <endpoint> ... </endpoint> </service> ... </services> </system.serviceModel>
Note
It is important that the name of the service in the WCF declarative configuration section match the name of the Spring object definition
315
Windows Communication Foundation (WCF) is where the dynamic proxy for your service type is generated. This dynamic proxy will implement a single 'WCF' interface, the same on that your service type implements. The implementation of the service interface methods on the proxy will delegate to a wrapped 'target' object which is the object instance retrieved by name from the Spring container using the Spring API, ApplicationContext.GetObject(name). Since the object retrieved in this manner is fully configured, your WCF service is as well.
Spring.ServiceModel.Activation.SpringServiceHost
Outside
can also use the class Spring.ServiceModel.Activation.ServiceHostFactory (which inherits from System.ServiceModel.Activation.ServiceHostFactory) to host your services so that they can be configured via dependency injection. To use the dynamic proxy approached described here you should still refer to the name of the service as the name of the object definition used to configure the service type in the Spring container. There are not many disadvantages to this approach other than the need to specify the service name as the name of the object definition in the Spring container and to ensure that singleton=false is used in the object definition. You can also use Spring.ServiceModel.Activation.ServiceHostFactory to host your service inside IIS but should still refer to the service by the name of the object in the Spring container.
of
standalone
application
you
The aop:config section implicitly uses Spring's autoproxying features to add additional behavior to any objects defined in the container that match the pointcut criteria.
316
Windows Communication Foundation (WCF) The value 'serverAppCalculatorEndpoint' refers to the name of an enpoints in the <client> section of the standard WCF configuration inside of App.config.
Spring does not provide any means to add [DataContract] or [DataMember] attributes to method arguments of your service operations. As such, either you will do that yourself or you may choose to use a serializer other than DataContractSerializer, for example one that relies on method arguments that implement the ISerializable interface, having the [Serializable] attribute, or are serializable via the XmlSerializer. Use the latter serializers is a good way to migrate from an existing RCP based approach, such as using .NET remoting, to WCF in order to take advantage of the WCF runtime and avoid editing much existing code. You can then incrementally refactor and/or create new operations that use DataContractSerializer.
317
Part V. Integration
This part of the reference documentation covers the Spring Framework's integration with a number of related enterprise .NET technologies. Chapter 31, Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS Chapter 32, Message Oriented Middleware - TIBCO EMS Chapter 33, Message Oriented Middleware - MSMQ Chapter 34, Scheduling and Thread Pooling Chapter 35, Template Engine Support
318
Chapter 31. Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS
31.1. Introduction
The goal of Spring's messaging is to increase your productiviity when writing an enterprise strength messaging middleware applications. Spring achieves these goals in several ways. First it provides several helper classes that remove from the developer the incidental complexity and resource management issues that arise when using messaging APIs. Second, the design of these messaging helper classes promote best practices in designing a messaging application by promoting a clear separation between the messaging middleware specific code and business processing that is technology agnostic. This is generally referred to a "plain old CLR object" (or POCO) programming model. This chapter discusses Spring's messaging support for providers whose API was modeled after the Java Message Service (JMS) API. Vendors who provide a JMS inspired API include Apache ActiveMQ, TIBCO, IBM, and Progress Software. If you are using Microsoft's Message Queue, please refer to the specific MSMQ section. The description of Spring messages features in this chapter apply to all of these JMS vendors. However, the documentation focuses on showing code examples that use Apache ActiveMQ. For code examples and some features specific to TIBCO EMS please refer to this chapter.
319
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS management of intermediate API objects Spring's messaging support, both in Java and .NET, addresses the errorprone boiler plate coding style one needs when using these APIs. The design principle common to Spring template classes is to provide helper methods to perform common operations and for more sophisticated usage, delegate the essence of the processing task to user implemented callback interfaces. The messaging template follows the same design. The message template class offer various convenience methods for the sending of messages, consuming a message synchronously, and exposing the message Session and MessageProducer to the user. The namespace Spring.Messaging.<VendorAcronym>.Support.Converter provides a IMessageConverter abstraction to convert between .NET objects and messages. The namespace Spring.Messaging.<VendorAcronym>.Support.Destinations provides various strategies for managing destinations, such as providing a service locater for destinations stored in a directory service. Finally, the namespace Spring.Messaging.<VendorAcronym>.Connections provides an implementations of the ConnectionFactory suitable for use in standalone applications. The rest of the sections in this chapter discusses each of the major helper classes in detail. Please refer to the sample application that ships with Spring for additional hands-on usage.
Note
To simplify documenting features that are common across all provider implementations of Spring's helper classes a specific provider, Apache ActiveMQ, was selected. As such when you see 'NmsTemplate' in the documentation, it also refers to EmsTemplate, XmsTemplate, etc. unless specifically documented otherwise. The provider specific API classes are typically named after their JMS counterparts with the possible exception of a leading 'I' in front of interfaces in order to follow .NET naming conventions. In the documentation these API artifacts are referred to as 'ConnectionFactory', 'Session', 'Message', etc. without the leading 'I'.
Note
To view some of this chapters contents that are based on TIBCO EMS please refer to the TIBCO EMS chapter.
320
321
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS Provider messaging APIs typically expose two types of send methods, one that takes delivery mode, priority, and time-to-live as quality of service (QOS) parameters and one that takes no QOS parameters which uses default values. Since there are many higher level send methods in NmsTemplate, the setting of the QOS parameters have been exposed as properties on the template class to avoid duplication in the number of send methods. Similarly, the timeout value for synchronous receive calls is set using the property ReceiveTimeout.
Note
Instances of the NmsTemplate class are thread-safe once configured. This is important because it means that you can configure a single instance of a NmsTemplate and then safely inject this shared reference into multiple collaborators. To be clear, the NmsTemplate is stateful, in that it maintains a reference to a ConnectionFactory, but this state is not conversational state.
31.2.2. Connections
The NmsTemplate requires a reference to a ConnectionFactory. The ConnectionFactory serves as the entry point for working with the provider's messaging API. It is used by the client application as a factory to create connections to the messaging server and encapsulates various configuration parameters, many of which are vendor specific such as SSL configuration options. To create a ActivfeMQ ConnectionFactory define can create an object definition as shown
<object id="nmsConnectionFactory" type="Apache.NMS.ActiveMQ.ConnectionFactory, Apache.NMS.ActiveMQ"> <constructor-arg index="0" value="tcp://localhost:61616"/> </object>
EmsTemplate also requres a reference to a ConnectionFactory, however, it is not the 'native' TIBCO.EMS.ConnectionFactory. Instead the connection factory type is Spring.Messaging.Ems.Common.IConnectionFactory. See the documentation for TIBCO EMS supper for more information here.
Between the ConnectionFactory and the Send operation there are three intermediate objects that are created and destroyed. To optimise the resource usage and increase performance two implementations of IConnectionFactory are provided. 31.2.3.1. SingleConnectionFactory
Spring.Messaging.Nms.Connections.SingleConnectionFactory
to CreateConnection and ignore calls to Close. 31.2.3.2. CachingConnectionFactory extends the functionality SingleConnectionFactory and adds the caching of Sessions, MessageProducers, and MessageConsumers.
Spring.Messaging.Nms.Connections.CachingConnectionFactory
of
The initial cache size is set to 1, use the property SessionCacheSize to increase the number of cached sessions. Note that the number of actual cached sessions will be more than that number as sessions are cached based on their
322
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS acknowledgment mode, so there can be up to 4 cached session instances when SessionCacheSize is set to one, one for each AcknowledgementMode. MessageProducers and MessageConsumers are cached within their owning session and also take into account the unique properties of the producers and consumers when caching.
MessageProducers are cached based on their destination. MessageConsumers are cached based on a key composed
of the destination, selector, noLocal delivery flag, and the durable subscription name (if creating durable consumers). Here is an example configuration
<object id="connectionFactory" type="Spring.Messaging.Nms.Connections.CachingConnectionFactory, Spring.Messaging.Nms"> <property name="SessionCacheSize" value="10" /> <property name="TargetConnectionFactory"> <object type="Apache.NMS.ActiveMQ.ConnectionFactory, Apache.NMS.ActiveMQ"> <constructor-arg index="0" value="tcp://localhost:61616"/> </object> </property> </object>
323
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS However, this property does influence the behavior of dynamic destination resolution via implementations of the IDestinationResolver interface. You can also configure the NmsTemplate with a default destination via the property DefaultDestination. The default destination will be used with send and receive operations that do not refer to a specific destination.
324
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS All methods take as an argument an instance of IMessageCreator which defines the API contract for you to create the JMS message. The interface is show below
public interface IMessageCreator { IMessage CreateMessage(ISession session); }
Intermediate Sessions and MessageProducers needed to send the message are managed by NmsTemplate. The session passed in to the method is never null. There is a similar set methods that use a delegate instead of the interface, which can be convenient when writing small implementation in .NET 2.0 using anonymous delegates. Larger, more complex implementations of the method 'CreateMessage' are better suited to an interface based implementation. void
SendWithDelegate(IDestination destination, MessageCreatorDelegate
messageCreatorDelegate)
void SendWithDelegate(string destinationName, MessageCreatorDelegate messageCreatorDelegate) void SendWithDelegate(MessageCreatorDelegate messageCreatorDelegate) The declaration of the delegate is
public delegate IMessage MessageCreatorDelegate(ISession session);
The following class shows how to use the SendWithDelegate method with an anonymous delegate to create a MapMessage from the supplied Session object. The use of the anonymous delegate allows for very terse syntax and easy access to local variables. The NmsTemplate is constructed by passing a reference to a ConnectionFactory.
public class SimplePublisher { private NmsTemplate template; public SimplePublisher() { template = new NmsTemplate(new ConnectionFactory("tcp://localhost:61616")); } public void Publish(string ticker, double price) { template.SendWithDelegate("APP.STOCK.MARKETDATA", delegate(ISession session) { IMapMessage message = session.CreateMapMessage(); message.Body.SetString("TICKER", ticker); message.Body.SetDouble("PRICE", price); message.NMSPriority = MsgPriority.Low; return message; }); } }
A zero argument constructor and ConnectionFactory property are also provided. Alternatively consider deriving from Spring's NmsGatewaySupport convenience base class which provides a ConnectionFactory property that will instantiate a NmsTemplate instance that is made available via the property NmsTemplate.
325
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS interface. This interface defines a simple contract to convert between .NET objects and JMS messages. The default implementation SimpleMessageConverter supports conversion between String and TextMessage, byte[] and BytesMesssage, and System.Collections.IDictionary and MapMessage. By using the converter, you and your application code can focus on the business object that is being sent or received via messaging and not be concerned with the details of how it is represented as a JMS message. There is also an XmlMessageConverter that converts objects to an XML string and vice-versa for sending via a TextMessage. Please refer to the API documentation and example application for more information on configuring an XmlMessageConverter. The family of ConvertAndSend messages are similar to that of the Send method with the additional argument of type IMessagePostProcessor. These methods are listed below. void ConvertAndSend(object message) void ConvertAndSend(object message, IMessagePostProcessor postProcessor) void ConvertAndSend(string destinationName, object message) void
ConvertAndSend(string destinationName, object message, IMessagePostProcessor
postProcessor);
postProcessor)
The example below uses the default message converter to send a Hashtable as a message to the destination "APP.STOCK".
public void PublishUsingDict(string ticker, double price) { IDictionary marketData = new Hashtable(); marketData.Add("TICKER", ticker); marketData.Add("PRICE", price); template.ConvertAndSend("APP.STOCK.MARKETDATA", marketData); }
To accommodate the setting of message's properties, headers, and body that can not be generally encapsulated inside a converter class, the IMessageConverterPostProcessor interface gives you access to the message after it has been converted but before it is sent. The example below demonstrates how to modify a message header and a property after a Hashtable is converted to a message using the IMessagePostProcessor. The methods ConvertAndSendUsingDelegate allow for the use of a delegate to perform message post processing. This family of methods is listed below void ConvertAndSendWithDelegate(object message, MessagePostProcessorDelegate postProcessor) void
ConvertAndSendWithDelegate(IDestination destination, object message,
MessagePostProcessorDelegate postProcessor)
void
ConvertAndSendWithDelegate(string
destinationName,
object
message,
MessagePostProcessorDelegate postProcessor)
326
and
public interface ISessionCallback { object DoInNms(ISession session); }
The delegate signatures are listed below and mirror the interface method signature
public delegate object SessionDelegate(ISession session); public delegate object ProducerDelegate(ISession session, IMessageProducer producer);
327
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS public Message Receive() public Message Receive(Destination destination) public Message Receive(string destinationName) public Message ReceiveSelected(string messageSelector) public Message ReceiveSelected(string destinationName, string messageSelector) public Message ReceiveSelected(Destination destination, string messageSelector) The Receive method without arguments will use the DefaultDestination. The ReceiveSelected methods apply the provided message selector string to the MessageConsumer that is created. The ReceiveAndConvert methods apply the template's message converter when receiving a message. The message converter to use is set using the property MessageConverter and is the SimpleMessageConverter implementation by default. These methods are listed below. public object ReceiveAndConvert() public object ReceiveAndConvert(Destination destination) public object ReceiveAndConvert(string destinationName) public object ReceiveSelectedAndConvert(string messageSelector) public object ReceiveSelectedAndConvert(string destinationName, string messageSelector) public object ReceiveSelectedAndConvert(Destination destination, string messageSelector)
Other vendors may provide a delegate based version of this callback or even both a delegate and interface options. Apache ActiveMQ supports the use of delegates for message reception callbacks. As a programming convenience in Spring.Messaging.Nms.Core is an interface IMessageListener that can be used with NMS. Below is a simple implementation of the IMessageListener interface that processes a message.
using Spring.Messaging.Nms.Core; using Apache.NMS; using Common.Logging; namespace MyApp { public class SimpleMessageListener : IMessageListener { private static readonly ILog LOG = LogManager.GetLogger(typeof(SimpleMessageListener)); private int messageCount; public int MessageCount
328
Once you've implemented your message listener, it's time to create a message listener container. You register you listener with a message listener container that specifies various messaging configuration parameters, such as the ConnectionFactory, and the number of concurrent consumers to create. There is an abstract base class for message listener containers, AbstractMessageListenerContainer, and one concrete implementation, SimpleMessageListenerContainer. SimpleMessageListenerContainer creates a fixed number of JMS Sessions/MessageConsumer pairs as set by the property ConcurrentConsumers. The default value of ConcurrentConsumers is one. Here is a sample configuration that uses the the custom schema provided in Spring.NET to more reasily configure MessageListenerContainers.
<objects xmlns="http://www.springframework.net" xmlns:nms="http://www.springframework.net/nms"> <object id="ActiveMqConnectionFactory" type="Apache.NMS.ActiveMQ.ConnectionFactory, Apache.NMS.ActiveMQ"> <constructor-arg index="0" value="tcp://localhost:61616"/> </object> <object id="ConnectionFactory" type="Spring.Messaging.Nms.Connections.CachingConnectionFactory, Spring.Messaging.Nms"> <constructor-arg index=0" ref="ActiveMqConnectionFactory"/> <property name="SessionCacheSize" value="10"/> </object> <object id="MyMessageListener" type="MyApp.SimpleMessageListener, MyApp"/> <nms:listener-container connection-factory="ConnectionFactory" concurrency="10"> <nms:listener ref="MyMessageListener" destination="APP.STOCK.REQUEST" /> </nms:listener-container> </objects>
The above configuration will create 10 threads that process messages off of the queue named "APP.STOCK.REQUEST". The threads are those owned by the messaging provider as a result of creating a MessageConsumer. Other important properties are ClientID, used to set the ClientID of the Connection and MessageSelector to specify the 'sql-like' message selector string. Durable subscriptions are supported via the properties SubscriptionDurable and DurableSubscriptionName. You may also register an exception listener using the property ExceptionListener. Exceptions that are thrown during message processing can be passed to an implementation of IExceptionHandler and registered with the container via the property ExceptionListener. The registered IExceptionHandler will be invoked if the exception is of the type NMSException (or the equivalent root exception type for other providers). The SimpleMessageListenerContainer will logs the exception at error level and not propagate the exception to the provider. All handling of acknowledgement and/or transactions is done by the listener container. You can override the method HandleListenerException to change this behavior.
329
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS Please refer to the Spring SDK documentation for additional description of the features and properties of SimpleMessageListenerContainer.
You can also choose to implement this interface and register it with the message listener container
31.5.4. MessageListenerAdapater
The MessageListenerAdapter class is the final component in Spring's asynchronous messaging support: in a nutshell, it allows you to expose almost any class to be invoked as a messaging callback (there are of course some constraints). Consider the following interface definition. Notice that although the interface extends neither the IMessageListener nor ISessionAwareMessageListener interfaces, it can still be used as a Message-Driven POCOs (MDP) via the use of the MessageListenerAdapter class. Notice also how the various message handling methods are strongly typed according to the contents of the various Message types that they can receive and handle.
public interface MessageHandler { void HandleMessage(string message); void HandleMessage(Hashtable message); void HandleMessage(byte[] message); }
In particular, note how the above implementation of the IMessageHandler interface (the above DefaultMessageHandler class) has no messaging provider API dependencies at all. It truly is a POCO that we will make into an MDP via the following configuration.
<object id="MessagleHandler" type="MyApp.DefaultMessageHandler, MyApp"/> <object id="MessageListenerAdapter" type="Spring.Messaging.Nms.Listener.Adapter.MessageListenerAdapter, Spring.Messaging.Nms"> <property name="HandlerObject" ref="MessagleHandler"/> </object> <object id="MessageListenerContainer" type="Spring.Messaging.Nms.Listener.SimpleMessageListenerContainer, Spring.Messaging.Nms"> <property name="ConnectionFactory" ref="ConnectionFactory"/> <property name="DestinationName" value="APP.REQUEST"/> <property name="MessageListener" ref="MessageListenerAdapter"/> </object>
330
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS The previous examples relies on the fact that the default IMessageConverter implementation of the MessageListenerAdapter is SimpleMessageConverter that can convert from messages to strings, byte[], and hashtables and object from a ITextMessage, IBytesMessage, IMapMessage, and IObjectMessage respectfully. Below is an example of another MDP that can only handle the receiving of NMS ITextMessage messages. Notice how the message handling method is actually called 'Receive' (the name of the message handling method in a MessageListenerAdapter defaults to 'HandleMessage'), but it is configurable (as you will see below). Notice also how the 'Receive(..)' method is strongly typed to receive and respond only to NMS ITextMessage messages.
public interface TextMessageHandler { void Receive(ITextMessage message); }
Please note that if the above 'MessageListener' receives a Message of a type other than ITextMessage, a ListenerExecutionFailedException will be thrown (and subsequently handled by the container by logging the exception). If your IMessageConverter implementation will return multiple object types, overloading the handler method is perfectly acceptable, the most specific matching method will be used. A method with an object signature would be consider a 'catch-all' method of last resort. For example, you can have an handler interface as shown below.
public interface IMyHandler { void DoWork(string text); void DoWork(OrderRequest orderRequest); void DoWork(InvoiceRequest invoiceRequest); void DoWork(object obj); }
Another of the capabilities of the MessageListenerAdapter class is the ability to automatically send back a response Message if a handler method returns a non-void value. The adapter's message converter will be used to convert the methods return value to a message. The resulting message will then be sent to the Destination defined in the JMS Reply-To property of the original Message (if one exists) , or the default Destination set on the MessageListenerAdapter (if one has been configured). If no Destination is found then an InvalidDestinationException will be thrown (and please note that this exception will not be swallowed and will propagate up the call stack). An interface that is typical when used with a message converter that supports multiple object types and has return values is shown below.
public interface IMyHandler
331
The example above is equivalent to creating two distinct listener container object definitions and two distinct MessageListenerAdapter object definitions as demonstrated in the section entitled Section 31.5.4, MessageListenerAdapater. In addition to the attributes shown above, the listener element may contain several optional ones. The following table describes all available attributes: Table 31.1. Attributes of the NMS <listener> element Attribute id Description A object name for the hosting listener container. If not specified, a object name will be automatically generated. The destination name for this listener, resolved through the IDestinationResolver strategy. The object name of the handler object. The name of the handler method to invoke. If the ref points to a IMessageListener or Spring ISessionAwareMessageListener, this attribute may be omitted.
destination (required)
332
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS Attribute response-destination Description The name of the default response destination to send response messages to. This will be applied in case of a request message that does not carry a "NMSReplyTo" field. The type of this destination will be determined by the listener-container's "destination-type" attribute. Note: This only applies to a listener method with a return value, for which each result object will be converted into a response message. The name of the durable subscription, if any. An optional message selector for this listener. An optional boolean value. Set to true for the publish-subscribe domain (Topics) or false (the default) for point-to-point domain (Queues). This is useful when using the default implementation for destination resolvers.
The <listener-container/> element also accepts several optional attributes. This allows for customization of the various strategies (for example, DestinationResolver) as well as basic messaging settings and resource references. Using these attributes, it is possible to define highly-customized listener containers while still benefiting from the convenience of the namespace.
<nms:listener-container connection-factory="MyConnectionFactory" destination-resolver="MyDestinationResolver" concurrency="10"> <nms:listener destination="queue.orders" ref="OrderService" method="PlaceOrder"/> <nms:listener destination="queue.confirmations" ref="ConfirmationLogger" method="Log"/> </nms:listener-container>
The following table describes all available attributes. Consult the class-level SDK documentation of the AbstractMessageListenerContainer and its subclass SimpleMessageListenerContainer for more detail on the individual properties. Table 31.2. Attributes of the NMS <listener-container> element Attribute connection-factory Description A reference to the NMS ConnectionFactory object (the default object name is 'ConnectionFactory'). A reference to the IDestinationResolver strategy for resolving JMS Destinations. A reference to the IMessageConverter strategy for converting NMS Messages to listener method arguments. Default is a SimpleMessageConverter. The NMS destination type for this listener: queue, topic or durableTopic. The default is queue. The NMS client id for this listener container. Needs to be specified when using durable subscriptions. The native NMS acknowledge mode: auto, client, dups-ok or transacted. A value of transacted activates a locally transacted
destination-resolver
message-converter
destination-type
client-id
acknowledge
333
Message Oriented Middleware - Apache ActiveMQ and TIBCO EMS Attribute Description
Session.
As an alternative, specify the transaction-manager attribute described below. Default is auto. concurrency The number of concurrent sessions/consumers to start for each listener. Default is 1; keep concurrency limited to 1 in case of a topic listener or if queue ordering is important; consider raising it for general queues. The time interval between connection recovery attempts. The default is 5 seconds. Specify as a TimeSpan value using Spring's TimeSpanConverter (e.g. 10s, 10m, 3h, etc) The maximum time try reconnection attempts. The default is 10 minutes. Specify as a TimeSpan value using Spring's TimeSpanConverter (e.g. 10s, 10m, 3h, etc) Set whether to automatically start the listeners after initialization. Default is true, optionally set to false. A reference to a IErrorHandler that will handle any uncaught exceptions other than those of the type NMSException (in the case of ActiveMQ or EMSException int he case of TIBCO EMS) that may occur during the execution of the message listener. By default no ErrorHandler is registered and that error-level logging is the default behavior. A reference to an Spring.Messaging.Nms.Core.IExceptionListener or TIBCO.EMS.IExceptionListener as appropriate. Is invokved in case of a NMSException or EMSException.
recovery-interval
max-recovery-time
auto-startup
error-handler
exception-listener
334
Note
A complete sample application using Spring's EMS integration classes is in the distribution under the directory examples\Spring\Spring.EmsQuickStart. Documentation for the Quickstart is available here.
335
32.3.2. Connections
To create a Spring.Messaging.Ems.Common.ConnectionFactory use the following object definition
<object id="emsConnectionFactory" type="Spring.Messaging.Ems.Common.EmsConnectionFactory, Spring.Messaging.Ems"> <constructor-arg name="serverUrl" value="tcp://localhost:7222"/> <constructor-arg name="clientId" value="SpringEMSClient"/> <property name="ConnAttemptCount" value="10" /> <property name="ConnAttemptDelay" value="100" /> <property name="ConnAttemptTimeout" value="1000" /> </object>
Please refer to the API documentation for other properties you way want to set, in particular for those relating to SSL.
32.3.3.1. SingleConnectionFactory
Spring.Messaging.Ems.Connections.SingleConnectionFactory
to CreateConnection and ignore calls to Close. You can configure a SingleConnectionFactory as you would an EmsConnectionFactory. 32.3.3.2. CachingConnectionFactory extends the functionality of SingleConnectionFactory and adds the caching of Sessions, MessageProducers, and MessageConsumers. See the documentation for ActiveMQ CachingConnectionFactory for some additional information here.
Spring.Messaging.Ems.Connections.CachingConnectionFactory
Notice that the property TargetConnectionFactory refers to 'emsConnectionFactory' defined in the previous section. This connection factory implementation also set the ReconnectOnException property to true by default allowing for automatic recovery of the underlying Connection.
336
Note
The CachingConnectionFactory requires explicit closing of all Sessions obtained from its shared Connection. This is the usual recommendation for native EMS access code anyway and Spring EMS code follows this recommendation. However, with the CachingConnectionFactory, its use is mandatory in order to actually allow for Session reuse.
Note
MessageConsumers obtained from a cached Session won't get closed until the Session will eventually be removed from the pool. This may lead to semantic side effects in some cases. For a durable subscriber, the logical Session.Close() call will also close the subscription. Re-registering a durable consumer for the same subscription on the same Session handle is not supported; close and reobtain a cached Session first. To avoid accidentally referring to the ConnectionFactory that does not support caching, (emsConnectionFactory), you should use an inner object definition as shown below.
<object id="connectionFactory" type="Spring.Messaging.Ems.Connections.CachingConnectionFactory, Spring.Messaging.Ems"> <property name="SessionCacheSize" value="10" /> <property name="TargetConnectionFactory"> <object type="Spring.Messaging.Ems.Common.EmsConnectionFactory, Spring.Messaging.Ems"> <constructor-arg name="serverUrl" value="tcp://localhost:7222"/> <constructor-arg name="clientId" value="SpringEMSClient"/> <property name="ConnAttemptCount" value="10" /> <property name="ConnAttemptDelay" value="100" /> <property name="ConnAttemptTimeout" value="1000" /> </object> </property> </object>
JndiLookupFactory object implements the IConfigurableFactoryObject interface, so the type that is associated with the name 'jndiConnectionFactory' is not JndiLookupFactoryObject, but the type returned from this factory's
337
Message Oriented Middleware - TIBCO EMS 'GetType' method, in this case the type of what was retrieved from JNDI. The IConfigurableFactoryObject interface also allows for the object that was returned to be dependency injected. Please refer to the documentation on IConfigurableFactoryObject for more information.
Note
The dictionary JndiProperties is set using Spring Expression language syntax for the property name. This provides a shortcut to the more verbose <dictionary/> element. To enable this functionality a the TIBCO.EMS.LookupContext was registered under the name 'LookupContext' in Spring's TypeRegistry. The use of this object retrieved from JNDI to configure Spring's CachingConnectionFactory set the property TargetConnectionFactory as shown below
<object id="cachingJndiConnectionFactory" type="Spring.Messaging.Ems.Connections.CachingConnectionFactory, Spring.Messaging.Ems"> <property name="SessionCacheSize" value="10" /> <property name="TargetConnectionFactory"> <object type="Spring.Messaging.Ems.Common.EmsConnectionFactory, Spring.Messaging.Ems"> <constructor-arg ref="jndiEmsConnectionFactory"/> </object> </property> </object>
Other useful properties and features of JndiLookupFactoryObject are JndiContextType : This is an enumeration that can have either the value JMS or LDAP. These translate to configuring JNDI context with the constants LookupContextFactory.TIBJMS_NAMING_CONT or LookupContextFactory.LDAP_CONTEXT for use with EMS's own JNDI registry or an LDAP directory respectively. The default is set use LookupContextFactory.TIBJMS_NAMING_CONT. The type JndiContextType is also registered in Spring's TypeRegistry so that you can use a SpEL expression to set the value as shown below.
<object id="jndiEmsConnectionFactory" type="Spring.Messaging.Ems.Jndi.JndiLookupFactoryObject, Spring.Messaging.Ems"> <property name="JndiName" value="TopicConnectionFactory"/> <property name="JndiProperties[LookupContext.PROVIDER_URL]" value="tibjmsnaming://localhost:7222"/> <property name="JndiContextType" expression="JndiContextType.JMS"/> <property name="ExpectedType" value="TIBCO.EMS.ConnectionFactory"/> </object>
Note
The TargetConnectionFactory is of the Spring wrapper type Spring.Messaging.Ems.Common.IConnectionFactory. You can pass into Spring's implementation of that interface, Spring.Messaging.Ems.Common.EmsConnectionFactory, the 'raw' TIBCO EMS type, TIBCO.EMS.ConnectionFactory. ExpectedType: This is a property of the type System.Type. You can set the type that the located JNDI object is supposed to be assignable to, if any. It's use is shown in the previous XML configuraiton listing. JndiLookupContext: This is a property of the type TIBCO.EMS.ILookupContext. If you create a custom implementation of ILookupContext (for example one that performs lazy caching), assign this property instead of configuring the property JndiContextType. DefaultObject: Sets a reference to an instance of an object to fall back to if the JNDI lookup fails. The default is not to have a fallback object.
338
32.3.6. MessageListenerContainers
Spring's MessageListenerContainer's are used to process messages asynchronously and concurrently. MessageListenerContainers are described more in this section.
A more DI friendly implementation would be to expose a EmsTemplate property or to inherit from Spring's EmsGatewaySupport base class which provides a IConnectionFactory property that will instantiate a EmsTemplate instance that is made available via the property EmsTemplate.
using Spring.Messaging.Ems.Common; using TIBCO.EMS; namespace Spring.Messaging.Ems.Core {
339
340
And the configuration to create 10 threads that process message off the queue named "APP.STOCK.REQUEST". See this section for more details about the message listener container.
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ems="http://www.springframework.net/ems"> <object id="connectionFactory" type="Spring.Messaging.Ems.Connections.CachingConnectionFactory, Spring.Messaging.Ems"> <property name="SessionCacheSize" value="10" /> <property name="TargetConnectionFactory"> <object type="Spring.Messaging.Ems.Common.EmsConnectionFactory, Spring.Messaging.Ems"> <constructor-arg name="serverUrl" value="tcp://localhost:7222"/> <constructor-arg name="clientId" value="SpringEMSClient"/> </object> </property> </object> <object name="simpleMessageListener" type="Spring.Messaging.Ems.Core.SimpleMessageListener, Spring.Messaging.Ems.Integration.Tests"/>
341
32.6.4. MessageListenerAdapter
Refer to this section for more information on this feature and change code/XML references of 'Nms' to 'Ems'.
342
343
Message Oriented Middleware - MSMQ how to use MessageQueueTemplate. In both cases you will quite likely want to take advantage of using MessageListenerConverters so you can better structure the translation from the System.Messaging.Message data structure to your business objects. After the initial learning hurdle, you should find that you will be much more productive leveraging Spring's helper classes to write enterprise MSMQ applications than rolling your own infrastructure. Feedback and new feature requests are always welcome. The Spring.MsmqQuickstart application located in the examples directory of the distribution shows this functionality in action.
The MessageQueue object is created via an instance of MessageQueueFactoryObject and the MessageQueueTemplate refers to this factory object by name and not by reference. The SimpleSender class looks like this
public class QuestionService : IQuestionService { private MessageQueueTemplate messageQueueTemplate; public MessageQueueTemplate { get { return messageQueueTemplate; } set { messageQueueTemplate = value; } } public void SendQuestion(string question) { MessageQueueTemplate.ConvertAndSend(question); } }
This class can be shared across multiple threads and the MessageQueueTemplate will take care of managing thread local access to a System.Messaging.MessageQueue as well as any System.Messaging.IMessageFormatter instances. Furthermore, since this is a transactional queue (only the name gives it away), the message will be sent using a single local messaging transaction. The conversion from the string to the underling message is managed by an instance of the IMessageConverter class. By default an implementation that uses an XmlMessageFormatter
344
Message Oriented Middleware - MSMQ with a TargetType of System.String is used. You can configure the MessageQueueTemplate to use other IMessageConveter implementations that do conversions above and beyond what the 'stock' IMessageFormatters do. See the section on MessageConverters for more details. On the receiving side we would like to consume the messages transactionally from the queue. Since no other database operations are being performed in our server side processing, we select the TransactionMessageListenerContainer and configure it to use the MessageQueueTransactionManager. The MessageQueueTransactionManager an implementation of Spring's IPlatformTransactionManager abstraction that provides a uniform API on top of various transaction manager (ADO.NET,NHibernate, MSMQ, etc). Spring's MessageQueueTransactionManager is responsible for createing, committing, and rolling back a MSMQ MessageQueueTransaction. While you can create the message listener container programmatically, we will show the declarative configuration approach below
<!-- Queue to receive from --> <object id='questionTxQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'> <property name='Path' value='.\Private$\questionTxQueue'/> <property name='MessageReadPropertyFilterSetAll' value='true'/> </object> <!-- MSMQ Transaction Manager --> <object id="messageQueueTransactionManager" type="Spring.Messaging.Core.MessageQueueTransactionManager, Spring.Messaging"/> <!-- Message Listener Container that uses MSMQ transactional for receives --> <object id="transactionalMessageListenerContainer" type="Spring.Messaging.Listener.TransactionalMessageListenerContainer, Spring.Messaging"> <property name="MessageQueueObjectName" value="questionTxQueue"/> <property name="PlatformTransactionManager" ref="messageQueueTransactionManager"/> <property name="MaxConcurrentListeners" value="10"/> <property name="MessageListener" ref="messageListenerAdapter"/> </object> <!-- Adapter to call a POCO as a messaging callback --> <object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging"> <property name="HandlerObject" ref="questionHandler"/> </object> <!-- The POCO class that you write --> <object id="questionHandler" type="MyNamespace.QuestionHandler, MyAssembly"/>
We have specified the queue to listen, that we want to consume the messages transactionally, process messages from the queue using 10 threads, and that our plain object that will handle the business processing is of the type QuestionHandler. The only class you need to write, QuestionHandler, looks like
public class QuestionHandler : IQuestionHandler { public void HandleObject(string question) { // perform message processing here Console.WriteLine("Received question: " + question); // use an instance of MessageQueueTemplate and have other MSQM send operations // partake in the same local message transaction used to receive } }
That is general idea. You write the sender class using MessageQueueTemplate and the consumer class which does not refer to any messaging specific class. The rest is configuration of Spring provided helper classes.
345
Message Oriented Middleware - MSMQ Note that if the HandleObject method has returned a string value a reply message would be sent to a response queue. The response queue would be taken from the Message's own ResponseQueue property or can be specified explicitly using MessageListenerAdapter's DefaultResponseQueueName property. If an exception is thrown inside the QuestionHandler, then the MSMQ transaction is rolled back, putting the message back on the queue for redelivery. If the exception is not due to a transient error in the system, but a logical processing exception, then one would get endless redelivery of the message - clearly not a desirable situation. These messages are so called 'poison messages' and a strategy needs to be developed to deal with them. This is left as a development task if you when using the System.Messaging APIs but Spring provides a strategy for handling poison messages, both for DTC based message reception as well as for local messaging transactions. In the last part this 'quick tour' we will configure the message listener container to handle poison messages. This is done by creating an instance of SendToQueueExceptionHandler and setting the property MaxRetry to be the number of exceptions or retry attempts we are willing to tolerate before taking corrective actions. In this case, the corrective action is to send the message to another queue. We can then create other message listener containers to read from those queues and handle the messages appropriately or perhaps you will avoid automated processing of these messages and take manual corrective actions.
<!-- The 'error' queue to send poison messages --> <object id='errorQuestionTxQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'> <property name='Path' value='.\Private$\errorQuestionTxQueue'/> <property name='MessageReadPropertyFilterSetAll' value='true'/> </object> <!-- Message Listener Container that uses MSMQ transactional for receives --> <object id="transactionalMessageListenerContainer" type="Spring.Messaging.Listener.TransactionalMessageListenerContainer, Spring.Messaging"> <!-- as before but adding --> <property name="MessageTransactionExceptionHandler" ref="messageTransactionExceptionHandler"/> </object> <!-- Poison message handling policy --> <object id="messageTransactionExceptionHandler" type="Spring.Messaging.Listener.SendToQueueExceptionHandler, Spring.Messaging"> <property name="MaxRetry" value="5"/> <property name="MessageQueueObjectName" value="errorQuestionTxQueue"/> </object>
In the event of an exception while processing the message, the message transaction will be rolled back (putting the message back on the queue questionTxQueue for redelivery). If the same message causes an exception in processing 5 times ,then it will be sent transactionally to the errorQuestionTxQueue and the message transaction will commit (removing it from the queue questionTxQueue). You can also specify that certain exceptions should commit the transaction (remove from the queue) but this is not shown here ,see below for more informatio non this functionality The SendToQueueExceptionHandler implements the interface IMessageTransactionExceptionHandler (discussed below) so you can write your own implementations should the provided ones not meet your needs. That's the quick tour folks. Hopefully you got a general feel for how things work, what requires configuration, and what is the code you need to write. The following sections describe each of Spring's helper classes in more detail. The sample application that ships with Spring is also a good place to get started.
346
The transactional settings of the underlying overloaded System.Messaging.MessageQueue Send method that are used are based on the following algorithm. 1. If the message queue is transactional and there is an ambient MessageQueueTransaction in thread local storage (put there via the use of Spring's MessageQueueTransactionManager or TransactionalMessageListenerContainer), the message will be sent transactionally using the MessageQueueTransaction object in thread local storage.
Note
This lets you group together multiple messaging operations within the same transaction without having to explicitly pass around the MessageQueueTransaction object. 2. f the message queue is transactional but there is no ambient MessageQueueTransaction, then a single message transaction is created on each messaging operation. (MessageQueueTransactionType = Single).
347
Message Oriented Middleware - MSMQ 3. If there is an ambient System.Transactions transaction then that transaction will be used (MessageQueueTransactionType = Automatic). 4. If the queue is not transactional, then a non-transactional send (MessageQueueTransactionType = None) is used. The delegate MessagePostProcessorDelegate has the following signature
public delegate Message MessagePostProcessorDelegate(Message message);
This lets you modify the message after it has been converted from and object to a message using the IMessageConverter but before it is sent. This is useful for setting Message properties (e.g. CorrelationId, AppSpecific, TimeToReachQueue). Using anonymous delegates in .NET 2.0 makes this a very succinct coding task. If you have elaborate properties that need to be set, perhaps creating a custom IMessageConverter would be appropriate. Overloaded Send and Receive operations that use the algorithm listed above to set transactional delivery options are also available. These are listed below
Message Receive(); Message Receive(string messageQueueObjectName); void Send(Message message); void Send(string messageQueueObjectName, Message message); void Send(MessageQueue messageQueue, Message message);
Note that in the last Send method that takes a MessageQueue instance, it is the callers responsibility to ensure that this instance is not accessed from multiple threads. This Send method is commonly used when getting the MessageQueue from the ResponseQueue property of a Message during an asynchronous receive process. The receive timeout of the Receive operations is set using the ReceiveTimeout property of MessageQueueTemplate. The default value is MessageQueue.InfiniteTimeout (which is actually ~3 months). The XML configuration snippit for defining a MessageQueueTemplate is shown in the previous section and also is located in the MSMQ quickstart application configuraiton file Messaging.xml
33.3.2. MessageQueueFactoryObject
The MessageQueueFactoryObject is responsible for creating MessageQueue instances. You configure the factory with some basic information, namely the constructor parameters you are familiar with already when creating a standard MessageQueue instance, and then setting MessageQueue properties, such a Label etc. Some configuration tasks of a MessageQueue involve calling methods, for example to set which properties of the message to read. These available as properties to set on the MessageQueueFactoryObject. An example declarative configuration is shown below
<object id='testqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'> <!-- propeties passed to the MessageQueue constructor --> <property name='Path' value='.\Private$\testqueue'/> <property name='DenySharedReceive' value='true'/> <property name='AccessMode' value='Receive'/> <property name='EnableCache' value='true'/> <!-- properties that call configuration methods on the MessageQueue --> <property name='MessageReadPropertyFilterSetAll' value='true'/> <property name='ProductTemplate'> <object> <property name='Label' value='MyLabel'/> <!-- other MessageQueue properties can be set here -->
348
Whenever an object reference is made to 'testqueue' an new instance of the MessageQueue class is created. This Spring's so-called 'prototype' model, which differs from 'singleton' mode. In the singleton creation mode whenever an object reference is made to a 'testqueue' the same MessageQueue instance would be used. So that a new instance can be retrieved based on need, the message listener containers take as an argument the name of the MessageQueueFactoryObject and not a reference. (i.e. use of 'value' instead of 'ref' in the XML).
Note
The MessageQueueFactoryObject class is an ideal candidate for use of a custom namespace. This will be provided in the future. This will allow you to use VS.NET IntelliSense to configure this commonly used object. An example of the potential syntax is shown below
<mq:messageQueue id="testqueue" path=".\Private$ \testqueue" MessageReadPropertyFilterSetAll="true"> <mq:properties label="MyLabel"/> </mq:messageQueue>
To isolate the creation logic of these classes, the factory interface IMessageQueueFactory is used. The interface is shown below
public interface IMessageQueueFactory { MessageQueue CreateMessageQueue(string messageQueueObjectName); IMessageConverter CreateMessageConverter(string messageConverterObjectName); }
A provided implementation, DefaultMessageQueueFactory will create an instance of each class perthread. It delegates the creation of the MessageQueue instance to the Spring container. The argument, messageConverterObjectName, must be the id/name of a MessageQueueFactoryObject defined in the Spring container.
DefaultMessageQueueFactory
leverages Spring's local thread storage support so it will work correctly in stand
alone and web applications. You can use the DefaultMessageQueueFactory independent of the rest of Spring's MSMQ support should you need only the functionality it offers. MessageQueueTemplate and the listener containers create an instance of DefaultMessageQueueFactory by default. Should you want to share the same instance across these two classes, or provide your own custom implementation, use the property MessageQueueFactory on either MessageQueueTemplate or the message listener classe.s
349
Message Oriented Middleware - MSMQ between an IMessageListener and a MessageQueue. (Note, message listener containers are conceptually different than Spring's Inversion of Control container, though it integrates and leverages the IoC container.) The message listener container takes care of registering to receive messages, participating in transactions, resource acquisition and release, exception conversion and suchlike. This allows you as an application developer to write the (possibly complex) business logic associated with receiving a message (and possibly responding to it), and delegate boilerplate MSMQ infrastructure concerns to the framework. A subclass of AbstractMessageListenerContainer is used to receive messages from a MessageQueue. Which subclass you pick depends on your transaction processing requirements. The following subclasses are available in the namespace Spring.Messaging.Listener NonTransactionalMessageListenerContainer - does not surround the receive operation with a transaction TransactionalMessageListenerContainer - surrounds the receive operation with local (non-DTC) based transaction(s). DistributedTxMessageListenerContainer - surrounds the receive operation with a distributed (DTC) transaction Each of these containers use an implementation in which is based on Peeking for messages on a MessageQueue. Peeking is the only resource efficient approach that can be used in order to have MessageQueue receipt in conjunction with transactions, either local MSMQ transactions, local ADO.NET based transactions, or DTC transactions. Each container can specify the number of threads that will be created for processing messages after the Peek occurs via the property MaxConcurrentListeners. Each processing thread will continue to listen for messages up until the timeout value specified by ListenerTimeLimit or until there are no more messages on the queue (whichever comes first). The default value of ListenerTimeLimit is TimeSpan.Zero, meaning that only one attempt to receive a message from the queue will be performed by each listener thread. The current implementation uses the standard .NET thread pool. Future implementations will use a custom (and pluggable) thread pool. 33.3.4.1. NonTransactionalMessageListenerContainer This container performs a Receive operation on the MessageQueue without any transactional settings. As such messages will not be redelivered if an exception is thrown during message processing. Exceptions during message processing can be handled via an implementation of the interface IExceptionHandler. This can be set via the property ExceptionHandler on the listener. The IExceptionHandler interface is shown below
public interface IExceptionHandler { void OnException(Exception exception, Message message); }
350
<!-- Listener container --> <object id="nonTransactionalMessageListenerContainer" type="Spring.Messaging.Listener.NonTransactionalMessageListenerContai Spring.Messaging"> <property name="MessageQueueObjectName" value="msmqTestQueue"/> <property name="MaxConcurrentListeners" value="2"/> <property name="ListenerTimeLimit" value="20s"/> <!-- 20 seconds --> <property name="MessageListener" ref="messageListenerAdapter"/> <property name="ExceptionHandler" ref="exceptionHandler"/> </object> <!-- Delegate to plain CLR object for message handling --> <object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging"> <property name="DefaultResponseQueueName" value="msmqTestResponseQueue"/> <property name="HandlerObject" ref="simpleHandler"/> </object> <!-- Classes you need to write --> <object id="simpleHandler" type="MyNamespace.SimpleHandler, MyAssembly"/> <object id="exceptionHandler" type="MyNamespace.SimpleExceptionHandler, MyAssembly"/>
33.3.4.2. TransactionalMessageListenerContainer This message listener container performs receive operations within the context of local transaction. This class requires an instance of Spring's IPlatformTransactionManager, either AdoPlatformTransactionManager, HibernateTransactionManager, or MessageQueueTransactionManager. If you specify a MessageQueueTransactionManager then a MessageQueueTransaction will be started before receiving the message and used as part of the container's receive operation. As with other IPlatformTransactionManager implementation's, the transactional resources (in this case an instance of the MessageQueueTransaction class) is bound to thread local storage. MessageQueueTemplate will look in threadlocal storage and use this 'ambient' transaction if found for its send and receive operations. The message listener is invoked and if no exception occurs, then the MessageQueueTransactionManager will commit the MessageQueueTransaction. The message listener implementation can call into service layer classes that are made transactional using standard Spring declarative transactional techniques. In case of exceptions in the service layer, the database operation will be rolled back (nothing new here), and the TransactionalMessageListenerContainer will call it's IMessageTransactionExceptionHandler implementation to determine if the MessageQueueTransaction should commit (removing the message from the queue) or rollback (leaving the message on the queue for redelivery).
351
Note
The use of a transactional service layer in combination with a MessageQueueTransactionManager is a powerful combination that can be used to achieve "exactly one" transaction message processing with database operations. This requires a little extra programming effort and is a more efficient alternative than using distributed transactions which are commonly associated with this functionality since both the database and the message transaction commit or rollback together. The additional programming logic needed to achieve this is to keep track of the Message.Id that has been processed successfully within the transactional service layer. This is needed as there may be a system failure (e.g. power goes off) between the 'inner' database commit and the 'outer' messaging commit, resulting in message redelivery. The transactional service layer needs logic to detect if incoming message was processed successfully. It can do this by checking the database for an indication of successful processing, perhaps by recording the Message.Id itself in a status table. If the transactional service layer determines that the message has already been processed, it can throw a specific exception for this case. The container's exception handler will recognize this exception type and vote to commit (remove from the queue) the 'outer' messaging transaction. Spring provides an exception handler with this functionality, see SendToQueueExceptionHandler described below. An configuring the TransactionalMessageListenerContainer MessageQueueTransactionManager is shown below example of using a
<!-- Queue to receive from --> <object id='msmqTestQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'> <property name='Path' value='.\Private$\testqueue'/> <property name='MessageReadPropertyFilterSetAll' value='true'/> <property name='ProductTemplate'> <object> <property name='Label' value='MyTestQueueLabel'/> </object> </property> </object> <!-- Queue to respond to --> <object id='msmqTestResponseQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'> <property name='Path' value='.\Private$\testresponsequeue'/> <property name='MessageReadPropertyFilterSetAll' value='true'/> <property name='ProductTemplate'> <object> <property name='Label' value='MyTestResponseQueueLabel'/> </object> </property> </object> <!-- Transaction Manager for MSMQ Messaging --> <object id="messageQueueTransactionManager" type="Spring.Messaging.Core.MessageQueueTransactionManager, Spring.Messaging"/> <!-- The transaction message listener container --> <object id="transactionalMessageListenerContainer" type="Spring.Messaging.Listener.TransactionalMessageListenerContainer, Spring.Messaging"> <property name="MessageQueueObjectName" value="msmqTestQueue"/> <property name="PlatformTransactionManager" ref="messageQueueTransactionManager"/> <property name="MaxConcurrentListeners" value="5"/> <property name="ListenerTimeLimit" value="20s"/> <property name="MessageListener" ref="messageListenerAdapter"/> <property name="MessageTransactionExceptionHandler" ref="messageTransactionExceptionHandler"/> </object> <!-- Delegate to plain CLR object for message handling --> <object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging"> <property name="DefaultResponseQueueName" value="msmqTestResponseQueue"/> <property name="HandlerObject" ref="simpleHandler"/>
352
If you specify either AdoPlatformTransactionManager or HibernateTransactionManager then a local database transaction will be started before the receiving the message. By default, the container will also start a local MessageQueueTransaction after the local database transaction has started, but before the receiving the message. This MessageQueueTransaction will be used to receive the message. By default the MessageQueueTransaction will be bound to thread local storage so that any MessageQueueTemplate send or receive operations will participate transparently in the same MessageQueueTransaction. If you do not want this behavior set the property ExposeContainerManagedMessageQueueTransaction to false. In case of exceptions during IMessageListener processing when using either either AdoPlatformTransactionManager or HibernateTransactionManager the container's IMessageTransactionExceptionHandler will determine if the MessageQueueTransaction should commit (removing it from the queue) or rollback (placing it back on the queue for redelivery). The listener exception will always trigger a rollback in the 'outer' database transaction. Poison message handing, that is, the endless redelivery of a message due to exceptions during processing, can be detected using implementations of the IMessageTransactionExceptionHandler. This interface is shown below
public interface IMessageTransactionExceptionHandler { TransactionAction OnException(Exception exception, Message message, messageQueueTransaction); }
MessageQueueTransaction
The return value is an enumeration with the values Commit and Rollback. A specific implementation is provided that will move the poison message to another queue after a maximum number of redelivery attempts. See SendToQueueExceptionHandler described below. You can set a specific implementation to by setting TransactionalMessageListenerContainer's property MessageTransactionExceptionHandler The IMessageTransactionExceptionHandler implementation SendToQueueExceptionHandler keeps track of the Message's Id property in memory with a count of how many times an exception has occurred. If that count is greater than the handler's MaxRetry count it will be sent to another queue using the provided MessageQueueTransaction. The queue to send the message to is specified via the property MessageQueueObjectName. 33.3.4.3. DistributedTxMessageListenerContainer This message listener container performs receive operations within the context of distributed transaction. A distributed transaction is started before a message is received. The receive operation participates in this transaction using by specifying MessageQueueTransactionType = Automatic. The transaction that is started is automatically promoted to two-phase-commit to avoid the default behavior of transaction promotion since the only reason to use this container is to use two different resource managers (messaging and database typically). The commit and rollback semantics are simple, if the message listener does not throw an exception the transaction is committed, otherwise it is rolled back.
353
Message Oriented Middleware - MSMQ Exceptions processing are handled by implementations IDistributedTransactionExceptionHandler interface. This interface is shown below
public interface IDistributedTransactionExceptionHandler { bool IsPoisonMessage(Message message); void HandlePoisonMessage(Message poisonMessage); void OnException(Exception exception, Message message); }
in
message
listener
of
the
the IsPoisonMessage method determines whether the incoming message is a poison message. This method is called before the IMessageListener is invoked. The container will call HandlePoisonMessage is IsPoisonMessage returns true and will then commit the distributed transaction (removing the message from the queue. Typical implementations of HandlePoisonMessage will move the poison message to another queue (under the same distributed transaction used to receive the message). The class SendToQueueDistributedTransactionExceptionHandler detects poison messages by tracking the Message Id property in memory with a count of how many times an exception has occurred. If that count is greater than the handler's MaxRetry count it will be sent to another queue. The queue to send the message to is specified via the property MessageQueueObjectName.
33.4. MessageConverters
33.4.1. Using MessageConverters
In order to facilitate the sending of business model objects, the MessageQueueTemplate has various send methods that take a .NET object as an argument for a message's data content. The overloaded methods ConvertAndSend and ReceiveAndConvert in MessageQueue delegate the conversion process to an instance of the IMessageConverter interface. This interface defines a simple contract to convert between .NET objects and JMS messages. The interface is shown below
public interface IMessageConverter : ICloneable { Message ToMessage(object obj); object FromMessage(Message message); }
There are a standard implementations provided the simply wrap existing IMessageFormatter implementations. XmlMessageConverter - uses a XmlMessageFormatter. BinaryMessageConverter - uses a BinaryMessageFormatter ActiveXMessageConverter - uses a ActiveXMessageFormatter The default implementation used in MessageQueueTemplate and the message listener containers is an instance of XmlMessageConverter configured with a TargetType to be System.String. You specify the types that the XmlMessageConverter can convert though either the array property TargetTypes or TargetTypeNames. Here is an example taken from the QuickStart application
<object id="xmlMessageConverter" singleton="false" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"> <property name="TargetTypes"> <list> <value>Spring.MsmqQuickStart.Common.Data.TradeRequest, Spring.MsmqQuickStart.Common</value> <value>Spring.MsmqQuickStart.Common.Data.TradeResponse, Spring.MsmqQuickStart.Common</value>
354
You can specify other IMessageConverter implementations using the MessageConverterObjectName property on the MessageQueueTemplate and MessageListenerAdapter.
Note
The scope of the object definition is set to singleton="false", meaning that a new instance of the MessageConverter will be created each time you ask the container for an object of the name 'xmlMessageConverter'. This is important to ensure that a new instance will be used for each thread. If you forget, a warning will be logged and IMessageConverter's Clone() method will be called to create an indepentend instance. Other implementations provided are XmlDocumentConverter - loads and saves an XmlDocument to the message BodyStream. This lets you manipulate directly the XML data independent of type serialization issues. This is quite useful if you use XPath expressions to pick out the relevant information to construct your business objects. Other potential implementations: RawBytesMessageConverter - directly write raw bytes to the message stream, compress CompressedMessageConverter - compresses the message payload EncryptedMessageConverter - encrypt the message (standard MSMQ encryptiong has several limitations) SoapMessageConverter - use soap formatting.
355
The next example has a similar method signature but the name of the handler method name has been changed to "DoWork", by setting the adapter's property DefaultHandlerMethod.
public interface IMyHandler { void DoWork(string text); }
If your IMessageConverter implementation will return multiple object types, overloading the handler method is perfectly acceptable, the most specific matching method will be used. A method with an object signature would be consider a 'catch-all' method of last resort.
public interface IMyHandler { void DoWork(string text); void DoWork(OrderRequest orderRequest); void DoWork(InvoiceRequest invoiceRequest); void DoWork(object obj); }
Another of the capabilities of the MessageListenerAdapter class is the ability to automatically send back a response Message if a handler method returns a non-void value. Any non-null value that is returned from the execution of the handler method will (in the default configuration) be converted to a string. The resulting string will then be sent to the ResponseQueue defined in the Message's ResponseQueue property of the original Message, or the DefaultResponseQueueName on the MessageListenerAdapter (if one has been configured) will be used. If not ResponseQueue is found then an Spring MessagingException will be thrown. Please note that this exception will not be swallowed and will propagate up the call stack. Here is an example of Handler signatures that have various return types.
public interface IMyHandler { string DoWork(string text); OrderResponse DoWork(OrderRequest orderRequest); InvoiceResponse DoWork(InvoiceRequest invoiceRequest); void DoWork(object obj); }
The following configuration shows how to hook up the adapter to process incoming MSMQ messages using the default message converter.
<!-- Delegate to plain CLR object for message handling --> <object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging"> <property name="DefaultResponseQueueName" value="msmqTestResponseQueue"/> <property name="HandlerObject" ref="myHandler"/> </object>
356
Message Oriented Middleware - MSMQ The good news is that if and when it comes time to move from a Spring MSMQ solution to WCF, you will be in a great position as the POCO interface used for business processing when receiving in a Spring based MSMQ application can easily be adapted to a WCF environment. There may also be some features unique to MSMQ and/or Spring's MSMQ support that you may find appealing over WCF. Many messaging applications still need to be 'closer to the metal' and this is not possible using the WCF bindings, for example Peeking and Label, AppSpecific properties, multicast.. An interesting recent quote by Yoel Arnon (MSMQ guru) "With all the respect to WCF, System.Messaging is still the major programming model for MSMQ programmers, and is probably going to remain significant for the foreseeable future. The message-oriented programming model is different from the service-oriented model of WCF, and many real-world solutions would always prefer it."
357
Note
There is a Quartz Quickstart application that is shipped with Spring.NET. It is documented here.
JobDetailObject
objects contain all information needed to run a job. The Spring Framework provides a that makes the JobDetail easier to configure and with sensible defaults. Let's have a look at
an example:
<object name="ExampleJob" type="Spring.Scheduling.Quartz.JobDetailObject, Spring.Scheduling.Quartz"> <property name="JobType" value="Example.Quartz.ExampleJob, Example.Quartz" /> <property name="JobDataAsMap"> <dictionary> <entry key="Timeout" value="5" /> </dictionary> </property> </object>
The job detail object has all information it needs to run the job (ExampleJob). The timeout is specified in the job data dictionary. The job data dictonary is available through the JobExecutionContext (passed to you at execution time), but the JobDetailObject also maps the properties from the job data map to properties of the actual job. So in this case, if the ExampleJob contains a property named Timeout, the JobDetailObject will automatically apply it:
namespace Example.Quartz; public class ExampleJob : QuartzJobObject { private int timeout; /// <summary> /// Setter called after the ExampleJob is instantiated /// with the value from the JobDetailObject (5) /// </summary> public int Timeout { set { timeout = value; }; }
358
All additional settings from the job detail object are of course available to you as well. Note: Using the name and group properties, you can modify the name and the group of the job, respectively. By default, the name of the job matches the object name of the job detail object (in the example above, this is ExampleJob).
The above example will result in the doIt method being called on the exampleBusinessObject method (see below):
public class ExampleBusinessObject { // properties and collaborators public void DoIt() { // do the actual work } }
Using the MethodInvokingJobDetailFactoryObject, you don't need to create one-line jobs that just invoke a method, and you only need to create the actual business object and wire up the detail object. By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering with each other. If you specify two triggers for the same JobDetail, it might be possible that before the first job has finished, the second one will start. If JobDetail classes implement the Stateful interface, this won't happen. The second job will not start before the first one has finished. To make jobs resulting from the MethodInvokingJobDetailFactoryObject non-concurrent, set the concurrent flag to false.
<object id="JobDetail" type="Spring.Scheduling.Quartz.MethodInvokingJobDetailFactoryObject, Spring.Scheduling.Quartz"> <property name="TargetObject" ref="ExampleBusinessObject" /> <property name="TargetMethod" value="DoIt" /> <property name="Concurrent" value="false" /> </object>
Note
By default, jobs will run in a concurrent fashion. Also note that when using MethodInvokingJobDetailFactoryObject you can't use database persistence for Jobs. See the class documentation for additional details.
359
Now we've set up two triggers, one running every 50 seconds with a starting delay of 10 seconds and one every morning at 6 AM. To finalize everything, we need to set up the SchedulerFactoryObject:
<object id="quartzSchedulerFactory" type="Spring.Scheduling.Quartz.SchedulerFactoryObject, Spring.Scheduling.Quartz"> <property name="triggers"> <list> <ref object="CronTrigger" /> <ref object="SimpleTrigger" /> </list> </property> </object>
More properties are available for the SchedulerFactoryObjecct for you to set, such as the calendars used by the job details, properties to customize Quartz with, etc. Have a look at the SchedulerFactoryObject SDK docs for more information.
360
35.2. Dependencies
The Spring NVelocity support depends on the Castle project's NVelocity implementation which is located in the lib directory of the Spring release.
The velocity engine could then be used to load and merge a local template using a simple relative path (the default resource loader path is the current execution directory):
StringWriter stringWriter = new StringWriter(); Hashtable modelTable = new Hashtable(); modelTable.Add("var1", TEST_VALUE); VelocityContext velocityContext = new VelocityContext(modelTable); velocityEngine.MergeTemplate("Template/Velocity/MyTemplate.vm", Encoding.UTF8.WebName, velocityContext, stringWriter); string mergedContent = stringWriter.ToString();
To disable the use of NVelocity's file loader that tracks runtime changes, set the element prefer-file-systemaccess of <engine/> to false.
config-file
A uri of a properties file defining the NVelocity no configuration. This value accepts all spring resource
361
Template Engine Support Attribute Description loader uri (e.g., file://, http://). See Section 35.3.7, Using a custom configuration file prefer-file-system-access Instructs the NVelocity engine factory to attempt no use NVelocity's file loader. When set to false the provided SpringResourceLoader will be used (and the ResourceLoaderPath property must be set) Instructs the NVelocity engine factory to use the no provided spring commons logging based logging system. See Section 35.3.8, Logging true Required Default Value
override-logging
true
Using the example above the template would be loaded using a namespace syntax for the template resource:
velocityEngine.MergeTemplate("MyAssembly.MyNamespace.MyTemplate.vm", Encoding.UTF8.WebName, velocityContext, stringWriter);
Note
By default spring will attempt to load resources using NVelocity's file based template loading (useful for detection of template changes at runtime). If this is not desirable you set the prefer-file-systemaccess property of the factory object to false which will cause the factory to utilize the supplied spring resource loader.
362
Template Engine Support Using the example above when resource loader paths are defined templates can be loaded using their name:
string mergedTemplate = Encoding.UTF8.WebName, string mergedTemplate = Encoding.UTF8.WebName, VelocityEngineUtils.MergeTemplateIntoString(velocityEngine, "MyFileTemplate.vm", model); // template loaded from file://Template/Velocity/ VelocityEngineUtils.MergeTemplateIntoString(velocityEngine, "MyAssemblyTemplate.vm", model); // template loaded from assembly://MyAssembly/MyNameSpace
default-cache-size
defines resource manager global cache no size, applies when caching is turned on. This maps to NVelocity's resource manager resource.manager.defaultcache.size property Enables template caching for the defined resource no loader. This maps to NVelocity's resource loader <name>.resource.loader.cache property The modification check interval value (seconds) no of the resource loader, applies only to resource loader with change detection capabilities (file or custom). This maps to NVelocity's resource loader
<name>.resource.loader.modificationCheckInterval
template-caching
false
modification-checkinterval
property
You can override specific properties by providing the VelocityProperties property to the NVelocity factory object (shown above)
<nv:engine id="velocityTemplate" > <nv:nvelocity-properties> <entry key="input.encoding" value="ISO-8859-1"/>
363
35.3.8. Logging
By default Spring will override NVelocity's default ILogSystem implementation with its own CommonsLoggingLogSystem implementation so that the logging stream of NVelocity will go to the same logging subsystem that Spring uses. If this is not desirable, you can specify the following property of the NVelocity factory object:
<template:nvelocity id="velocityEngine" override-logging="false" />
For convenience in defining NVelocity engine instances a custom namespace is provided, for example the resource loader definition could be done this way:
<objects xmlns="http://www.springframework.net" xmlns:nv="http://www.springframework.net/nvelocity"> <nv:nvelocity id="velocityEngine" > <nv:resource-loader> <nv:file path="Template/Velocity/" /> </nv:resource-loader> </nv:nvelocity> </objects
When templates are packaged in an assembly, NVelocity's assembly resource loader can be used to define where templates reside:
<!-- Assembly based template loading with NVelocity assembly resource loader --> <object id="velocityEngine" type="Spring.Template.Velocity.VelocityEngineFactoryObject, Spring.Template.Velocity"> <property name="VelocityProperties"> <dictionary key-type="string" value-type="object"> <entry key="resource.loader" value="assembly"/> <entry key="assembly.resource.loader.class" value="NVelocity.Runtime.Resource.Loader.AssemblyResourceLoader"/ > <entry key="assembly.resource.loader.assembly" value="MyAssembly"/> </dictionary>
364
Note
By default spring will attempt to load resources using NVelocity's file based template loading (useful for detection of template changes at runtime). If this is not desirable you set the preferFileSystemAccess property of the factory object to false which will cause the factory to utilize the supplied spring resource loader. To refer to a property file based configuration of the TemplateEngine use the definition:
<object id="velocityEngine" type="Spring.Template.Velocity.VelocityEngineFactoryObject, Spring.Template.Velocity" > <property name="ConfigLocation " value="file://Template/Velocity/config.properties" /> </object>
Note
You can override specific properties by providing the VelocityProperties property. To not integrate with the Common.Logging subsystem, set the OverrideLogging property to false:
<object id="velocityEngine" type="Spring.Template.Velocity.VelocityEngineFactoryObject, Spring.Template.Velocity" > <property name="OverrideLogging" value="false" /> </object>
365
366
The VS.NET 2005 or later, the XML editor uses the attribute xsi:schemaLocation as a hint to associate the physical location of a schema file with the XML document being edited. VS.NET 2002/2003 do not recognize the xsi:schemaLocation element. If you reference the Spring.NET XML schema as shown below, you can get intellisense and validation support while editing a Spring configuration file in VS.NET 2005/2008/2010. In order to get this functionality in VS.NET 2002/2003 you will need to register the schema with VS.NET or include the schema as part of your application project.
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd"> <object id="..." type="..."> ... </object> <object id="..." type="..."> ... </object> ... </objects>
It is typically more convenient to install the schema in VS.NET, even for VS.NET 2005/2008/2010, as it makes the xml a little less verbose and you don't need to keep copying the XSD file for each project you create. The following table lists the schema directories for each version of VS.NET: Table 36.1. Visual Studio Version VS.NET 2003 Directory in which to place Spring .XSD files
C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml
VS.NET 2005
VS.NET 2008
C:\Program
Files\Microsoft
Visual
Studio
9.0\Xml\Schemas
367
Visual Studio.NET Integration Visual Studio Version VS.NET 2010 Directory in which to place Spring .XSD files
C:\Program Files\Microsoft Visual Studio
10.0\Xml\Schemas
Spring's .xsd schemas are located in the directory doc/schema. In that directory is also a NAnt build file to help copy over the .xsd files to the appropriate VS.NET locations. To execute this script simply type 'nant' in the doc/ schema directory. Once you have registered the schema with VS.NET you can adding only the namespace declaration to the objects element,
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.net"> <object id="..." type="..."> ... </object> <object id="..." type="..."> ... </object> ... </objects>
Once registered, the namespace declaration alone is sufficient to get intellisense and validation of the configuration file from within VS.NET. Alternatively, you can select the .xsd file to use by setting the targetSchema property in the Property Sheet for the configuration file. As shown in the section Section 5.2.3, Using the container Spring.NET supports using .NET's application configuration file as the location to store the object definitions that will be managed by the object factory.
<configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> </sectionGroup> </configSections> <spring> <context> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> ... </objects> </spring> </configuration>
In this case VS.NET 2003 will still provide you with intellisense help but you will not be able to fully validate the document as the entire schema for App.config is not known. To be able to validate this document one would need to install the .NET Configuration File schema and an additional schema that incorporates the <spring> and <context> section in addition to the <objects> would need to be created. Validating schema is a new feature in VS 2005 or later. It is validating all the time while you edit, you will see any errors that it finds in the Error List window.
368
Visual Studio.NET Integration Keep these trade offs in mind as you decide where to place the bulk of your configuration information. Conventional wisdom is do quick prototyping with App.config and use another IResource location, file or embedded assembly resource, for serious development.
36.2. Enhancing the XML Editing and Validation Experience using the Spring.NET Visual Studio 2010 Extension
If you are using VS.NET 2010, you are encouraged to install the Spring.NET Visual Studio 2010 Extension. For more information and to download the latest version of this 100% free tool, visit http://springframework.net/ vsaddin/. The latest release of the Spring.NET Visual Studio 2010 Extension provides Intellisensetm support in VS.NET 2010 for the following areas of editing Spring XML configuration files: Type completion Property name completion Constructor argument name completion Property value completion for property of type 'Type', 'Enum' and 'Boolean' In addition, this tool also provides for the following enhancements to the Visual Studio 2010 XML Editor experience: Snippets integration (inline or by menu) Quickinfo tooltip for properties and types A brief screencast demonstrating the use of this tool can be viewed here: http://maruxelo.free.fr/spring/ index2.html
In VS.NET 2008 when you create a new project you will see the category Spring.NET and the four solution templates as shown below
369
All of the templates have the required Spring dependencies set and Spring application configuration files are present and ready for you to add object definitions.
370
371
372
373
You start to type the name of the class and will get a filter list. In this case we are typing HibernateOrderDao.
374
Visual Studio.NET Integration Hittingn 'enter' will then insert the fully qualfied type name with the namespace but not the assembly reference. To add the assembly reference either hit 'CTRL+ENTER" or select the yellow 'light bulb' to and select 'add module qualification'.
You will need to remove the extraneous 'Verstion' information. This will leave you with the following object definition.
If you use Spring's autowiring functionality, then you can even avoid having to type the property information when referring to collaborating objects. See Section 5.3.6, Autowiring collaborators. for more information on autowiring.
375
For example, to set a property reference for the object definition from the previous chapter, type 'odpr' (Object Definition Property Reference) and you will be prompted to hit 'tab' to complete the XML fragment.
Hitting tab will generate the XML to use for an object property values
You will need to type the name of the property and name of the reference. Unfortunately, intellisence for property completion and ref completion is not available. Typing the missing information in then leaves the completed object definition.
There are similar live templates for object property values (odpv), object constructors (odctor) and object definitions (odef)
376
377
What we want to do is get a reference to an instance of the MovieLister class... since this is a Spring.NET example we'll get this reference from Spring.NET's IoC container, the IApplicationContext. There are a
378
IoC Quickstarts number of ways to get a reference to an IApplicationContext instance, but for this example we'll be using an IApplicationContext that is instantiated from a custom configuration section in a standard .NET application config file...
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> </sectionGroup> </configSections> <spring> <context> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> <description>An example that demonstrates simple IoC features.</description> </objects> </spring> </configuration>
The objects that will be used in the example application will be configured as XML <object/> elements nested inside the <objects/> element. The body of the Main method in the MovieApp class can now be fleshed out a little further...
using System; using Spring.Context; ... public static void Main () { IApplicationContext ctx = ContextRegistry.GetContext(); } ...
As can be seen in the above C# snippet, a using statement has been added to the MovieApp source. The Spring.Context namespace gives the application access to the IApplicationContext class that will serve as the primary means for the application to access the IoC container. The line of code...
IApplicationContext ctx = ContextRegistry.GetContext();
... retrieves a fully configured IApplicationContext implementation that has been configured using the named <objects/> section from the application config file.
Notice that the full, assembly-qualified name of the MovieLister class has been specified in the type attribute of the object definition, and that the definition has been assigned the (unique) id of MyMovieLister. Using this id, an instance of the object so defined can be retrieved from the IApplicationContext reference like so...
... public static void Main ()
379
IoC Quickstarts
{ IApplicationContext ctx = ContextRegistry.GetContext(); MovieLister lister = (MovieLister) ctx.GetObject ("MyMovieLister"); } ...
The lister instance has not yet had an appropriate implementation of the IMovieFinder interface injected into it. Attempting to use the MoviesDirectedBy method will most probably result in a nasty NullReferenceException since the lister instance does not yet have a reference to an IMovieFinder. The XML configuration for the IMovieFinder implementation that is going to be injected into the lister instance looks like this...
<objects xmlns="http://www.springframework.net"> <object name="MyMovieFinder" type="Spring.Examples.MovieFinder.SimpleMovieFinder, Spring.Examples.MovieFinder"/> </object> </objects>
When the MyMovieLister object is retrieved from (i.e. instantiated by) the IApplicationContext in the application, the Spring.NET IoC container will inject the reference to the MyMovieFinder object into the MovieFinder property of the MyMovieLister object. The MovieLister object that is referenced in the application is then fully configured and ready to be used in the application to do what is does best... list movies by director.
... public static void Main () { IApplicationContext ctx = ContextRegistry.GetContext(); MovieLister lister = (MovieLister) ctx.GetObject ("MyMovieLister"); Movie[] movies = lister.MoviesDirectedBy("Roberto Benigni"); Console.WriteLine ("\nSearching for movie...\n"); foreach (Movie movie in movies) { Console.WriteLine ( string.Format ("Movie Title = '{0}', Director = '{1}'.", movie.Title, movie.Director)); } Console.WriteLine ("\nMovieApp Done.\n\n"); } ...
To help ensure that the XML configuration of the MovieLister class must specify a value for the MovieFinder property, you can add the [Required] attribute to the MovieLister's MovieFinder property. The example code shows uses this attribute. For more information on using and configuring the [Required] attribute, refer to this section of the reference documentation.
380
IoC Quickstarts
... <object name="AnotherMovieFinder" type="Spring.Examples.MovieFinder.ColonDelimitedMovieFinder, Spring.Examples.MovieFinder"> </object> ...
This XML snippet describes an IMovieFinder implementation that uses a colon delimited text file as it's movie source. The C# source code for this class defines a single constructor that takes a System.IO.FileInfo as it's single constructor argument. As this object definition currently stands, attempting to get this object out of the IApplicationContext in the application with a line of code like so...
IMovieFinder finder = (IMovieFinder) ctx.GetObject ("AnotherMovieFinder");
will
because the Spring.Examples.MovieFinder.ColonDelimitedMovieFinder class does not have a default constructor that takes no arguments. If we want to use this implementation of the IMovieFinder interface, we will have to supply an appropriate constructor argument...
... <object name="AnotherMovieFinder" type="Spring.Examples.MovieFinder.ColonDelimitedMovieFinder, Spring.Examples.MovieFinder"> <constructor-arg index="0" value="movies.txt"/> </object> ...
result
in
fatal
Spring.Objects.Factory.ObjectCreationException,
Unsurprisingly, the <constructor-arg/> element is used to supply constructor arguments to the constructors of managed objects. The Spring.NET IoC container uses the functionality offered by System.ComponentModel.TypeConverter specializations to convert the movies.txt string into an instance of the System.IO.FileInfo that is required by the single constructor of the Spring.Examples.MovieFinder.ColonDelimitedMovieFinder (see Section 6.3, Type conversion for a more in depth treatment concerning the automatic type conversion functionality offered by Spring.NET). So now we have two implementations of the IMovieFinder interface that have been defined as distinct object definitions in the config file of the example application; if we wanted to, we could switch the implementation that the MyMovieLister object uses like so...
... <object name="MyMovieLister" type="Spring.Examples.MovieFinder.MovieLister, Spring.Examples.MovieFinder"> <!-- lets use the colon delimited implementation instead --> <property name="movieFinder" ref="AnotherMovieFinder"/> </object> <object name="MyMovieFinder" type="Spring.Examples.MovieFinder.SimpleMovieFinder, Spring.Examples.MovieFinder"/> </object> <object name="AnotherMovieFinder" type="Spring.Examples.MovieFinder.ColonDelimitedMovieFinder, Spring.Examples.MovieFinder"> <constructor-arg index="0" value="movies.txt"/> </object> ...
Note that there is no need to recompile the application to effect this change of implementation... simply changing the application config file and then restarting the application will result in the Spring.NET IoC container injecting the colon delimited implementation of the IMovieFinder interface into the MyMovieLister object.
37.2.5. Summary
This example application is quite simple, and admittedly it doesn't do a whole lot. It does however demonstrate the basics of wiring together an object graph using an intuitive XML format. These simple features will get you through pretty much 80% of your object wiring needs. The remaining 20% of the available configuration options
381
IoC Quickstarts are there to cover corner cases such as factory methods, lazy initialization, and suchlike (all of the configuration options are described in detail in the Chapter 5, The IoC container).
37.2.6. Logging
Often enough the first use of Spring.NET is also a first introduction to log4net. To kick start your understanding of log4net this section gives a quick overview. The authoritative place for information on log4net is the log4net website. Other good online tutorials are Using log4net (OnDotNet article) and Quick and Dirty Guide to Configuring Log4Net For Web Applications. Spring.NET is using version 1.2.9 whereas most of the documentation out there is for version 1.2.0. There have been some changes between the two so always double check at the log4net web site for definitive information. Also note that we are investigating using a "commons" logging library so that Spring.NET will not be explicity tied to log4net but will be able to use other logging packages such as NLog and Microsoft enterprise logging application block. The general usage pattern for log4net is to configure your loggers, (either in App/Web.config or a seperate file), initialize log4net in your main application, declare some loggers in code, and then log log log. (Sing along...) We are using App.config to configure the loggers. As such, we declare the log4net configuration section handler as shown below
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
The appender is the output sink - in this case the console. There are a large variety of output sinks such as files, databases, etc. Refer to the log4net Config Examples for more information. Of interest as well is the PatternLayout which defines exactly the information and format of what gets logged. Usually this is the date, thread, logging level, logger name, and then finally the log message. Refer to PatternLayout Documentation for information on how to customize. The logging name is up to you to decide when you declare the logger in code. In the case of this example we used the convention of giving the logging name the name of the fully qualified class name.
private static readonly ILog LOG = LogManager.GetLogger(typeof (MovieApp));
Other conventions are to give the same logger name across multiple classes that constitute a logical component or subsystem within the application, for example a data access layer. One tip in selecting the pattern layout is to shorten the logging name to only the last 2 parts of the fully qualified name to avoid the message sneaking off to the right too much (where can't see it) because of all the other information logged that precedes it. Shortening the logging name is done using the format %logger{2}.
382
IoC Quickstarts To initialize the logging system add the following to the start of your application
XmlConfigurator.Configure();
Note that if you are using or reading information on version 1.2.0 this used to be called DOMConfigurator.Configure(); The logger sections associate logger names with logging levels and appenders. You have great flexibility to mix and match names, levels, and appenders. In this case we have defined the root logger (using the special tag root) to be at the debug level and have an console sink. We can then specialize other loggers with different setting. In this case, loggers that start with "Spring" in their name are logged at the info level and also sent to the console. Setting the value of this logger from INFO to DEBUG will show you detailed logging information as the Spring container goes about its job of creating and configuring your objects. Coincidentally, the example code itself uses Spring in the logger name, so this logger also controls the output level you see from running MainApp. Finally, you are ready to use the simple logger api to log, i.e.
LOG.Info("Searching for movie...");
Logging exceptions is another common task, which can be done using the error level
try { //do work { catch (Exception e) { LOG.Error("Movie Finder is broken.", e); }
383
IoC Quickstarts
<list> <value>Spring.Examples.AppContext.Images, Spring.Examples.AppContext</value> <ref object="myResourceManager"/> </list> </property> </object> <object name="myResourceManager" type="Spring.Objects.Factory.Config.ResourceManagerFactoryObject, Spring.Core"> <property name="baseName"> <value>Spring.Examples.AppContext.MyResource</value> </property> <property name="assemblyName"> <value>Spring.Examples.AppContext</value> </property> </object> ...
The main application creates the application context and then retrieves various resources via their key names. In the code all the key names are declared as static fields in the class Keys. The resource file Images.resx contains image data under the key name bubblechamber (aka Keys.BUBBLECHAMBER). The code Image image = (Image)ctx.GetResourceObject(Keys.BUBBLECHAMBER); is used to retrieve the image from the context. The resource files MyResource.resx contains a text resource, Hello {0} {1} under the key name HelloMessage (aka Keys.HELLO_MESSAGE) that can be used for string text formatting purposes. The example code
string msg = ctx.GetMessage(Keys.HELLO_MESSAGE, CultureInfo.CurrentCulture, "Mr.", "Anderson");
retrieves the text string and replaces the placeholders in the string with the passed argument values resulting in the text, "Hello Mr. Anderson". The current culture is used to select the resource file MyResource.resx. If instead the Spanish culture is specified
CultureInfo spanishCultureInfo = new CultureInfo("es"); string esMsg = ctx.GetMessage(Keys.HELLO_MESSAGE, spanishCultureInfo, "Mr.", "Anderson");
Then the resource file MyResource.es.resx is used instead as in standard .NET localization. Spring is simply delegating to .NET ResourceManager to select the appropriate localized resource. The Spanish version of the resource differs from the English one in that the text under the key HelloMessage is Hola {0} {1} resulting in the text "Hola Mr. Anderson". As you can see in this example, the title "Mr." should not be used in the case of the spanish localization. The title can be abstracted out into a key of its own, called FemaleGreeting (aka Keys.FEMALE_GREETING). The replacement value for the message argument {0} can then be made localization aware by wrapping the key in a convenience class DefaultMessageResolvable. The code
string[] codes = {Keys.FEMALE_GREETING}; DefaultMessageResolvable dmr = new DefaultMessageResolvable(codes, null); msg = ctx.GetMessage(Keys.HELLO_MESSAGE, CultureInfo.CurrentCulture, dmr, "Anderson");
will assign msg the value, Hello Mrs. Anderson, since the value for the key FemaleGreeting in MyResource.resx is 'Mrs.' Similarly, the code
esMsg = ctx.GetMessage(Keys.HELLO_MESSAGE, spanishCultureInfo,
384
IoC Quickstarts
dmr, "Anderson");
will assign esMsg the value, Hola Senora Anderson, since the value for the key FemaleGreeting in MyResource.es.resx is 'Senora'. Localization can also apply to objects and not just strings. The .NET 1.1 framework provides the utility class ComponentResourceManager that can apply multiple resource values to object properties in a performant manner. (VS.NET 2005 makes heavy use of this class in the code it generates for winform applications.) The example program has a simple class, Person, that has an integer property Age and a string property Name. The resource file, Person.resx contains key names that follow the pattern, person.<PropertyName>. In this case it contains person.Name and person.Age. The code to assign these resource values to an object is shown below
Person p = new Person(); ctx.ApplyResources(p, "person", CultureInfo.CurrentUICulture);
While you could also use the Spring itself to set the properties of these objects, the configuration of simple properties using Spring will not take into account localization. It may be convenient to combine approaches and use Spring to configure the Person's object references while using IApplicationContext inside an AfterPropertiesSet callback (see IInitializingObject) to set the Person's culture aware properties.
385
IoC Quickstarts
void SimpleClientEvent( object sender, MyClientEventArgs args )
fires this event. On the subscribing side, the class MyEventSubscriber contains a method, HandleClientEvents that matches the delegate signature and has a boolean property which is set to true if this method is called.
ClientMethodThatTriggersEvent1()
The publisher and subscriber classes are defined in an application context configuration file but that is not required in order to participate with the event registry. The main program, EventRegistryApp creates the application context and asks it for an instance of MyEventPublisher The publisher is registered with the event registry via the call, ctx.PublishEvents( publisher ). The event registry keeps a reference to this publisher for later use to register any subscribers that match its event signature. Two subscribers are then created and one of them is wired to the publisher by calling the method ctx.Subscribe( subscriber, typeof(MyEventPublisher) ) Specifying the type indicates that the subscriber should be registered only to events from objects of the type MyEventPublisher. This acts as a simple filtering mechanism on the subscriber. The publisher then fires the event using normal .NET eventing semantics and the subscriber is called. The subscriber prints a message to the console and sets a state variable to indicate it has been called. The program then simply prints the state variable of the two subscribers, showing that only one of them (the one that registered with the event registry) was called.
386
IoC Quickstarts
ActivateObject, ValidateObject, PassivateObject, and DestroyObject)
as appropriate when the pool is created, objects are borrowed and returned to the pool, and when the pool is destroyed. In our case, as already said, we want to to implement a pool of QueuedExecutor. Ok, here the declaration:
public class QueuedExecutorPoolableFactory : IPoolableObjectFactory {
When an object is taken from the pool, to satisfy a client request, may be the object should be activated. We can possibly implement the activation like this:
void IPoolableObjectFactory.ActivateObject(object o) { QueuedExecutor executor = o as QueuedExecutor; executor.Restart(); }
even if a QueuedExecutor restarts itself as needed and so a valid implementation could leave this method empty. After activation, and before the pooled object can be succesfully returned to the client, it is validated (should the object be invalid, it will be discarded: this can lead to an empty unusable pool 1). Here we check that the worker thread exists:
bool IPoolableObjectFactory.ValidateObject(object o) { QueuedExecutor executor = o as QueuedExecutor; return executor.Thread != null; }
Passivation, symmetrical to activation, is the process a pooled object is subject to when the object is returned to the pool. In our case we simply do nothing:
void IPoolableObjectFactory.PassivateObject(object o) { }
You may think that we can provide a smarter implementation and you are probably right. However, it is not so difficult to create a new pool in case the old one became unusable. It could not be your preferred choice but surely it leverages simplicity and object immutability
387
IoC Quickstarts
pool = new SimplePool(new QueuedExecutorPoolableFactory(), size);
without worrying about obtaining and returning an object from/to the pool. Here is the implementation:
public class PooledObjectHolder : IDisposable { IObjectPool pool; object pooled; /// <summary> /// Builds a new <see cref="PooledObjectHolder"/> /// trying to borrow an object form it /// </summary> /// <param name="pool"></param> private PooledObjectHolder(IObjectPool pool) { this.pool = pool; this.pooled = pool.BorrowObject(); } /// <summary> /// Allow to access the borrowed pooled object /// </summary> public object Pooled { get { return pooled; } } /// <summary> /// Returns the borrowed object to the pool /// </summary> public void Dispose() { pool.ReturnObject(pooled); } /// <summary> /// Creates a new <see cref="PooledObjectHolder"/> for the /// given pool. /// </summary> public static PooledObjectHolder UseFrom(IObjectPool pool) { return new PooledObjectHolder(pool); } }
Please don't forget to destroy all the pooled istances once you have finished! How? Well using something like this in PooledQueuedExecutor:
public void Stop () { // waits for all the grep-task to have been queued ... foreach (ISync sync in syncs)
388
IoC Quickstarts
{ sync.Acquire(); } pool.Close(); }
public static void Main(string[] args) { if (args.Length < 3) { Console.Out.WriteLine("usage: {0} regex directory file-pattern [pool-size]", Assembly.GetEntryAssembly().CodeBase); Environment.Exit(1); } string regexPattern = args[0]; string startPath = args[1]; string filePattern = args[2]; int size = 10; try { size = Int32.Parse(args[3]); } catch { } Console.Out.WriteLine ("pool size {0}", size); ParallelGrep grep = new ParallelGrep(size); grep.Recurse(startPath, filePattern, regexPattern); grep.Stop(); }
37.6. AOP
Refer to Chapter 38, AOP QuickStart.
389
Note
To follow this AOP QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.AopQuickStart
390
AOP QuickStart
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 Section 13.3.2, Advice types).
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.
391
AOP QuickStart
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>
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
392
AOP QuickStart 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.'
393
AOP QuickStart
} 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>
394
AOP QuickStart
</object>
You'll will perhaps have noticed that this treatment of pointcuts introduced the concept of an advisor (see Section 13.4, Advisor API in Spring.NET). 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.
395
AOP QuickStart
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>
38.3.1.2. 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.
396
AOP QuickStart '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...
397
AOP QuickStart
<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>
38.3.1.3. 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(); } }
398
AOP QuickStart 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>
399
AOP QuickStart 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 Section 13.3.2.3, Throws advice). 38.3.1.4. 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. 38.3.1.5. 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.
400
AOP QuickStart 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>
38.3.1.6. Configuring advice In case it is not immediately apparent, remember that advice is just a plain old CLR object (a POCO); 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).
401
AOP QuickStart
38.4.1. 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 Chapter 41, SpringAir - Reference Application).
402
AOP QuickStart
403
Note
To follow this Quarts QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.Calculator
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
404
Portable Service Abstraction Quick Start 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
405
Portable Service Abstraction Quick Start 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!
39.3. 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)
406
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"/>
407
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.
408
Portable Service Abstraction Quick Start 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>
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 --> <!-- ================================== -->
409
-->
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
410
Portable Service Abstraction Quick Start 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 Section 5.9.2.1, Example: The PropertyPlaceholderConfigurer 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>
411
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 ---
412
Portable Service Abstraction Quick Start 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.
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
413
Portable Service Abstraction Quick Start 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>
414
Portable Service Abstraction Quick Start 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
415
Selecting the CalculatorService and CalculatorServiceWeaved links will bring you to the standard user interface generated for browsing a web service, as shown below
416
417
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'
418
Note
To follow the Web Form QuickStart load the solution file found in the directory <spring-installdir>\examples\Spring\Spring.WebQuickStart
The ASP.NET MVC2 and MVC3 examples show how you can configure various MVC components using the Spring Dependency Injection container.
Note
To follow this MVC2 QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.MvcQuickStart
To follow this MVC3 QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.Mvc3QuickStart
419
420
In this example there are separate configuration files for test and production configuration. The Services.xml file is in fact the same between the two, and the example will be refactored in future to remove that duplication. The Dao layer in the test configuration is an in-memory version, faking database access, whereas the production version uses an ADO.NET based solution. The pages that comprise the application are located in the directory 'Web/BookTrip'. In that directory is another Web.config that is responsible for configuring that directory's .aspx pages. There are three main pages in the flow of the application. TripForm - form to enter in airports, times, round-trip or one-way Suggested Flights - form to select flights ReservationConfirmationPage - your confirmation ID from the booking process. The XML configuration to configure the TripForm form is shown below
<object type="TripForm.aspx" parent="standardPage"> <property name="BookingAgent" ref="bookingAgent" /> <property name="AirportDao" ref="airportDao" /> <property name="TripValidator" ref="tripValidator" /> <property name="Results"> <dictionary> <entry key="displaySuggestedFlights" value="redirect:SuggestedFlights.aspx" /> </dictionary> </property> </object>
As you can see the various services it needs are set using standard DI techniques. The Results property externalizes the page flow, redirecting to the next page in the flow, SuggestedFlights. The 'parent' attribute lets this page inherit properties from a template. The is located in the top level Web.config file, packaged under the Config directory. The standardPage sets up properties of Spring's base page class, from which all the pages in this application inherit from. (Note that to perform only dependency injection on pages you do not need to inherit from Spring's Page class).
421
This is all you need to set up in order to have values from the Trip object 'marshaled' to and from the web controls. The InitializeDataBindings method set this up, using the Spring Expression Language to define the UI element property that is associate with the model (Trip) property.
The 'Validate' method of the page takes as arguments the object to validate and a IValidator instance. The TripForm property TripValidator is set via dependency injection (as shown above). The validation logic is defined declaratively in the XML configuration file and is shown below.
<v:group id="tripValidator"> <v:required id="departureAirportValidator" test="StartingFrom.AirportCode"> <v:message id="error.departureAirport.required" providers="departureAirportErrors, validationSummary"/> </v:required>
422
<v:group id="destinationAirportValidator"> <v:required test="ReturningFrom.AirportCode"> <v:message id="error.destinationAirport.required" providers="destinationAirportErrors, validationSummary"/> </v:required> <v:condition test="ReturningFrom.AirportCode != StartingFrom.AirportCode" when="ReturningFrom.AirportCode != ''"> <v:message id="error.destinationAirport.sameAsDeparture" providers="destinationAirportErrors, validationSummary"/> </v:condition> </v:group> <v:group id="departureDateValidator"> <v:required test="StartingFrom.Date"> <v:message id="error.departureDate.required" providers="departureDateErrors, validationSummary"/> </v:required> <v:condition test="StartingFrom.Date >= DateTime.Today" when="StartingFrom.Date != DateTime.MinValue"> <v:message id="error.departureDate.inThePast" providers="departureDateErrors, validationSummary"/> </v:condition> </v:group> <v:group id="returnDateValidator" when="Mode == 'RoundTrip'"> <v:required test="ReturningFrom.Date"> <v:message id="error.returnDate.required" providers="returnDateErrors, validationSummary"/> </v:required> <v:condition test="ReturningFrom.Date >= StartingFrom.Date" when="ReturningFrom.Date != DateTime.MinValue"> <v:message id="error.returnDate.beforeDeparture" providers="returnDateErrors, validationSummary"/> </v:condition> </v:group> </v:group>
The validation logic has 'when' clauses so that return dates can be ignored if the Mode property of the Trip object is set to 'RoundTrip'.
41.6. Internationalization
Both image and text based internationalization are supported. You can see this in action by clicking on the English, Srpski, or ###### links on the bottom of the page.
423
424
Note
To follow this Data Access QuickStart load the solution file found in the directory <spring-installdir>\examples\Spring\Spring.DataQuickStart
</objects>
You should change the value of the provider element to correspond to you database and the connection string as appropriate. Please refer to the documentation on the DbProvider abstraction for details particular to your database
425
ADO.NET Data Access QuickStart configuration. You should also install the Northwind database, which is available for SqlServer 2005 from this download location. The minimal schema to support other database providers may be supported in the future. 42.1.1.1. AdoTemplate Configuration The various DAO objects refer to an instance of AdoTemplate which is responsible for performing data access operations. This is declared in ExampleTest.xml as shown below
<object id="adoTemplate" type="Spring.Data.Generic.AdoTemplate, Spring.Data"> <property name="DbProvider" ref="dbProvider"/> <property name="DataReaderWrapperType" value="Spring.Data.Support.NullMappingDataReader, Spring.Data"/> </object>
The property DbProvider refers to the database configuration you previously defined. Also the property DataReaderWrapper is set to the NullMappingDataReader that ships with Spring. This provides convenient default values for null values returned from the database. To read more about AdoTemplate, refer to the chapter, Data access using ADO.NET.
42.1.2. CommandCallback
The code that exercises the use of a CommandCallback is shown below
[Test] public void CallbackDaoTest() { CommandCallbackDao commandCallbackDao = ctx["commandCallbackDao"] as CommandCallbackDao; int count = commandCallbackDao.FindCountWithPostalCode("1010"); Assert.AreEqual(3, count); }
This the minimal configuration required for a DAO object, typically DAO objects in your application will include other configuraiton information, for example properties to specify the maximum size of the result set returned etc. The implementation of the FindCountWithPostalCode is shown below
public virtual int FindCountWithPostalCodeWithDelegate(string postalCode) { // Using anonymous delegates allows you to easily reference the // surrounding parameters for use with the DbCommand processing. return AdoTemplate.Execute<int>(delegate(DbCommand command) { // Do whatever you like with the DbCommand... downcast to get // provider specific funtionality if necesary. command.CommandText = cmdText; DbParameter p = command.CreateParameter(); p.ParameterName = "@PostalCode"; p.Value = postalCode; command.Parameters.Add(p); return (int)command.ExecuteScalar(); }); }
426
ADO.NET Data Access QuickStart Anonymous delegates are used to specify the implementation of the callback function that passes in a DbCommand object. You can then use the DbCommand object as you see fit to access the database. If you are using Spring's delcarative transaction management features then this DbCommand would have its transaction and connection properties based on the context of the surrounding transaction. All resource management for the DbCommand are handled for you by the framework, as well as error reporting on error etc. If you execute the test, it will pass, assuming you haven't modified any data in the Northwind database from its raw installation.
427
Note
To follow this Quarts QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.TxQuickStart
43.2.1. 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); }
428
Transactions QuickStart
43.3. 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); } }
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); }
429
Transactions QuickStart
}
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.
Note
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"/>
430
Transactions QuickStart
<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
431
Transactions QuickStart 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.
43.4. Configuration
The configuration file system-test-local-config.xml shown in the previous program listing includes applicationconfig.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">
432
Transactions QuickStart
<!-- 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"/>
<!-- 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>
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.
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.
433
Transactions QuickStart
[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); } }
<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" <property name="UseDynamicLogger" <property name="LogLevel" </object> value="true"/> value="true"/> value="Info"/>
<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"/>
434
Transactions QuickStart
</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 INFO - Entering DoTransfer;45b6af04-b736-4efa-a489-45462726ddf2;creditAmount=217; debitAmount=217 - 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
435
Note
Even though data access is performed through NHibernate API all Spring.NET provided 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.
Solution Explorer for NHibernate QuickStart Application The data access layer consists of two projects, Spring.Northwind.Dao and Spring.Northwind.Dao.NHibernate. The former contains only the DAO (data access object) interfaces and the latter the NHibernate implementation of those interfaces. The project Spring.Northwind.Service contains a simple service that calls into multiple DAO objects in order to satisfy a fulliment process. The Web project is a ASP.NET web application and the Spring.Northwind.IntegrationTests project contains integration tests for the DAO and Service layers. When you run the application you will see
436
NHibernate QuickStart
Following the link to the customer listing pages bring up the following screen
437
NHibernate QuickStart You can click on the Name of the customer or the Orders link to view that customers orders. Selecting "BOTTM"'s orders brings us to the next page
Notice that the order 11045 has yet to be shipped. If you select 'Process Orders' this will call the Fulliment Service and the order will be processed and shipped.p
438
NHibernate QuickStart
You can then go back to the customer list. If you select the name Elizabeth Lincoln, then you can edit the customer details.
44.3. Implementation
This section discussed the Spring implementation details for each layer.
439
NHibernate QuickStart
{ TEntity Get(TId id); IList<TEntity> GetAll(); }
The ISupportsSave and ISupportsDeleteDao interfaces provide the rest of the CRUD functionality.
public interface ISupportsSave<TEntity, TId> { TId Save(TEntity entity); void Update(TEntity entity); }
The ICustomerDao interface combines these to manage the persistence of customer objects.
public interface ICustomerDao : IDao<Customer, string>, ISupportsDeleteDao<Customer>, ISupportsSave<Customer, string> { }
Similar interfaces are defined to manage Order and Products in IOrderDao and IProductDao respectfully.
440
NHibernate QuickStart
441
NHibernate QuickStart
get { return sessionFactory.GetCurrentSession(); } } protected IList<T> GetAll<T>() where T : class { ICriteria criteria = CurrentSession.CreateCriteria<T>(); return criteria.List<T>(); } }
// If called from a transactional service layer, typically with the transaction // propagation setting set to REQUIRED, then any DAO operations will use the // same settings as started from the transactional layer. [Transaction(ReadOnly = true)] public Customer Get(string customerId) { return CurrentSession.Get<Customer>(customerId); } [Transaction(ReadOnly = true)] public IList<Customer> GetAll() { return GetAll<Customer>(); }
[Transaction(ReadOnly = false)] public string Save(Customer customer) { return (string) CurrentSession.Save(customer); } [Transaction(ReadOnly = false)] public void Update(Customer customer) { CurrentSession.SaveOrUpdate(customer); } [Transaction(ReadOnly = false)] public void Delete(Customer customer) { CurrentSession.Delete(customer); } }
Note
As mentioned in the code comments above, as this application has a distinctly CRUD based component, Spring's Transaction attribute is used to ensure that that method exeuctes as a unit of work. Often in more sophisticated applications even the basic of CRUD are handled through a service layer so as to enforce security, auditing, alterting or enforce business rules. The Repository attribute is used to indicate that this class plays the role of a Repository or a Data Access Object. The term repository comes from modeling terminology popularized by Eric Evan's book Domain Driven Design
442
NHibernate QuickStart (DDD). Those familiar with DDD will note that this implementation is very simply and does not expose higher level persistence functionality to the application, for example FindCustomersWithOpenOrders. How well the role of Repository applies to this implementation is not relevant, and we will often refer to Repository and DAO intechangable when describing the data access layer. What is relevant is that the Repository attribute serves as a marker, a place in the code that can be used to identify methods whose invocation should be intercepted so that additional behavior can be added. In Aspect-Oriented Programming terminology, the Repository attribute represents a pointcut. The behavior that we would like to add to this DAO implementation exception translation. Exception translation from the data access layer to a service layer is important as it shields the service layer from the implementation details of the data access layer. A NHibernate based DAO will throw different exceptions and a ADO.NET based implementation and so on. Spring provides a rich technology neutral data-access exception hierarchy. See Chapter 18, DAO support. Instead of adding exception translation code in each data access method, AOP offers a simple solution. Using Spring's IObjectPostProcessor extension point, each DAO object that is managed by Spring will be automatically wrapped up in a proxy that adds the exception translation behavior. This is done by adding the following object definition to the Spring application context.
<objects> <!-- configure session factory --> <!-- Exception translation object post processor --> <object type="Spring.Dao.Attributes.PersistenceExceptionTranslationPostProcessor, Spring.Data"/> <!-- Configure transaction management strategy --> <!-- DAO objects go here -->
</objects>
The Spring managed DAO object definitions are shown below, referring to a SessionFactory that is created via Spring's LocalSessionFactoryObject. See the file Dao.xml for more details.
<objects xmlns="http://www.springframework.net" xmlns:db="http://www.springframework.net/database"> <!-- Referenced by main application context configuration file --> <description> The Northwind object definitions for the Data Access Objects. </description> <!-- Database Configuration --> <db:provider id="DbProvider" provider="SQLite-1.0.65" connectionString="Data Source=|DataDirectory|Northwind.db;Version=3;FailIfMissing=True;"/> <!-- NHibernate SessionFactory configuration --> <object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21"> <property name="DbProvider" ref="DbProvider"/> <property name="MappingAssemblies"> <list> <value>Spring.Northwind.Dao.NHibernate</value> </list> </property> <property name="HibernateProperties"> <dictionary> <entry key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/> <entry key="dialect" value="NHibernate.Dialect.SQLiteDialect"/> <entry key="connection.driver_class" value="NHibernate.Driver.SQLite20Driver"/> </dictionary> </property> <!-- provides integation with Spring's declarative transaction management features --> <property name="ExposeTransactionAwareSessionFactory" value="true" />
443
NHibernate QuickStart
</object> <!-- Transaction Management Strategy - local database transactions --> <object id="transactionManager" type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21"> <property name="DbProvider" ref="DbProvider"/> <property name="SessionFactory" ref="NHibernateSessionFactory"/> </object> <!-- Exception translation object post processor --> <object type="Spring.Dao.Attributes.PersistenceExceptionTranslationPostProcessor, Spring.Data"/> <!-- Data Access Objects --> <object id="CustomerDao" type="Spring.Northwind.Dao.NHibernate.HibernateCustomerDao, Spring.Northwind.Dao.NHibernate"> <property name="SessionFactory" ref="NHibernateSessionFactory"/> </object> <object id="OrderDao" type="Spring.Northwind.Dao.NHibernate.HibernateOrderDao, Spring.Northwind.Dao.NHibernate"> <property name="SessionFactory" ref="NHibernateSessionFactory"/> </object>
</objects>
Note
It is not required that you use Spring's [Repository] attribute. You can specify an attribute type to the PersistenceExceptionTranslationPostProcessor via the property RepositoryAttributeType to avoid coupling your DAO implementation to Spring.
private IProductDao productDao; private ICustomerDao customerDao; private IOrderDao orderDao; private IShippingService shippingService;
// Properties for the preceding fields omitted for brevity [Transaction] public void ProcessCustomer(string customerId) { //Find all orders for customer Customer customer = CustomerDao.Get(customerId); foreach (Order order in customer.Orders) { if (order.ShippedDate.HasValue)
444
NHibernate QuickStart
{ log.Warn("Order with " + order.Id + " has already been shipped, skipping."); continue; } //Validate Order Validate(order); log.Info("Order " + order.Id + " validated, proceeding with shipping.."); //Ship with external shipping service ShippingService.ShipOrder(order); //Update shipping date order.ShippedDate = DateTime.Now; //Update shipment date OrderDao.Update(order); //Other operations...Decrease product quantity... etc } } private void Validate(Order order) { //no-op - throw exception on error. } } }
What is important to note about this method is that it uses two DAO objects, CustomerDao and OrderDao as well as an additional collaborating service, IShippingService. The fact that all of the collaborating objects are interfaced based means that we can write a unit test for the business functionality of the ProcessCustomer method. Also, the use of the [Transaction] attribute will enable this business processing to proceed as a single unit-ofwork. Spring's declarative transaction management features make it very easy to mix and match different DAO objects with a service method without having to worry about propagating the transaction/connection or hibernate session to each DAO object. The Fullfillment service layer is configured to refer to its collaborating objects as shown below in the configuration file Services.xml
<!-- Property placeholder configurer for database settings --> <object id="FulfillmentService" type="Spring.Northwind.Service.FulfillmentService, Spring.Northwind.Service"> <property name="CustomerDao" ref="CustomerDao"/> <property name="OrderDao" ref="OrderDao"/> <property name="ShippingService" ref="ShippingService"/> </object> <object id="ShippingService" type="Spring.Northwind.Service.FedExShippingService, Spring.Northwind.Service"/ > <tx:attribute-driven/>
445
NHibernate QuickStart integration tests for their data access layers simultaneously and the rollback ensures that the changes made are not persisted. While in the test method, you have a consistent view of the data and can therefore exercise all the methods of your DAO object. The project Spring.Northwind.IntegrationTests shows how this works. As a convenience, an abstract base class is created that in turn inherits from Spring's integration testing class AbstractTransactionalDbProviderSpringContextTests
[TestFixture] public abstract class AbstractDaoIntegrationTests : AbstractTransactionalDbProviderSpringContextTests { protected override string[] ConfigLocations { get { return new string[] { "assembly://Spring.Northwind.Dao.NHibernate/Spring.Northwind.Dao/Dao.xml", "assembly://Spring.Northwind.Service/Spring.Northwind.Service/Services.xml" }; } } }
Note
This unit test is NUnit based but there is similar support available for Microsoft MSTest framework. The exact same object definition files that will be used in the production application are loaded for the integration test. To test the data access layer, you inherit from AbstractDaoIntegrationTests and expose public properties for each DAO implementation you want to test. Within each test method exercise the API of the DAO. This also tests the NHibernate mappings.
[TestFixture] public class NorthwindIntegrationTests : AbstractDaoIntegrationTests { private ICustomerDao customerDao; private IOrderDao orderDao; private ISessionFactory sessionFactory; // These properties will be injected based on type public ICustomerDao CustomerDao { set { customerDao = value; } } public IOrderDao OrderDao { set { orderDao = value; } } public ISessionFactory SessionFactory { set { sessionFactory = value; } } [Test] public void CustomerDaoTests() { Assert.AreEqual(91, customerDao.GetAll().Count); Customer c = new Customer(); c.Id = "MPOLL"; c.CompanyName = "Interface21"; customerDao.Save(c);
446
NHibernate QuickStart
c = customerDao.Get("MPOLL"); Assert.AreEqual(c.Id, "MPOLL"); Assert.AreEqual(c.CompanyName, "Interface21"); //Without flushing, nothing changes in the database: int customerCount = (int)AdoTemplate.ExecuteScalar(CommandType.Text, "select count(*) from Customers"); Assert.AreEqual(91, customerCount); //Flush the session to execute sql in the db. SessionFactoryUtils.GetSession(sessionFactory, true).Flush(); //Now changes are visible outside the session but within the same database transaction customerCount = (int)AdoTemplate.ExecuteScalar(CommandType.Text, "select count(*) from Customers"); Assert.AreEqual(92, customerCount); Assert.AreEqual(92, customerDao.GetAll().Count); c.CompanyName = "SpringSource"; customerDao.Update(c); c = customerDao.Get("MPOLL"); Assert.AreEqual(c.Id, "MPOLL"); Assert.AreEqual(c.CompanyName, "SpringSource"); customerDao.Delete(c);
SessionFactoryUtils.GetSession(sessionFactory, true).Flush(); customerCount = (int)AdoTemplate.ExecuteScalar(CommandType.Text, "select count(*) from Customers"); Assert.AreEqual(92, customerCount); try { c = customerDao.Get("MPOLL"); Assert.Fail("Should have thrown HibernateObjectRetrievalFailureException when finding customer with Id = MPOLL"); } catch (HibernateObjectRetrievalFailureException e) { Assert.AreEqual("Customer", e.PersistentClassName); } } [Test] public void ProductDaoTests() { // ommited for brevity } }
This test uses AdoTemplate to access the database using the standard ADO.NET APIs. It is done to demonstrate that the common configuration of NHibernate is for it not to flush to the database until a commit occurs. If we did not explicitly flush, then no SQL would be sent down to the database and some potential errors would go undetected. Since the test method will rollback the transaction, we don't have to worry about 'dirtying' the database and changing its state.
447
NHibernate QuickStart
public partial class FullfillmentResult : Page { private IFulfillmentService fulfillmentService; private ICustomerEditController customerEditController; public IFulfillmentService FulfillmentService { set { fulfillmentService = value; } } public ICustomerEditController CustomerEditController { set { customerEditController = value; } }
protected void Page_Load(object sender, EventArgs e) { /// code omitted for brevity fulfillmentService.ProcessCustomer(customerEditController.CurrentCustomer.Id); } protected void customerOrders_Click(object sender, EventArgs e) { SetResult("Back"); }
The page is injected with a reference to the FullfillmentService and also another UI component. While Spring's ASP.NET framework supports DI for standard ASP.NET pages and user controls, you can also inherit from Spring's base page class to get added functionality. In this example the use of externalized page flow, or Result Mapping is shown. The Results property indicates the 'how', 'where' and 'what data' to bring along when moving between different web pages and associates it with a logical name "Back". This avoid hardcoding server side transfers or redirects in your code as well as other ASP.NET page references. See the chapter on Spring's ASP.NET Web Framework for more details.
448
Note
To follow this Quarts QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.Scheduling.Quartz.Example
449
Quartz QuickStart
Console.WriteLine("{0}: ExecuteInternal called, user name: {1}, next fire time {2}", DateTime.Now, userName, context.NextFireTimeUtc.Value.ToLocalTime()); } }
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 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
<object name="exampleJob" type="Spring.Scheduling.Quartz.JobDetailObject, Spring.Scheduling.Quartz"> <property name="JobType" value="Spring.Scheduling.Quartz.Example.ExampleJob, Spring.Scheduling.Quartz.Example" /> <!-- We can inject values through JobDataMap --> <property name="JobDataAsMap"> <dictionary> <entry key="UserName" value="Alexandre" /> </dictionary> </property> </object>
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 first time the trigger fires. We then will schedule this job to be executed on 20 second increments of every minute as shown below using Spring's CronTriggerObject which creates a Quartz CronTrigger.
<object id="cronTrigger" type="Spring.Scheduling.Quartz.CronTriggerObject, Spring.Scheduling.Quartz"> <property name="jobDetail" ref="exampleJob" /> <!-- run every 20 second of minute --> <property name="cronExpressionString" value="0/20 * * * * ?" /> </object>
450
Quartz QuickStart
public void DoAdminWork() { Console.WriteLine("{0}: DoAdminWork called, user name: {1}", DateTime.Now, userName); } }
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 MethodInvokingJobDetailFactoryObject as shown below
<object id="adminService" type="Spring.Scheduling.Quartz.Example.AdminService, Spring.Scheduling.Quartz.Example"> <!-- we inject straight to target object --> <property name="UserName" value="admin-service" /> </object> <object id="jobDetail" type="Spring.Scheduling.Quartz.MethodInvokingJobDetailFactoryObject, Spring.Scheduling.Quartz"> <!-- We don't actually need to implement IJob as we can use delegation --> <property name="TargetObject" ref="adminService" /> <property name="TargetMethod" value="DoAdminWork" /> </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 database persistence for Jobs. See the class documentation for additional details.
<object id="simpleTrigger" type="Spring.Scheduling.Quartz.SimpleTriggerObject, Spring.Scheduling.Quartz"> <!-- see the example of method invoking job above --> <property name="jobDetail" ref="jobDetail" /> <!-- 5 seconds --> <property name="startDelay" value="5s" /> <!-- repeat every 5 seconds --> <property name="repeatInterval" value="5s" /> </object>
This creates an instances of Quartz's SimpleTrigger class (as compared to its CronTrigger class used in the previous section). StartDelay and RepeatInterval properties are TimeSpan objects than can be set using the convenient strings such as 10s, 1h, etc, as supported by Spring's custom TypeConverter for TimeSpans. This trigger can then be added to the scheduler's list of registered triggers as shown below.
<object type="Spring.Scheduling.Quartz.SchedulerFactoryObject, Spring.Scheduling.Quartz"> <property name="triggers"> <list> <ref object="cronTrigger" /> <ref object="simpleTrigger" /> </list> </property> </object>
The interleaved output of both these jobs being triggered is shown below.
8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 8/8/2008 1:40:18 1:40:20 1:40:23 1:40:28 1:40:33 1:40:38 1:40:40 1:40:43 1:40:48 1:40:53 1:40:58 1:41:00 1:41:03 PM: PM: PM: PM: PM: PM: PM: PM: PM: PM: PM: PM: PM: DoAdminWork called, user name: Gabriel ExecuteInternal called, user name: Alexandre, next fire time 8/8/2008 1:40:40 PM DoAdminWork called, user name: Gabriel DoAdminWork called, user name: Gabriel DoAdminWork called, user name: Gabriel DoAdminWork called, user name: Gabriel ExecuteInternal called, user name: Alexandre, next fire time 8/8/2008 1:41:00 PM DoAdminWork called, user name: Gabriel DoAdminWork called, user name: Gabriel DoAdminWork called, user name: Gabriel DoAdminWork called, user name: Gabriel ExecuteInternal called, user name: Alexandre, next fire time 8/8/2008 1:41:20 PM DoAdminWork called, user name: Gabriel
451
Note
To follow this NMS QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.NmsQuickStart
452
NMS QuickStart
46.3. Gateways
Gateways represent the service operation to send a message. The client will send a stock request to the server based on the contract defined by the IStockService interface .
public interface IStockService { void Send(TradeRequest tradeRequest); }
The server will send market data to the clients based on the contract defined by the IMarketDataService interface.
public interface IMarketDataService { void SendMarketData(); }
The market data gateway has no method parameters as it is assumed that implementations will manage the data to send internally. The TradeRequest object is one of the data objects that will be exchanged in the application and is discussed in the next section. 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 purposes.
Running xsd.exe on this schema will result in a class that contains properties for each of the element names. A partial code listing of the TradeRequest class is shown below
// This code was generated by a tool. public partial class TradeRequest { public string Ticker { get { return this.tickerField; } set {
453
NMS QuickStart
this.tickerField = value; } } public long Quantity { get { return this.quantityField; } set { this.quantityField = value; } } // Additional properties not shown for brevity. }
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 TradeResponse will be used. The schema for the TradeResponse is shown below
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.springframework.net/nms/common/2008-08-05"> <xs:element name="TradeResponse"> <xs:complexType> <xs:sequence> <xs:element name="Ticker" type="xs:string"/> <xs:element name="Quantity" type="xs:integer"/> <xs:element name="Price" type="xs:decimal"/> <xs:element name="OrderType" type="xs:string"/> <xs:element name="Error" type="xs:boolean"/> <xs:element name="ErrorMessage" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
The TradeResponse type also generated from a schema using xsd.exe. A partial code listing is shown below
// This code was generated by a tool. public partial class TradeResponse { public string Ticker { get { return this.tickerField; } set { this.tickerField = value; } } public long Quantity { get { return this.quantityField; } set { this.quantityField = value; } } // Additional properties not shown for brevity. }
The market data information will be sent using a Hashtable data structure.
454
NMS QuickStart
The
the services, located in the namespace Spring.NmsQuickStart.Server.Services.Stubs, will result in always sending back a error-free trade response message. A realistic implementation would likely have the execution venue and trading service be remote services and the trading service could be implemented as a local transactional service layer that uses spring's declarative transaction management features. The client will receive a TradeResponse message as well as a Hashtable of data representing the market data. The message handle for the client is the class Spring.NmsQuickStart.Client.Handlers.StockAppHandler and is shown below.
public class StockAppHandler { // definition of stockController omitted for brevity. public void Handle(Hashtable data) { // forward to controller to update view stockController.UpdateMarketData(data); } public void Handle(TradeResponse tradeResponse) { // forward to controller to update view stockController.UpdateTrade(tradeResponse); } }
stub
implementations
of
What is important to note about these handlers is that they contain no messaging API artifacts. As such you can write unit and integration tests against these classes independent of the middleware. The missing link between the messaging world and the objects processed by the message handlers are message converters. Spring's messaging helper classes, i.e. SimpleMessageListenerContainer and NmsTemplate use message converters to pass data to the handlers and to send data via messaging for gateway implementations
455
NMS QuickStart
implementation
of
The
Send
method
is
using
NmsTemplate's
ConvertAndSendWithDelegate(object
obj,
method. The anonymous delegate allows you to modify the message properties, such as NMSReplyTo and NMSCorrelationID 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.
MessagePostProcessorDelegate messagePostProcessorDelegate)
The object definition for the NmsStockServiceGateway is shown below along with its dependent object definitions of NmsTemplate and the ConnectionFactory.
456
NMS QuickStart
<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"> <constructor-arg value="APP.STOCK.JOE"/> </object> </property> </object> <object name="NmsTemplate" type="Spring.Messaging.Nms.Core.NmsTemplate, Spring.Messaging.Nms"> <property name="ConnectionFactory" ref="ConnectionFactory"/> <property name="DefaultDestinationName" value="APP.STOCK.REQUEST"/> <property name="MessageConverter" ref="XmlMessageConverter"/> </object> <object id="ConnectionFactory" type="Apache.NMS.ActiveMQ.ConnectionFactory, Apache.NMS.ActiveMQ"> <constructor-arg index="0" value="tcp://localhost:61616"/> </object>
In this example the 'raw' Apache.NMS.ActiveMQ.ConnectionFactory connection factory was used. It would be more efficient resource wise to use Spring's CachingConnectionFactory wrapper class so that connections will not be open and closed for each message send as well as allowing for the caching of other intermediate NMS API objects such as sessions and message producers. A similar configuration interface. is used on the server to configure the that implements class the
Spring.NmsQuickStart.Server.Gateways.MarketDataServiceGateway IMarketDataService
Since the client is also a consumer of messages, on the topic APP.STOCK.MARKETDATA and the queue APP.STOCK.JOE (for Trader Joe!), two message listener containers are defined as shown below.
<nms:listener-container connection-factory="ConnectionFactory"> <nms:listener ref="MessageListenerAdapter" destination="APP.STOCK.JOE" /> <nms:listener ref="MessageListenerAdapter" destination="APP.STOCK.MARKETDATA" pubsub-domain="true"/> </nms:listener-container>
Refer to the messages reference docs for all the available attributes to configure the container and also the section on registering the NMS schema with Spring.. On the server we define a message listener container for the queue APP.STOCK.REQUEST but set the concurrency property to 10 so that 10 threads will be consuming messages from the queue.
<nms:listener-container connection-factory="ConnectionFactory" concurrency="10"> <nms:listener ref="MessageListenerAdapter" destination="APP.STOCK.REQUEST" /> </nms:listener-container>
457
NMS QuickStart
458
Note
To follow this EMS QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.EmsQuickStart
459
The
Send
method
is
using
EmsTemplate's
ConvertAndSendWithDelegate(object
obj,
method. The anonymous delegate allows you to modify the message properties, such as ReplyTo and CorrelationID 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.
MessagePostProcessorDelegate messagePostProcessorDelegate)
The object definition for the EmsStockServiceGateway is shown below along with its dependent object definitions of EmsTemplate and the ConnectionFactory.
<object id="ConnectionFactory" type="Spring.Messaging.Ems.Common.EmsConnectionFactory, Spring.Messaging.Ems"> <constructor-arg index="0" value="tcp://localhost:7222"/> </object> <!-- EMS based implementation of technology neutral IStockServiceGateway --> <object name="StockServiceGateway" type="Spring.EmsQuickStart.Client.Gateways.EmsStockServiceGateway, Spring.EmsQuickStart.Client"> <property name="EmsTemplate" ref="EmsTemplate"/> <property name="DefaultReplyToQueue"> <object type="TIBCO.EMS.Queue, TIBCO.EMS"> <constructor-arg value="APP.STOCK.JOE"/> </object> </property> </object>
<object name="EmsTemplate" type="Spring.Messaging.Ems.Core.EmsTemplate, Spring.Messaging.Ems"> <property name="ConnectionFactory" ref="ConnectionFactory"/> <property name="DefaultDestinationName" value="APP.STOCK.REQUEST"/> <property name="MessageConverter" ref="XmlMessageConverter"/> </object>
460
TIBCO EMS QuickStart In this example the Spring.Messaging.Ems.Common.EmsConnectionFactory connection factory was used. It would be more efficient resource wise to use Spring's CachingConnectionFactory wrapper class so that connections will not be open and closed for each message send as well as allowing for the caching of other intermediate EMS API objects such as sessions and message producers. A similar configuration interface. is used on the server to configure the that implements class the
Spring.EmsQuickStart.Server.Gateways.MarketDataServiceGateway IMarketDataService
Since the client is also a consumer of messages, on the topic APP.STOCK.MARKETDATA and the queue APP.STOCK.JOE (for Trader Joe!), two message listener containers are defined as shown below.
<ems:listener-container connection-factory="ConnectionFactory"> <ems:listener ref="MessageListenerAdapter" destination="APP.STOCK.JOE" /> <ems:listener ref="MessageListenerAdapter" destination="APP.STOCK.MARKETDATA" pubsub-domain="true"/> </ems:listener-container>
Refer to the messages reference docs for all the available attributes to configure the container and also the section on registering the EMS schema with Spring.. On the server we define a message listener container for the queue APP.STOCK.REQUEST but set the concurrency property to 10 so that 10 threads will be consuming messages from the queue.
<ems:listener-container connection-factory="ConnectionFactory" concurrency="10"> <ems:listener ref="MessageListenerAdapter" destination="APP.STOCK.REQUEST" /> </ems:listener-container>
461
462
Note
To follow this MSMQ QuickStart load the solution file found in the directory <spring-installdir>\examples\Spring\Spring.MsmqQuickStart
Note
You must create the queues mentioned previously using standard Windows Computer Management console to manage MSMQ. This article [http://www.worldofasp.net/tut/MSMQ/ Basic_Introduction_about_MSMQ_in_NET_Framework_98.aspx] covers the basics of creating the queus in the management console. 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..
48.3. Gateways
The gateway interfaces are the same as those described in the NMS QuickStart here.
463
MSMQ QuickStart
48.6. MessageConverters
The message converter used is Spring.Messaging.Support.Converters.XmlMessageConverter. It is configured by specifying the data types that will be send and received. Here is a configuration example for types generated from the XML Schema and a plain string.
<object id="xmlMessageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"> <property name="TargetTypes"> <list> <value>Spring.MsmqQuickStart.Common.Data.TradeRequest, Spring.MsmqQuickStart.Common</value> <value>Spring.MsmqQuickStart.Common.Data.TradeResponse, Spring.MsmqQuickStart.Common</value> <value>System.String, mscorlib</value> </list> </property> </object>
The
Send
method
is
using
MessageQueueTemplate's
ConvertAndSend(object
obj,
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.
MessagePostProcessorDelegate messagePostProcessorDelegate)
464
MSMQ QuickStart 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"> <property name="MessageQueueTemplate" ref="messageQueueTemplate"/> <property name="DefaultResponseQueueObjectName" value="responseTxQueue"/> </object> <object id="messageQueueTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging"> <property name="DefaultMessageQueueObjectName" value="requestTxQueue"/> <property name="MessageConverterObjectName" value="xmlMessageConverter"/> </object> <object id="xmlMessageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"> <property name="TargetTypes"> <list> <value>Spring.MsmqQuickStart.Common.Data.TradeRequest, Spring.MsmqQuickStart.Common</value> <value>Spring.MsmqQuickStart.Common.Data.TradeResponse, Spring.MsmqQuickStart.Common</value> <value>System.String, mscorlib</value> </list> </property> </object> <object id="requestTxQueue" type="Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging"> <property name="Path" value=".\Private$\request.txqueue"/> <property name="MessageReadPropertyFilterSetAll" value="true"/> </object> <object id="responseTxQueue" type="Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging"> <property name="Path" value=".\Private$\response.joe.txqueue"/> <property name="MessageReadPropertyFilterSetAll" value="true"/> </object>
Since
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 links.
<!-- MSMQ Transaction Manager --> <object id="messageQueueTransactionManager" type="Spring.Messaging.Core.MessageQueueTransactionManager, Spring.Messaging"/> <!-- Message Listener Container that uses MSMQ transactional for receives --> <object id="transactionalMessageListenerContainer" type="Spring.Messaging.Listener.TransactionalMessageListenerContainer, Spring.Messaging"> <property name="MessageQueueObjectName" value="responseTxQueue"/> <property name="PlatformTransactionManager" ref="messageQueueTransactionManager"/> <property name="MessageListener" ref="messageListenerAdapter"/> <property name="MessageTransactionExceptionHandler" ref="sendToQueueExceptionHandler"/> </object> <!-- Delegate to plain CLR object for message handling --> <object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging"> <property name="HandlerObject" ref="stockAppHandler"/> <property name="DefaultHandlerMethod" value="Handle"/> <property name="MessageConverterObjectName" value="xmlMessageConverter"/> </object> <object id="sendToQueueExceptionHandler" type="Spring.Messaging.Listener.SendToQueueExceptionHandler, Spring.Messaging"> <property name="MessageQueueObjectName" value="deadTxQueue"/> </object> <object id="deadTxQueue" type="Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging"> <property name="Path" value=".\Private$\dead.queue"/> <property name="MessageReadPropertyFilterSetAll" value="true"/> </object>
the
client
also
needs
to
465
MSMQ QuickStart A 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. similar configuration is used on the server to
466
Note
To follow this Quarts QuickStart load the solution file found in the directory <spring-install-dir>
\examples\Spring\Spring.WcfQuickStart
and the implementation is straightforward, only adding a property that controls how long each method should sleep. An abbreviated listing of the implementation is shown below
public class CalculatorService : ICalculator { private int sleepInSeconds; public int SleepInSeconds { get { return sleepInSeconds; } set { sleepInSeconds = value; } } public double Add(double n1, double n2) { Thread.Sleep(sleepInSeconds*1000); return n1 + n2; }
467
WCF QuickStart
}
Look at the standard WCF configuration section in App.config for additional configuration details. In that section you will see that the name of the WCF service corresponds to the name of the service object inside the spring container.
468
WCF QuickStart
Divide(11, 2) : 5.5 Multiply(2, 5) : 10 Subtract(7, 4) : 3 ServerApp Calculator Add(1, 1) : 2 Divide(11, 2) : 5.5 Multiply(2, 5) : 10 Subtract(7, 4) : 3 --- Press <return> to continue ---
469
470
As you can easily see the <beans> and <bean> elements are replaced by <objects> and <object> elements. The class definition in Spring.Java contains the fully qualified class name. The Spring.NET version also contains the fully qualified classname but in addition specifies the name of the assembly where that type is located. This is necessary since .NET does not have a 'classpath' concept. Assembly names in .NET can have up to four parts to describe the exact version. The other XML Schema elements in Spring.NET are the same as in Spring.Java's DTD except for specifying string based key value pairs. In Java this is represented by the java.util.Properties class and the xml element is name <props> as shown below
<property name="people"> <props> <prop key="PennAndTeller">The magic property</prop> <prop key="GeorgeCarlin">The funny property</prop> </props> </property>
In .NET the analogous class is System.Collections.Specialized.NameValueCollection and is represented by the xml element <name-values>. The listing of the elements also follows the .NET convention of application configuration files using the <add> element with 'key' and 'value' attributes. This is show below
471
472
The <configSections> and <section> elements are a standard part of the .NET application configuration file. These elements are used to register an instance of IConfigurationSectionHandler and associate it with another xml element in the file, in this case the <context> and <objects> elements. The following code segment is used to retrieve the IApplicationContext from the .NET application configuration file.
IApplicationContext ctx = ConfigurationUtils.GetSection("spring/context") as IApplicationContext;
In order to enforce the usage of the named configuration section spring/context the preferred instantiation mechanism is via the use of the registry class ContextRegistry as shown below
IApplicationContext ctx = ContextRegistry.GetContext();
473
<object id="prototypeTarget" type="Spring.Aop.Framework.ProxyFactoryObject"> <property name="interceptorNames" value="nopInterceptor,target"/> <!-- not valid! --> </object> </objects>
In Spring.NET, the InterceptorNames property of the ProxyFactoryObject can only be used to specify the names of interceptors. Use the TargetName property to specify the name of the target object that is to be proxied. The main reason for not supporting exactly the same style of configuration as Spring Java is because this 'feature' is regarded as a legacy holdover from Rod Johnson's initial Spring AOP implementation, and is currently only kept as-is (in Spring Java) for reasons of backward compatibility.
474
475
public class HibernateCustomerDao : ICustomerDao { private HibernateTemplate hibernateTemplate; public ISessionFactory SessionFactory { set { hibernateTemplate = new HibernateTemplate(value); } } public Customer SaveOrUpdate(Customer customer) { hibernateTemplate.SaveOrUpdate(customer); return customer; } }
The HibernateTemplate class provides many methods that mirror the methods exposed on the Hibernate Session interface, in addition to a number of convenience methods such as the one shown above. If you need access to the Session to invoke methods that are not exposed on the HibernateTemplate, you can always drop down to a callback-based approach like so.
public class HibernateCustomerDao : ICustomerDao { private HibernateTemplate hibernateTemplate; public ISessionFactory SessionFactory { set { hibernateTemplate = new HibernateTemplate(value); } } public Customer SaveOrUpdate(Customer customer) { return HibernateTemplate.Execute(
476
Using the anonymous delegate is particularly convenient when you would otherwise be passing various method parameter calls to the interface based version of this callback. Furthermore, when using generics, you can avoid the typecast and write code like the following
IList<Supplier> suppliers = HibernateTemplate.ExecuteFind<Supplier>( delegate(ISession session) { return session.CreateQuery("from Supplier s were s.Code = ?") .SetParameter(0, code) .List<Supplier>(); });
where code is a variable in the surrounding block, accessible inside the anonymous delegate implementation. A callback implementation effectively can be used for any Hibernate data access. HibernateTemplate will ensure that Session instances are properly opened and closed, and automatically participate in transactions. The template instances are thread-safe and reusable, they can thus be kept as instance variables of the surrounding class. For simple single step actions like a single Find, Load, SaveOrUpdate, or Delete call, HibernateTemplate offers alternative convenience methods that can replace such one line callback implementations. Furthermore, Spring provides a convenient HibernateDaoSupport base class that provides a SessionFactory property for receiving a SessionFactory and for use by subclasses. In combination, this allows for very simple DAO implementations for typical requirements:
public class HibernateCustomerDao : HibernateDaoSupport, ICustomerDao { public Customer SaveOrUpdate(Customer customer) { HibernateTemplate.SaveOrUpdate(customer); return customer; } }
477
Classic Spring Usage This code will not translate the Hibernate exception to a generic DataAccessException.
<!-- Transaction Interceptor --> <object id="transactionInterceptor" type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data"> <property name="TransactionManager" ref="transactionManager"/> <property name="TransactionAttributeSource" ref="attributeTransactionAttributeSource"/> </object> <object id="attributeTransactionAttributeSource" type="Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data"> </object>
Granted this is a bit verbose and hard to grok at first sight - however you only need to grok this once as it is 'boiler plate' XML you can reuse across multiple projects. What these object definitions are doing is to instruct Spring's to look for all objects within the IoC configuration that have the [Transaction] attribute and then apply the AOP transaction interceptor to them based on the transaction options contained in the attribute. The attribute serves both as a pointcut and as the declaration of transactional option information. Since this XML fragment is not tied to any specific object references it can be included in its own file and then imported via the <import> element. In examples and test code this XML configuration fragment is named autoDeclarativeServices.xml See Section 5.2.2.3, Composing XML-based configuration metadata for more information. The classes and their roles in this configuration fragment are listed below TransactionInterceptor is the AOP advice responsible for performing transaction management functionality. TransactionAttributeSourceAdvisor is an AOP Advisor that holds the TransactionInterceptor, which is the advice, and a pointcut (where to apply the advice), in the form of a TransactionAttributeSource. AttributesTransactionAttributeSource is an implementation of the ITransactionAttributeSource interface that defines where to get the transaction metadata defining the transaction semantics (isolation level, propagation behavior, etc) that should be applied to specific methods of specific classes. The transaction metadata is specified via implementations of the ITransactionAttributeSource interface. This example shows the use of the
478
Classic Spring Usage implementation Spring.Transaction.Interceptor.AttributesTransactionAttributeSource to obtain that information from standard .NET attributes. By the very nature of using standard .NET attributes, the attribute serves double duty in identifying the methods where the transaction semantics apply. Alternative implementations of ITransactionAttributeSource available are MatchAlwaysTransactionAttributeSource, NameMatchTransactionAttributeSource, or MethodMapTransactionAttributeSource. MatchAlwaysTransactionAttributeSource is configured with a ITransactionAttribute instance that is applied to all methods. The shorthand string representation, i.e. PROPAGATION_REQUIRED can be used AttributesTransactionAttributeSource : Use a standard. .NET attributes to specify the transactional information. See TransactionAttribute class for more information. NameMatchTransactionAttributeSource allows ITransactionAttributes to be matched by method name. The NameMap IDictionary property is used to specify the mapping. For example
<object name="nameMatchTxAttributeSource" type="Spring.Transaction.Interceptor.NameMatchTransactionAttributeSource, Spring.Data" <property name="NameMap"> <dictionary> <entry key="Execute" value="PROPAGATION_REQUIRES_NEW, -ApplicationException"/> <entry key="HandleData" value="PROPAGATION_REQUIRED, -DataHandlerException"/> <entry key="Find*" value="ISOLATION_READUNCOMMITTED, -DataHandlerException"/> </dictionary> </property> </object>
Key values can be prefixed and/or suffixed with wildcards as well as include the full namespace of the containing class. MethodMapTransactionAttributeSource : Similar to NameMatchTransactionAttributeSource but specifies that only fully qualified method names (i.e. type.method, assembly) and wildcards can be used at the start or end of the method name for matching multiple methods. DefaultAdvisorAutoProxyCreator: looks for Advisors in the context, and automatically creates proxy objects which are the transactional wrappers Refer to the following section for a more convenient way to achieve the same goal of declarative transaction management using attributes.
479
Note the use of an inner object definition for the target which will make it impossible to obtain an unproxied reference to the TestObjectManager. As can be seen in the above definition, the TransactionAttributes property holds a collection of name/value pairs. The key of each pair is a method or methods (a * wildcard ending is optional) to apply transactional semantics to. Note that the method name is not qualified with a package name, but rather is considered relative to the class of the target object being wrapped. The value portion of the name/value pair is the TransactionAttribute itself that needs to be applied. When specifying it as a string value as in this example, it's in String format as defined by TransactionAttributeConverter. This format is:
PROPAGATION_NAME,ISOLATION_NAME,readOnly,timeout_NNNN,+Exception1,-Exception2
Note that the only mandatory portion of the string is the propagation setting. The default transactions semantics which apply are as follows: Exception Handling: All exceptions thrown trigger a rollback. Transactions are read/write Isolation Level: TransactionDefinition.ISOLATION_DEFAULT Timeout: TransactionDefinition.TIMEOUT_DEFAULT Multiple rollback rules can be specified here, comma-separated. A - prefix forces rollback; a + prefix specifies commit. Under the covers the IDictionary of name value pairs will be converted to an instance of
NameMatchTransactionAttributeSource
The string used for PROPAGATION_NAME are those defined on the Spring.Transaction.TransactionPropagation enumeration, namely Required, Supports, Mandatory, RequiresNew, NotSupported, Never, Nested. The string used for ISOLATION_NAME are those defined on the System.Data.IsolationLevel enumberateion, namely ReadCommitted, ReadUncommitted, RepeatableRead, Serializable. The TransactionProxyFactoryObject allows you to set optional "pre" and "post" advice, for additional interception behavior, using the "PreInterceptors" and "PostInterceptors" properties. Any number of pre and post advices can be set, and their type may be Advisor (in which case they can contain a pointcut), MethodInterceptor or any advice type supported by the current Spring configuration (such as ThrowsAdvice, AfterReturningAdvice or BeforeAdvice, which are supported by default.) These advices must support a shared-instance model. If you need transactional proxying with advanced AOP features such as stateful mixins, it's normally best to use the generic ProxyFactoryObject, rather than the TransactionProxyFactoryObject convenience proxy creator.
480
Classic Spring Usage pattern of method names, Save*, Find*, etc. This commonality can be placed in an abstract object definition, which other object definitions refer to and change only the configuration information that is different. An abstract object definition is shown below
<object id="txProxyTemplate" abstract="true" type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data"> <property name="PlatformTransactionManager" ref="adoTransactionManager"/> <property name="TransactionAttributes"> <name-values> <add key="Save*" value="PROPAGATION_REQUIRED"/> <add key="Delete*" value="PROPAGATION_REQUIRED"/> </name-values> </property> </object>
The ProxyFactoryObject will create a proxy for the Target, i.e. a TestObjectManager instance. An inner object definition could also have been used such that it would make it impossible to obtain an unproxied object from the container. The interceptor name refers to the following definition.
<object id="transactionInterceptor" type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data"> <property name="TransactionManager" ref="adoTransactionManager"/> <!-- note do not have converter from string to this property type registered --> <property name="TransactionAttributeSource" ref="methodMapTransactionAttributeSource"/> </object> <object name="methodMapTransactionAttributeSource" type="Spring.Transaction.Interceptor.MethodMapTransactionAttributeSource, Spring.Data"> <property name="MethodMap"> <dictionary> <entry key="Spring.Data.TestObjectManager.SaveTwoTestObjects, Spring.Data.Integration.Tests"
481
The transaction options for each method are specified using a dictionary containing the class name + method name, assembly as the key and the value is of the form <Propagation Behavior>, <Isolation Level>, <ReadOnly>, -Exception, +Exception All but the propagation behavior are optional. The + and - are used in front of the name of an exception. Minus indicates to rollback if the exception is thrown, the Plus indicates to commit if the exception is thrown.
482
</objects>
Note
The 'xsi:schemaLocation' fragment is not actually required, but can be included to reference a local copy of a schema (which can be useful during development) and assumes the XML editor will look to that location and load the schema. The above Spring XML configuration fragment is boilerplate that you can copy and paste (!) and then plug <object/> definitions into like you have always done. However, the entire point of using custom schema tags is to make configuration easier. The rest of this chapter gives an overview of custom XML Schema based configuration that are included with the release.
483
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
Tip
You are strongly encouraged to look at the 'spring-tx-1.1.xsd' file that ships with the Spring distribution. This file is (of course), the XML Schema for Spring's transaction configuration, and covers all of the various tags in the tx namespace, including attribute defaults and suchlike. This file is documented inline, and thus the information is not repeated here in the interests of adhering to the DRY (Don't Repeat Yourself) principle. In the interest of completeness, to use the tags in the tx schema, you need to have the following preamble at the top of your Spring XML configuration file; the emboldened text in the following snippet references the correct schema so that the tags in the tx namespace are available to you.
<?xml version="1.0" encoding="UTF-8"?> <object xmlns="http://www.springframework.net" xmlns:aop="http://www.springframework.net/aop" xmlns:tx="http://www.springframework.net/tx"> <!-<object/>
<!-- <tx/> transaction definitions here --> <!-- <aop/> AOP definitions here --> </object>
Note
Often when using the tags in the tx namespace you will also be using the tags from the aop namespace (since the declarative transaction support in Spring is implemented using AOP). The above XML snippet contains the relevant lines needed to reference the aop schema so that the tags in the aop namespace are available to you. You will also need to configure the AOP and Transaction namespace parsers in the main .NET application configuration file as shown below
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring">
484
You will also need to configure the AOP namespace parser in the main .NET application configuration file as shown below
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" /> </parsers> </spring> </configuration>
485
You will also need to configure the Database namespace parser in the main .NET application configuration file as shown below
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /> </parsers> </spring> </configuration>
You need to configure the remoting namespace parser in the main .NET application configuration file as shown below
486
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.ServiceModel.Config.WcfNamespaceParser, Spring.Services" /> </parsers> </spring> </configuration>
You will also need to configure the remoting namespace parser in the main .NET application configuration file as shown below
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services" /> </parsers>
487
You will also need to configure the remoting namespace parser in the main .NET application configuration file as shown below
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Messaging.Nms.Config.NmsNamespaceParser, Spring.Messaging.Nms" /> </parsers> </spring> </configuration>
You will also need to configure the validation namespace parser in the main .NET application configuration file as shown below
488
Note
As of Spring.NET 1.2.0 it is no longer necessary to explicitly configure the namespace parsers that come with Spring via a custom section in App.config. You will still need to register custom namespace parsers if you are writing your own.
<configuration> <configSections> <sectionGroup name="spring"> <!-- other Spring config sections handler like context, typeAliases, etc not shown for brevity --> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Validation.Config.ValidationNamespaceParser, Spring.Core" /> </parsers> </spring> </configuration>
489
490
The emphasized line contains an extension base for all tags that will be identifiable (meaning they have an id attribute that will be used as the object identifier in the container). We are able to use this attribute because we imported the Spring-provided 'objects' namespace. The vs: prefixed elements are for better integration with intellisense in VS.NET. The above schema will be used to configure Regex objects, directly in an XML application context file using the <myns:regex/> element.
<myns:regex id="usZipCodeRegex" pattern="(^\d{5}$)|(^\d{5}-\d{4}$)" options="Compiled"/>
Note that after we've created the infrastructure classes, the above snippet of XML will essentially be exactly the same as the following XML snippet. In other words, we're just creating an object in the container, identified by the name 'usZipCodeRegex' of type Regex, with a couple of constructor arguments set.
<object id="usZipCodeRegex" type="System.Text.RegularExpressions.Regex, System"> <constructor-arg name="pattern" value="(^\d{5}$)|(^\d{5}-\d{4}$)"/> <constructor-arg name="options" value="Compiled"/> </object>
Note
The schema-based approach to creating configuration format allows for tight integration with an IDE that has a schema-aware XML editor. Using a properly authored schema, you can use intellisense to have a user choose between several configuration options defined in the enumeration. The schema for creating IDbProvider instances shows the use of XSD enumerations.
491
Notice that there isn't actually a whole lot of parsing logic in this class. Indeed... the NamespaceParserSupport class has a built in notion of delegation. It supports the registration of any number of IObjectDefinitionParser instances, to which it will delegate to when it needs to parse an element in it's namespace. This clean separation of concerns allows an INamespaceParser to handle the orchestration of the parsing of all of the custom elements in it's namespace, while delegating to IObjectDefinitionParsers to do the grunt work of the XML parsing; this means that each IObjectDefinitionParser will contain just the logic for parsing a single custom element, as we can see in the next step. To help in the registration of the parser for this namespace, the NamespaceParser attribute is used to map the XML namespace string, i.e. http://www.mycompany.com/schema/myns, to the location of the XML Schema file as an embedded assembly resource.
namespace CustomNamespace { public class RegexObjectDefinitionParser : AbstractSimpleObjectDefinitionParser { protected override Type GetObjectType(XmlElement element) { return typeof (Regex); } protected override void DoParse(XmlElement element, ObjectDefinitionBuilder builder) { // this will never be null since the schema explicitly requires that a value be supplied string pattern = element.GetAttribute("pattern"); builder.AddConstructorArg(pattern); // this however is an optional property string options = element.GetAttribute("options"); if (StringUtils.HasText(options)) { RegexOptions regexOptions = (RegexOptions)Enum.Parse(typeof (RegexOptions), options);
492
We use the Spring-provided AbstractSingleObjectDefinitionParser to handle a lot of the basic grunt work of creating a single IObjectDefinition. We supply the AbstractSingleObjectDefinitionParser superclass with the type that our single IObjectDefinition will represent. In this simple case, this is all that we need to do. The creation of our single IObjectDefinition is handled by the AbstractSingleObjectDefinitionParser superclass, as is the extraction and setting of the object definition's unique identifier. The property ShouldGenerateIdAsFallback will generate a throw-away object id incase one is not specified, this is useful when nesting object definitions.
C.5.1. NamespaceParsersSectionHandler
The handler is of the type and is registered with .NET in the normal manner. The custom configuration section will simply point to the INamespaceParser implementation that has the Namespace attribute. For our example, we need to write the following:
Spring.Context.Support.NamespaceParsersSectionHandler
<configuration> <configSections> <sectionGroup name="spring"> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="CustomNamespace.MyNamespaceParser, CustomNamespace" /> </parsers> </spring> </configuration>
custom
configuration
section
493
494
<?xml version="1.0" encoding="UTF-8" ?> <xs:schema xmlns="http://www.springframework.net" xmlns:xs="http://www.w3.org/2001/ XMLSchema" xmlns:vs="http://schemas.microsoft.com/Visual-Studio-Intellisense" targetNamespace="http:// www.springframework.net" elementFormDefault="qualified" attributeFormDefault="unqualified" vs:friendlyname="Spring.NET Configuration" vs:ishtmlschema="false" vs:iscasesensitive="true" vs:requireattributequotes="true" vs:defaultnamespacequalifi <xs:annotation> <xs:documentation> Spring Objects XML Schema Definition Based on Spring Beans DTD, authored by Rod Johnson & Juergen Hoeller Author: Griffin Caprio This defines a simple and consistent way of creating a namespace of managed objects configured by a Spring XmlObjectFactory. This document type is used by most Spring functionality, including web application contexts, which are based on object factories. Each object element in this document defines an object. Typically the object type (System.Type is specified, along with plain vanilla object properties. Object instances can be "singletons" (shared instances) or "prototypes" (independent instances). References among objects are supported, i.e. setting an object property to refer to another object in the same factory or an ancestor factory. As alternative to object references, "inner object definitions" can be used. Singleton flags and names of such "inner object" are always ignored: Inner object are anonymous prototypes. There is also support for lists, dictionaries, and sets. </xs:documentation> </xs:annotation> <xs:annotation> <xs:documentation>Defines a base type for any required string. Defines a string with a minimum length of 0</xs:documentation> </xs:annotation> <xs:simpleType name="nonNullString"> <xs:restriction base="xs:string"> <xs:minLength value="0"/> </xs:restriction> </xs:simpleType> <xs:annotation> <xs:documentation> Element containing informative text describing the purpose of the enclosing element. Always optional. Used primarily for user documentation of XML object definition documents. </xs:documentation> </xs:annotation> <xs:simpleType name="description"> <xs:restriction base="nonNullString"/> </xs:simpleType> <xs:complexType name="valueObject"> <xs:simpleContent> <xs:extension base="xs:string"> <xs:attribute name="type" type="nonNullString" use="optional"/> </xs:extension> </xs:simpleContent> </xs:complexType> <xs:complexType name="expression"> <xs:sequence> <xs:element name="property" type="property" minOccurs="0" maxOccurs="2"/> </xs:sequence> <xs:attribute name="value" type="nonNullString" use="required"/> </xs:complexType> <!--
495
Spring.NET's spring-objects.xsd
Defines a reference to another object in this factory or an external factory (parent or included factory). --> <xs:complexType name="objectReference"> <xs:attribute name="object" type="nonNullString" use="optional"/> <xs:attribute name="local" type="xs:IDREF" use="optional"/> <xs:attribute name="parent" type="nonNullString" use="optional"/> <!-References must specify a name of the target object. The "object" attribute can reference any name from any object in the context, to be checked at runtime. Local references, using the "local" attribute, have to use object ids; they can be checked by this DTD, thus should be preferred for references within the same object factory XML file. --> </xs:complexType> <!-- Defines a reference to another object or a type. --> <xs:complexType name="objectOrClassReference"> <xs:attribute name="object" type="nonNullString" use="optional"/> <xs:attribute name="local" type="xs:IDREF" use="optional"/> <xs:attribute name="type" type="nonNullString" use="optional"/> </xs:complexType> <xs:group name="objectList"> <xs:sequence> <xs:element name="description" type="description" minOccurs="0"/> <xs:choice> <xs:element name="object" type="vanillaObject"/> <!-Defines a reference to another object in this factory or an external factory (parent or included factory). --> <xs:element name="ref" type="objectReference"/> <!-Defines a string property value, which must also be the id of another object in this factory or an external factory (parent or included factory). While a regular 'value' element could instead be used for the same effect, using idref in this case allows validation of local object ids by the xml parser, and name completion by helper tools. --> <xs:element name="idref" type="objectReference"/> <!-A objectList can contain multiple inner object, ref, collection, or value elements. Lists are untyped, pending generics support, although references will be strongly typed. A objectList can also map to an array type. The necessary conversion is automatically performed by AbstractObjectFactory. --> <xs:element name="list"> <xs:complexType> <xs:group ref="objectList" minOccurs="0" maxOccurs="unbounded"/> <xs:attribute name="element-type" type="nonNullString" use="optional"/> </xs:complexType> </xs:element> <!-A set can contain multiple inner object, ref, collection, or value elements. Sets are untyped, pending generics support, although references will be strongly typed. --> <xs:element name="set"> <xs:complexType> <xs:group ref="objectList" minOccurs="0" maxOccurs="unbounded"/> </xs:complexType> </xs:element> <!-A Spring map is a mapping from a string key to object (a .NET IDictionary). Maps may be empty. --> <xs:element name="dictionary" type="objectMap"/> <!-Name-values elements differ from map elements in that values must be strings. Name-values may be empty. --> <xs:element name="name-values" type="objectNameValues"/> <!-Contains a string representation of a property value.
496
Spring.NET's spring-objects.xsd
The property may be a string, or may be converted to the required type using the System.ComponentModel.TypeConverter machinery. This makes it possible for application developers to write custom TypeConverter implementations that can convert strings to objects. Note that this is recommended for simple objects only. Configure more complex objects by setting properties to references to other objects. --> <xs:element name="value" type="valueObject"/> <!-Contains a string representation of an expression. --> <xs:element name="expression" type="expression"/> <!-Denotes a .NET null value. Necessary because an empty "value" tag will resolve to an empty String, which will not be resolved to a null value unless a special TypeConverter does so. --> <xs:element name="null"/> </xs:choice> </xs:sequence> </xs:group> <xs:complexType name="objectNameValues"> <xs:sequence> <!-The "value" attribute is the string value of the property. The "key" attribute is the name of the property. --> <xs:element name="add" minOccurs="0" maxOccurs="unbounded"> <xs:complexType mixed="true"> <xs:attribute name="key" type="nonNullString" use="required"/> <xs:attribute name="value" use="required" type="xs:string"/> <xs:attribute name="delimiters" use="optional" type="xs:string"/> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="importElement"> <xs:attribute name="resource" type="nonNullString" use="required"/> </xs:complexType> <xs:complexType name="aliasElement"> <xs:attribute name="name" type="nonNullString" use="required"/> <xs:attribute name="alias" type="nonNullString" use="required"/> </xs:complexType> <xs:complexType name="objectMap"> <xs:sequence> <xs:element type="mapEntryElement" name="entry" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="key-type" type="nonNullString" use="optional"/> <xs:attribute name="value-type" type="nonNullString" use="optional"/> </xs:complexType> <xs:complexType name="mapEntryElement"> <xs:sequence> <xs:element type="mapKeyElement" name="key" minOccurs="0" maxOccurs="1"/> <xs:group ref="objectList" minOccurs="0" maxOccurs="1"/> </xs:sequence> <xs:attribute name="key" type="nonNullString" use="optional"/> <xs:attribute name="value" type="nonNullString" use="optional"/> <xs:attribute name="expression" type="nonNullString" use="optional"/> <xs:attribute name="key-ref" type="nonNullString" use="optional"/> <xs:attribute name="value-ref" type="nonNullString" use="optional"/> </xs:complexType> <xs:complexType name="mapKeyElement"> <xs:group ref="objectList" minOccurs="1"/> </xs:complexType> <xs:annotation> <xs:documentation>Defines constructor argument.</xs:documentation> </xs:annotation> <xs:complexType name="lookupMethod"> <xs:attribute name="name" type="nonNullString" use="required"/> <xs:attribute name="object" type="nonNullString" use="required"/> </xs:complexType> <xs:complexType name="constructorArgument">
497
Spring.NET's spring-objects.xsd
<xs:group ref="objectList" minOccurs="0"/> <!-The constructor-arg tag can have an optional named parameter attribute, to specify a named parameter in the constructor argument list. --> <xs:attribute name="name" type="nonNullString" use="optional"/> <!-The constructor-arg tag can have an optional index attribute, to specify the exact index in the constructor argument list. Only needed to avoid ambiguities, e.g. in case of 2 arguments of the same type. --> <xs:attribute name="index" type="nonNullString" use="optional"/> <!-The constructor-arg tag can have an optional type attribute, to specify the exact type of the constructor argument. Only needed to avoid ambiguities, e.g. in case of 2 single argument constructors that can both be converted from a String. --> <xs:attribute name="type" type="nonNullString" use="optional"/> <xs:attribute name="value" type="nonNullString" use="optional"/> <xs:attribute name="expression" type="nonNullString" use="optional"/> <xs:attribute name="ref" type="nonNullString" use="optional"/> </xs:complexType> <xs:annotation> <xs:documentation>Defines property.</xs:documentation> </xs:annotation> <xs:complexType name="property"> <xs:group ref="objectList" minOccurs="0"/> <!-- The property name attribute is the name of the objects property. --> <xs:attribute name="name" type="nonNullString" use="required"/> <xs:attribute name="value" type="nonNullString" use="optional"/> <xs:attribute name="expression" type="nonNullString" use="optional"/> <xs:attribute name="ref" type="nonNullString" use="optional"/> </xs:complexType> <xs:annotation> <xs:documentation>Defines a single named object.</xs:documentation> </xs:annotation> <xs:complexType name="vanillaObject"> <xs:sequence> <xs:element name="description" type="description" minOccurs="0" maxOccurs="1"/> <!-Object definitions can specify zero or more constructor arguments. They correspond to either a specific index of the constructor argument list or are supposed to be matched generically by type. This is an alternative to "autowire constructor". --> <xs:element name="constructor-arg" type="constructorArgument" minOccurs="0" maxOccurs="unbounded"/ > <!-Object definitions can have zero or more properties. Spring supports primitives, references to other objects in the same or related factories, lists, dictionaries and properties. --> <xs:element name="property" type="property" minOccurs="0" maxOccurs="unbounded"/> <!-Object definitions can specify zero or more lookup-methods. --> <xs:element name="lookup-method" type="lookupMethod" minOccurs="0" maxOccurs="unbounded"/> <!-- Object definitions can have zero or more replaced-methods. --> <xs:element name="replaced-method" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="arg-type" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:attribute name="match" type="nonNullString" use="required"/> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="name" type="nonNullString" use="required"/> <xs:attribute name="replacer" type="nonNullString" use="required"/> </xs:complexType> </xs:element> <!-- Object definitions can have zero or more subscriptions. --> <xs:element name="listener" minOccurs="0" maxOccurs="unbounded"> <xs:complexType>
498
Spring.NET's spring-objects.xsd
<xs:sequence> <xs:element name="ref" type="objectOrClassReference" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> <!-- The event(s) the object is interested in. --> <xs:attribute name="event" type="nonNullString" use="optional"/> <!-- The name or name pattern of the method that will handle the event(s). --> <xs:attribute name="method" type="nonNullString" use="required"/> </xs:complexType> </xs:element> </xs:sequence> <!-Objects can be identified by an id, to enable reference checking. There are constraints on a valid XML id: if you want to reference your object in .NET code using a name that's illegal as an XML id, use the optional "name" attribute. If neither given, the object type name is used as id. --> <xs:attribute name="id" type="xs:ID" use="optional"/> <!-Optional. Can be used to create one or more aliases illegal in an id. Multiple aliases can be separated by any number of spaces or commas. --> <xs:attribute name="name" type="nonNullString" use="optional"/> <!-Each object definition must specify the full, assembly qualified of the type, or the name of the parent object from which the type can be worked out. Note that a child object definition that references a parent will just add respectively override property values and be able to change the singleton status. It will inherit all of the parent's other parameters like lazy initialization or autowire settings. --> <xs:attribute name="type" type="nonNullString" use="optional"/> <xs:attribute name="parent" type="nonNullString" use="optional"/> <!-Is this object "abstract", i.e. not meant to be instantiated itself but rather just serving as parent for concrete child object definitions? Default is false. Specify true to tell the object factory to not try to instantiate that particular object in any case. --> <xs:attribute name="abstract" type="xs:boolean" use="optional" default="false"/> <!-Is this object a "singleton" (one shared instance, which will be returned by all calls to GetObject() with the id), or a "prototype" (independent instance resulting from each call to getObject(). Default is singleton. Singletons are most commonly used, and are ideal for multi-threaded service objects. --> <xs:attribute name="singleton" type="xs:boolean" use="optional" default="true"/> <!-Optional attribute controlling the scope of singleton instances. It is only applicable to ASP.Net web applications and it has no effect on prototype objects. Applications other than ASP.Net web applications simply ignore this attribute. It has 3 possible values: 1. "application" Default object scope. Objects defined with application scope will behave like traditional singleton objects. Same instance will be returned from every call to IApplicationContext.GetObject() 2. "session" Objects with this scope will be stored within user's HTTP session. Session scope is typically used for objects such as shopping cart, user profile, etc. 3. "request" Object with this scope will be initialized for each HTTP request, but unlike with prototype objects, same instance will be returned from all calls to IApplicationContext.GetObject() within the same HTTP request. For example, if one ASP page forwards request to another using Server.Transfer method, they can easily share the state by configuring dependency to the same request-scoped object. --> <xs:attribute name="scope" use="optional" default="application"> <xs:simpleType> <xs:restriction base="xs:string">
499
Spring.NET's spring-objects.xsd
<xs:enumeration value="application"/> <xs:enumeration value="session"/> <xs:enumeration value="request"/> </xs:restriction> </xs:simpleType> </xs:attribute> <!-Is this object to be lazily initialized? If false, it will get instantiated on startup by object factories that perform eager initialization of singletons. --> <xs:attribute name="lazy-init" use="optional" default="default"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="true"/> <xs:enumeration value="false"/> <xs:enumeration value="default"/> </xs:restriction> </xs:simpleType> </xs:attribute> <!-Optional attribute controlling whether to "autowire" object properties. This is an automagical process in which object references don't need to be coded explicitly in the XML object definition file, but Spring works out dependencies. There are 5 modes: 1. "no" The traditional Spring default. No automagical wiring. Object references must be defined in the XML file via the <ref> element. We recommend this in most cases as it makes documentation more explicit. 2. "byName" Autowiring by property name. If a object of class Cat exposes a dog property, Spring will try to set this to the value of the object "dog" in the current factory. 3. "byType" Autowiring if there is exactly one object of the property type in the object factory. If there is more than one, a fatal error is raised, and you can't use byType autowiring for that object. If there is none, nothing special happens - use dependency-check="objects" to raise an error in that case. 4. "constructor" Analogous to "byType" for constructor arguments. If there isn't exactly one object of the constructor argument type in the object factory, a fatal error is raised. 5. "autodetect" Chooses "constructor" or "byType" through introspection of the object class. If a default constructor is found, "byType" gets applied. The latter two are similar to PicoContainer and make object factories simple to configure for small namespaces, but doesn't work as well as standard Spring behaviour for bigger applications. Note that explicit dependencies, i.e. "property" and "constructor-arg" elements, always override autowiring. Autowire behaviour can be combined with dependency checking, which will be performed after all autowiring has been completed. --> <xs:attribute name="autowire" use="optional" default="default"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="no"/> <xs:enumeration value="byName"/> <xs:enumeration value="byType"/> <xs:enumeration value="constructor"/> <xs:enumeration value="autodetect"/> <xs:enumeration value="default"/> </xs:restriction> </xs:simpleType> </xs:attribute> <!-Optional attribute controlling whether to check whether all this objects dependencies, expressed in its properties, are satisfied. Default is no dependency checking.
500
Spring.NET's spring-objects.xsd
"simple" type dependency checking includes primitives and String "object" includes collaborators (other objects in the factory) "all" includes both types of dependency checking --> <xs:attribute name="dependency-check" use="optional" default="default"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="none"/> <xs:enumeration value="objects"/> <xs:enumeration value="simple"/> <xs:enumeration value="all"/> <xs:enumeration value="default"/> </xs:restriction> </xs:simpleType> </xs:attribute> <!-The names of the objects that this object depends on being initialized. The object factory will guarantee that these objects get initialized before. Note that dependencies are normally expressed through object properties or constructor arguments. This property should just be necessary for other kinds of dependencies like statics (*ugh*) or database preparation on startup. --> <xs:attribute name="depends-on" type="nonNullString" use="optional"/> <!-Optional attribute for the name of the custom initialization method to invoke after setting object properties. The method must have no arguments, but may throw any exception. --> <xs:attribute name="init-method" type="nonNullString" use="optional"/> <!-Optional attribute for the name of the custom destroy method to invoke on object factory shutdown. The method must have no arguments, but may throw any exception. Note: Only invoked on singleton objects! --> <xs:attribute name="destroy-method" type="nonNullString" use="optional"/> <xs:attribute name="factory-method" type="nonNullString" use="optional"/> <xs:attribute name="factory-object" type="nonNullString" use="optional"/> </xs:complexType> <xs:annotation> <xs:documentation>The document root. At least one object definition is required.</xs:documentation> </xs:annotation> <xs:element name="objects"> <xs:complexType> <xs:sequence> <xs:element name="description" type="description" minOccurs="0" maxOccurs="1"/> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="import" type="importElement"/> <xs:element name="alias" type="aliasElement"/> <xs:element name="object" type="vanillaObject"/> <xs:any namespace="##other" processContents="strict"/> </xs:choice> </xs:sequence> <!-Default values for all object definitions. Can be overridden at the "object" level. See those attribute definitions for details. --> <xs:attribute name="default-lazy-init" type="xs:boolean" use="optional" default="false"/> <xs:attribute name="default-dependency-check" use="optional" default="none"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="none"/> <xs:enumeration value="objects"/> <xs:enumeration value="simple"/> <xs:enumeration value="all"/> </xs:restriction> </xs:simpleType> </xs:attribute> <xs:attribute name="default-autowire" use="optional" default="no"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="no"/> <xs:enumeration value="byName"/> <xs:enumeration value="byType"/>
501
Spring.NET's spring-objects.xsd
<xs:enumeration value="constructor"/> <xs:enumeration value="autodetect"/> </xs:restriction> </xs:simpleType> </xs:attribute> </xs:complexType> </xs:element> </xs:schema>
502