Upload
harshad-nelwadkar
View
15.401
Download
2
Embed Size (px)
DESCRIPTION
Spring MVC
Citation preview
Advanced Spring MVC
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
About the speaker
� Principal Consultant at Inteface21
� Core Developer on Spring
� Founder of Spring Modules
� Author of Pro Spring
Contact me at [email protected]
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Diving in at the deep end
� Handling forms
� Workflow
� Validation
� Binding
� Multiple view technologies
� Testing outside the container
� Exploring the outer reaches
� Interceptors and Filters
� Internationalization
� File Uploads
� Handling Exceptions
� MVC in Spring 1.3
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Handling Forms
� Spring provides excellent features for handling form processing
� SimpleFormController and CancellableFormController
• Handle single page form input
� AbstractWizardFormController
• Handle multi-page form input
• Consider Spring WebFlow for a more comprehensive solution
� Let's look at SimpleFormController in
more detail
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
SimpleFormController : Key Concepts
� Well-defined, extensible workflow
� Explicit form request and submission workflows
� Use template methods to extend the flow
� Flexible binding system
� Sensible default binders
� Plug-in custom binders
� Binding support for JSP, Velocity and FreeMarker
� Externalized validation
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Workflow of SimpleFormController
� Split into two distinct branches
� Form request
• Binding is disabled by default
• Allows for loading for command object and reference data
• Shows the form view
� Form submission
• Binding is enabled by default
− Shows form on error
• Validation is enabled
− Shows form on error
• Provides callback methods to handle submission logic
• Shows configurable success view after processing
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Identifying Workflow Branches
� Sensible defaults:
� GET request – form request
� POST request – form submission
� Fully customizable
� Override the isFormSubmission() method
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Important Concepts
� Command Object� Object representing the data being manipulated
by the form� Form data is bound to the command object� Accessible in submission callbacks
� Reference Data� Data needed for form rendering� Often select list data
� Form View� The view that displays the form� Also used to display errors inline
� Success View� The view to show after a successful form
submission
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Anatomy of a Form Request (1)
� Call to formBackingObject()
� Retrieve the command object
� Allows for pre-population of the form
� Store in Session if sessionForm property is true
� Call to initBinder()
� Register custom editors
� Binding
� If bindOnNewForm is true
� Bind errors are stored and exposed in the model
� Callback method onBindOnNewForm()
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Anatomy of a Form Request (2)
� Call to referenceData()
� Load reference data needed for displaying the form
� Call to getFormView()
� Get the name of the form view
� Default implementation uses value of formViewproperty
� Call to showForm()
� Completes ModelAndView and returns
� Command object stored in session if configured
� Renders the actual form
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Anatomy of a Form Submission (1)
� Call to formBackingObject()� Retrieve the command object� Default implementation will retrieve from the
Session if sessionForm is true otherwise it will create a new instance of the configured commandClass
� Call to initBinder()� Register custom editors
� Binding� Can prevent binding by overriding
suppressBinding()� Bind errors are stored and exposed in the model
� Call to onBind()� Called after bind but before validation
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Anatomy of a Form Submission (2)
� Validation done using Validators
� Call to onBindAndValidate()
� Called after bind and validate
� If validation fails then add errors to the ModelAndView and show the form again
� If validation succeeds call onSubmit()callbacks
� May need retrieve the success view manually with a call to getSuccessView()
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
SimpleFormController Configuration Parameters (1)� commandClass
� The class of the command object
� Used by default formBackingObject()implementation
� commandName
� The name the command object is exposed as in the model (default=‘command’)
� successView
� The name of the view used after successfully processing a form submission
� formView
� The name of the view used to render the form
� Used by default showForm() implementation
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
SimpleFormController Configuration Parameters (2)� sessionForm
� Indicates whether the command object should be stored in the session
� validateOnBinding� Specify whether the Validator list should be
used to validate the command object after binding
� bindOnNewForm
� Indicates whether binding should be invoked when rendering the form view
� validators� Specify a list of Validators to apply to the
command object after binding
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Customizing Data Binding
� Uses standard JavaBeans PropertyEditors
� The same approach is used for custom binding in the ApplicationContext
� Register your custom editors by overriding the initBinder() method
� Extend the PropertyEditorSupport class to
simplify implementation
� Remember that PropertyEditors can do any
logic
� Call a service object for domain object binding
� Interact with the Spring ApplicationContext to
lookup a manged bean
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Adding Validation Logic
� Implement the Validator interface
� Decoupled from the web tier
� Generic collection of validation errors
� Use the Errors interface to log validation errors
� Use ValidationUtils for common validation checks
� Errors are associated with codes for i18n of error messages
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Binding in the View (1)
� Using the <spring:bind/> tag
� Has access to
� The bound values
� Any errors from binding or validation
� Provides full flexibility for binding data and displaying errors
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Binding in the View (2)
<spring:bind path="order.cardDetails.numberorder.cardDetails.numberorder.cardDetails.numberorder.cardDetails.number"><td><input type="text" size="60" <input type="text" size="60" <input type="text" size="60" <input type="text" size="60"
name="<name="<name="<name="<c:outc:outc:outc:out value='${value='${value='${value='${status.expressionstatus.expressionstatus.expressionstatus.expression}'/>"}'/>"}'/>"}'/>"value="<value="<value="<value="<c:outc:outc:outc:out value='${value='${value='${value='${status.displayValuestatus.displayValuestatus.displayValuestatus.displayValue}' />"/>}' />"/>}' />"/>}' />"/>
</td><td>
<<<<c:ifc:ifc:ifc:if test="${test="${test="${test="${status.errorstatus.errorstatus.errorstatus.error}">}">}">}"><div class="error"><div class="error"><div class="error"><div class="error"><<<<c:forEachc:forEachc:forEachc:forEach items="${items="${items="${items="${status.errorMessagesstatus.errorMessagesstatus.errorMessagesstatus.errorMessages}" }" }" }" varvarvarvar="error">="error">="error">="error"><<<<c:outc:outc:outc:out value="${error}"/>value="${error}"/>value="${error}"/>value="${error}"/>
</</</</c:forEachc:forEachc:forEachc:forEach>>>></div></div></div></div>
</</</</c:ifc:ifc:ifc:if>>>></td>
</spring:bind>
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Understanding the BindStatus
� Un-typed view of the world
� Necessary to show data binding errors
� Holds the exact data entered by the user
� Wraps the command object
� Binding in the view is to the BindStatusnot the command object
SimpleFormController in Action
Live Demo
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Multiple View Technologies
� Two approaches
� Manually construct View instances in Controllers
• Not ideal
• Couples Controllers to View implementations
� Configure multiple ViewResolvers
• Ideal approach
• Controllers remain decoupled from Viewimplementations.
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
View Resolution Strategy
� At startup� DispatcherServlet retrieves ViewResolvers
(by type)
� Applies ordering to ViewResolver list• Using the Ordered interface
• Un-ordered ViewResolvers are placed at the end of the list
� Stores the ordered ViewResolver list
� At request time� Iterates over the ordered list of ViewResolvers� Asks each ViewResolver to resolve the view
name
� Stops as soon as it finds a view
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Important Information
� Some ViewResolvers always resolve a
view
� Any view that extends UrlBasedViewResolver
� InternalResourceViewResolver always
resolves a view
� May be a 404-error page
� Always put these resolvers at the end of the list
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring Multiple ViewResolvers
<bean id="beanNameViewResolver" class="...BeanNameViewResolver"><property name="order" value="0"/><property name="order" value="0"/><property name="order" value="0"/><property name="order" value="0"/>
</bean>
<bean id="jspViewResolver" class="...InternalResourceViewResolver"><property name="viewClass" value="...JstlView" /><property name="prefix" value="/WEB-INF/view/" /><property name="suffix" value=".jsp" />
</bean>
Explict ordering
Multiple Views in Action
Live Demo
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Testing outside the container
� Spring MVC is easy to test outside the container� Support classes in spring-mock.jar
� MockHttpServletRequest� MockHttpServletResponse� …
� Unit testing� Use EasyMock/JMock� Test exception handling� Test ModelAndView creation
� Integration testing� Extend
AbstractTransactionalDataSourceSpringContextTests
� Use real service/data tier objects
Testing Outside the Container
Live Demo
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Request Interception
� Spring provides two mechanisms for request interception:� A HandlerInterceptor
• Configured in Spring ApplicationContext• Configured for a HandlerMapping
• Applies to all handlers mapped by the HandlerMapping
� A standard servlet Filter• Uses standard servlet infrastructure
• Mapped to a particular URL pattern
• Configured in web.xml
� Important distinction between the two:� Filters come before the DispatcherServlet� HandlerInterceptors come before the handler
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Request Interception in Action
Handler
Request comes in
Servlet Filter Chain
DispatcherServlet
HandlerMapping
Locate HandlerExecutionChain
Response rendered
HandlerInterceptor Chain
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
The HandlerInterceptor Interface (1)
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
The HandlerInterceptor Interface (2)
� preHandle()
� Called before the handler is invoked by DispatcherServlet
� Can handle the request and end processing of the HandlerExecutionChain
� postHandle()
� Called after the handler is invoked but before view rendering
� Can extend the ModelAndView
� afterCompletion()
� Called after view rendering is completed
� Has access to any Exception raised during the request
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring a HandlerInterceptor
<bean name="handlerMapping"
class="o.sf.w.s.handler.BeanNameUrlHandlerMapping">
<property name="interceptors"> <property name="interceptors"> <property name="interceptors"> <property name="interceptors">
<list><list><list><list>
<ref bean="<ref bean="<ref bean="<ref bean="maintenanceInterceptormaintenanceInterceptormaintenanceInterceptormaintenanceInterceptor" />" />" />" />
<ref bean="<ref bean="<ref bean="<ref bean="localeChangeInterceptorlocaleChangeInterceptorlocaleChangeInterceptorlocaleChangeInterceptor" />" />" />" />
</list></list></list></list>
</property></property></property></property>
</bean>
Ordered list of HandlerInterceptors
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Filters and Dependency Injection
� Standard servlet Filters sit outside the Spring ApplicationContext
� Spring provides DelegatingFilterProxyallowing for Filters to be first-class
Spring beans
� Configure the DelegatingFilterProxy in
the web.xml file
� Configure the real Filter in the Spring ApplicationContext
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring a Filter Proxy(1)
<filter>
<filter-name>requestStatisticsFilterrequestStatisticsFilterrequestStatisticsFilterrequestStatisticsFilter</filter-name>
<filter-class>
o.sf.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>requestStatisticsFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
Corresponds to the bean name
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring a Filter Proxy (2)
<bean id="requestStatisticsFilterrequestStatisticsFilterrequestStatisticsFilterrequestStatisticsFilter"
class="...RequestStatisticsFilter" />
Just a plain Spring bean!
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Use Cases for Filters and Interceptors
� Logging
� Extensive support in Spring already
� Monitoring
� Track response times
� Exception Handling
� Monitor exceptions raised
� Better solution in HandlerExceptionResolvers
� Conditional Request Handling
� Maintenance mode
� Many more
� Any logic that can apply across requests
Filters and Interceptors in Action
Live Demo
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Internationalization (1)
� Spring provides comprehensive i18n support� Locale-aware validation framework
� Integrated Locale resolution
� Locale exposure for view frameworks
� Localized message lookup
� Four important components� MessageSource
� LocaleResolver
� LocaleContextHolder
� LocalChangeInterceptor
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Internationalization (2)
� Four important components� MessageSource
• Provides Locale -aware message lookup
• Used by validation framework
• Can be exposed to view technologies for generating localized responses
� LocaleResolver• Resolves the Locale for a request
• Provides extensible, configurable resolution behaviour
� LocaleContextHolder• Stores the Locale for the current Thread in a LocaleContext
object
� LocalChangeInterceptor• HandlerInterceptor allowing the user to select a Locale
• Interacts with the LocaleResolver to store the selected Locale
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
MessageSource Implementations
� Spring provides two implementations of MessageSource� ResourceBundleMessageSource
• Uses standard Java ResourceBundles to lookup
messages
� ReloadableResourceBundleMessageSource
• Uses ResourceBundles also
• Allows for ResourceBundle refresh without
stopping the VM
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring a MessageSource
<bean id="messageSourcemessageSourcemessageSourcemessageSource"
class="…ResourceBundleMessageSource">
<property name="basenames" value="labelslabelslabelslabels"/>
</bean>
Language specific files in '<basename>_locale.properties '
'Special' bean name
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Understanding Locale Resolution
Request comes in
DispatcherServlet
LocaleResolverLocale
ContextHolder
Request Proceeds
Available throughout request
Grab LocaleResolver Expose Locale
(via LocaleResolver)
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
LocaleResolver Implementations (1)
� AcceptHeaderLocaleResolver
� Reads Locale from the 'accept-language' header of request
� Default implementation
� Supports per-user configuration
� Cannot be used with LocaleChangeInterceptor
� CookieLocaleResolver
� Reads the Locale from a cookie
� Cookie name is configurable
� Falls back to 'accept-language' header
� Supports per-user configuration
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
LocaleResolver Implementations (1)
� FixedLocaleResolver
� Uses single, fixed Locale
� Configured in the ApplicationContext
� Cannot be used with LocaleChangeInterceptor
� SessionLocaleResolver
� Stores the Locale in the user's session
� Configurable session attribute
� Supports per user-configuration
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring a LocaleResolver
� LocaleResolver are accessed by type
� Simply declare a bean of type LocaleResolver :
<bean id="localeResolver"
class="...SessionLocaleResolver" />
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Understanding Locale Switching
DispatcherServlet
LocaleContextHolder
LocaleChangeInterceptor
LocaleResolver
setLocale
Request ProceedsLocaleAware
Component
Requests Locale
Retrieved from LocaleResolver
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring the LocaleChangeInterceptor
<bean id="localeChangeInterceptor"
class="…LocaleChangeInterceptor">
<property name="<property name="<property name="<property name="paramNameparamNameparamNameparamName" " " "
value="value="value="value="langlanglanglang"/>"/>"/>"/>
</bean>
http://www.myapp.com/home?lang=en_GB
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Understanding Message Resolution
MessageSource
LocaleContextHolder
LocaleAware
Component
Render response/output
getLocale()
Resolve message using Locale
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Using JSTL <fmt:*/> tags (1)
� Spring can expose its managed Locale to JSTL
� Must explicitly configure JSTL as the view:
<bean id="jspViewResolver" class="...InternalResourceViewResolver">
<property name="<property name="<property name="<property name="viewClassviewClassviewClassviewClass" value="..." value="..." value="..." value="...JstlViewJstlViewJstlViewJstlView" />" />" />" />
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Using JSTL <fmt:*/> tags (2)
� Once configured simply use <fmt:*/> tags as normal:
<fmt:message key="home.title"/>
Internationalization in Action
Live Demo
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Handling File Uploads (1)
� Handled by implementations of MultipartResolver
� Looked up automatically by DispatcherServlet
� Use special name: 'multipartResolver '
� Two implementations � CommonsMultipartFileResolver
• Uses Jakarta Commons FileUpload
� CosMultipartResolver• Use the com.oreilly.servlet multipart support
package
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Handling File Uploads (1)
� Multipart requests are converted to instances of MultipartHttpServletRequest� Can access directly in Controllers
public interface MultipartHttpServletRequest
extends HttpServletRequest {
Iterator getFileNames();
MultipartFile getFile(String name);
Map getFileMap();
}
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Data Binding and File Uploads
� Bind to a byte[] property
� Simply override initBinder() :
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor(byte[].classbinder.registerCustomEditor(byte[].classbinder.registerCustomEditor(byte[].classbinder.registerCustomEditor(byte[].class, , , ,
new new new new ByteArrayMultipartFileEditorByteArrayMultipartFileEditorByteArrayMultipartFileEditorByteArrayMultipartFileEditor());());());());
}
File Uploads in Action
Live Demo
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Last Ditch Exception Handling
� In general you want to allow fatal exceptions to propagate up the stack
� These exceptions can be handled by a last ditch handler
� Log the error
� Notify an administrator
� Display a friendly error-page for the user
� Use an implementation of the HandlerExceptionResolver interface
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
HandlerExceptionResolver interface
public interface HandlerExceptionResolver {
ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);
}
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Understanding Exception Handling
DispatcherServlet
HandlerExceptionResolver
Request routed to handler
HandlerException
Occurs!
Resolve exception
Render ModelAndView from resolver
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring the HandlerExceptionResolver
� Special bean, resolved by type
� Single implementation out of the box –SimpleMappingExceptionResolver
� Map Exception types to view names
� Puts the Exception into the model
automatically
� Useful out of the box, even better for extension
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Configuring SimpleMappingExceptionResolver
<bean id="exceptionHandler" class="...SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<value>
java.lang.Exceptionjava.lang.Exceptionjava.lang.Exceptionjava.lang.Exception=error=error=error=error
</value>
</property>
</bean>
All exceptions are mapped to the 'error' view
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Extending SimpleMappingExceptionResolver
� Rather than build your own HandlerExceptionResolver extend the SimpleMappingExceptionResolver
� Simple process:
� Override the resolveException() method
� Call super.resolveException()
� Perform custom processing
� Return (potentially modified) ModelAndView
Exception Handling in Action
Live Demo
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
Summary
� Spring MVC has numerous extension points
� Form handling workflow
� Validation
� Binding
� View resolution
� Exception handling
� Filter and HandlerInterceptors
� Make use of the existing form workflow rather than build controllers from scratch
� Use custom binders to simply form controller logic
� Use Filters and HandlerInterceptors to add cross-cutting web logic
Copyright 2004-2005, Interface21 Ltd. - Copying, publishing, or distributing without expressed written permission is prohibited.
More Information
� http://www.springframework.org
� Books:
� Pro Spring (Apress)
� Pro Java Development with Spring (Wiley)
� Expert Spring MVC (Apress, Forthcoming)
• Deepest coverage of Spring MVC
• http://www.apress.com/book/bookDisplay.html?bID=10048
• Expert authors: Seth Ladd, Keith Donald and more…
Q&A