Spring MVC Beginner’s Guide Beginner's Amuthan G
User Manual:
Open the PDF directly: View PDF .
Page Count: 545 [warning: Documents this large are best viewed by clicking the View PDF Link!]
- Spring MVC Beginner's Guide
- Time for action – whitelisting
- Credits
- About the Author
- About the Reviewers
- www.PacktPub.com
- Support files, eBooks, discount offers, and more
- Why subscribe?
- Free access for Packt account holders
- Preface
- What this book covers
- What you need for this book
- Who this book is for
- Conventions
- Time for action – heading
- What just happened?
- Pop quiz – heading
- Have a go hero – heading
- Reader feedback
- Customer support
- Downloading the example code
- Errata
- Piracy
- Questions
- 1. Configuring a Spring Development Environment
- Setting up Java
- Time for action – installing JDK
- Time for action – setting up environment variables
- Configuring a build tool
- Time for action – installing the Maven build tool
- Installing a web server
- Time for action – installing the Tomcat web server
- Configuring a development environment
- Time for action – installing Spring Tool Suite
- Time for action – configuring Tomcat on STS
- What just happened?
- Time for action – configuring Maven on STS
- Creating our first Spring MVC project
- Time for action – creating a Spring MVC project in STS
- What just happened?
- Spring MVC dependencies
- Time for action – adding Spring jars to the project
- What just happened?
- Time for action – adding Java version properties in pom.xml
- A jump-start to MVC
- Time for action – adding a welcome page
- What just happened?
- The dispatcher servlet
- Time for action – configuring the dispatcher servlet
- What just happened?
- Deploying our project
- Time for action – running the project
- Summary
- 2. Spring MVC Architecture – Architecting Your Web Store
- The dispatcher servlet
- Time for action – examining request mapping
- What just happened?
- Pop quiz – request mapping
- The web application context
- Time for action – understanding the web application context
- What just happened?
- Pop quiz – the web application context
- The web application context configuration
- Pop quiz – web application context configuration
- View resolvers
- Time for action – understanding InternalResourceViewResolver
- What just happened?
- Model View Controller
- An overview of the Spring MVC request flow
- The web application architecture
- The domain layer
- Time for action – creating a domain object
- What just happened?
- The persistence layer
- Time for action – creating a repository object
- What just happened?
- The service layer
- Time for action – creating a service object
- What just happened?
- Have a go hero – accessing the product domain object via a service
- An overview of the web application architecture
- Have a go hero – listing all our customers
- Summary
- 3. Control Your Store with Controllers
- Defining a controller
- Time for action – adding class-level request mapping
- What just happened?
- Pop quiz – class-level request mapping
- The role of a controller in Spring MVC
- Handler mapping
- Using URI template patterns
- Time for action – showing products based on category
- What just happened?
- Pop quiz – request path variable
- Using matrix variables
- Time for action – showing the products based on filter
- What just happened?
- Understanding request parameters
- Time for action – adding the product details page
- What just happened?
- Pop quiz – the request parameter
- Time for action – implementing a master detail view
- What just happened?
- Have a go hero – adding multiple filters to list products
- Summary
- 4. Working with Spring Tag Libraries
- Serving and processing forms
- Time for action – serving and processing forms
- What just happened?
- Customizing data binding
- Time for action – whitelisting form fields
- What just happened?
- Externalizing text messages
- Time for action – externalizing messages
- What just happened?
- Using Spring Security tags
- Time for action – adding a login page
- What just happened?
- Summary
- 5. Working with View Resolver
- Resolving views
- The redirect view
- Time for action – examining RedirectView
- What just happened?
- Pop quiz – redirect view
- Serving static resources
- Time for action – serving static resources
- What just happened?
- Pop quiz – static view
- Time for action – adding images to the product detail page
- What just happened?
- The multipart request in action
- Time for action – adding images to the product page
- What just happened?
- Have a go hero – uploading product user manuals to the server
- Using ContentNegotiatingViewResolver
- Time for action – configuring ContentNegotiatingViewResolver
- What just happened?
- Working with the handler exception resolver
- Time for action – adding the response status exception
- What just happened?
- Time for action – adding an exception handler
- What just happened?
- Summary
- 6. Intercept Your Store with Interceptor
- Working with interceptors
- Time for action – configuring an interceptor
- What just happened?
- Pop quiz – interceptor
- Internationalization (i18n)
- Time for action – adding internationalization
- What just happened?
- Have a go hero – fully internationalize the product detail page
- Audit logging
- Time for action – adding the data audit interceptor
- What just happened?
- Conditional redirecting
- Time for action – intercepting offer page requests
- What just happened?
- Summary
- 7. Validate Your Products with a Validator
- Bean validation
- Time for action – adding bean validation support
- What just happened?
- Have a go hero – adding more validation in the add products page
- Custom validation with JSR-303 / bean validation
- Time for action – adding custom validation support
- What just happened?
- Have a go hero – adding custom validation to a category
- Spring validation
- Time for action – adding Spring validation
- What just happened?
- Time for action – combining Spring and bean validations
- What just happened?
- Have a go hero – adding Spring validation to the product image
- Summary
- 8. Give REST to Your Application with Ajax
- Introducing REST
- Time for action – implementing RESTful web services
- What just happened?
- Time for action – consuming REST web services
- What just happened?
- Handling a web service in Ajax
- Time for action – consuming REST web services via Ajax
- What just happened?
- Summary
- 9. Apache Tiles and Spring Web Flow in Action
- Working with Spring Web Flow
- Time for action – implementing the order-processing service
- What just happened?
- Time for action – implementing the checkout flow
- What just happened?
- Understanding the flow definition
- Understanding the checkout flow
- Pop quiz – web flow
- Time for action – creating views for every view state
- What just happened?
- Have a go hero – adding a decision state
- Enhancing reusability through Apache Tiles
- Time for action – creating views for every view state
- What just happened?
- Pop quiz – Apache Tiles
- Summary
- 10. Testing Your Application
- Unit testing
- Time for action – unit-testing domain objects
- What just happened?
- Have a go hero – adding tests for cart
- Integration testing with the Spring Test Context framework
- Time for action – testing the product validator
- What just happened?
- Time for action – testing the product controller
- What just happened?
- Time for action – testing REST controllers
- What just happened?
- Have a go hero – adding tests for the remaining REST methods
- Summary
- A. Using the Gradle Build Tool
- Installing Gradle
- The Gradle build script for your project
- Understanding the Gradle script
- B. Pop Quiz Answers
- Chapter 2, Spring MVC Architecture – Architecting Your Web Store
- Pop quiz – request mapping
- Pop quiz – the web application context
- Pop quiz – web application context configuration
- Chapter 3, Control Your Store with Controllers
- Pop quiz – class-level request mapping
- Pop quiz – request path variable
- Pop quiz – the request parameter
- Chapter 5, Working with View Resolver
- Pop quiz – redirect view
- Pop quiz – static view
- Chapter 6, Intercept Your Store with Interceptor
- Pop quiz – interceptor
- Chapter 9, Apache Tiles and Spring Web Flow in Action
- Pop quiz – web flow
- Pop quiz – Apache Tiles
- Index
SpringMVCBeginner’sGuide
TableofContents
SpringMVCBeginner’sGuide
Credits
AbouttheAuthor
AbouttheReviewers
www.PacktPub.com
Supportfiles,eBooks,discountoffers,andmore
Whysubscribe?
FreeaccessforPacktaccountholders
Preface
Whatthisbookcovers
Whatyouneedforthisbook
Whothisbookisfor
Conventions
Timeforaction–heading
Whatjusthappened?
Popquiz–heading
Haveagohero–heading
Readerfeedback
Customersupport
Downloadingtheexamplecode
Errata
Piracy
Questions
1.ConfiguringaSpringDevelopmentEnvironment
SettingupJava
Timeforaction–installingJDK
Timeforaction–settingupenvironmentvariables
Configuringabuildtool
Timeforaction–installingtheMavenbuildtool
Installingawebserver
Timeforaction–installingtheTomcatwebserver
Configuringadevelopmentenvironment
Timeforaction–installingSpringToolSuite
Timeforaction–configuringTomcatonSTS
Whatjusthappened?
Timeforaction–configuringMavenonSTS
CreatingourfirstSpringMVCproject
Timeforaction–creatingaSpringMVCprojectinSTS
Whatjusthappened?
SpringMVCdependencies
Timeforaction–addingSpringjarstotheproject
Whatjusthappened?
Timeforaction–addingJavaversionpropertiesinpom.xml
Ajump-starttoMVC
Timeforaction–addingawelcomepage
Whatjusthappened?
Thedispatcherservlet
Timeforaction–configuringthedispatcherservlet
Whatjusthappened?
Deployingourproject
Timeforaction–runningtheproject
Summary
2.SpringMVCArchitecture–ArchitectingYourWebStore
Thedispatcherservlet
Timeforaction–examiningrequestmapping
Whatjusthappened?
Popquiz–requestmapping
Thewebapplicationcontext
Timeforaction–understandingthewebapplicationcontext
Whatjusthappened?
Popquiz–thewebapplicationcontext
Thewebapplicationcontextconfiguration
Popquiz–webapplicationcontextconfiguration
Viewresolvers
Timeforaction–understandingInternalResourceViewResolver
Whatjusthappened?
ModelViewController
AnoverviewoftheSpringMVCrequestflow
Thewebapplicationarchitecture
Thedomainlayer
Timeforaction–creatingadomainobject
Whatjusthappened?
Thepersistencelayer
Timeforaction–creatingarepositoryobject
Whatjusthappened?
Theservicelayer
Timeforaction–creatingaserviceobject
Whatjusthappened?
Haveagohero–accessingtheproductdomainobjectviaaservice
Anoverviewofthewebapplicationarchitecture
Haveagohero–listingallourcustomers
Summary
3.ControlYourStorewithControllers
Definingacontroller
Timeforaction–addingclass-levelrequestmapping
Whatjusthappened?
Popquiz–class-levelrequestmapping
TheroleofacontrollerinSpringMVC
Handlermapping
UsingURItemplatepatterns
Timeforaction–showingproductsbasedoncategory
Whatjusthappened?
Popquiz–requestpathvariable
Usingmatrixvariables
Timeforaction–showingtheproductsbasedonfilter
Whatjusthappened?
Understandingrequestparameters
Timeforaction–addingtheproductdetailspage
Whatjusthappened?
Popquiz–therequestparameter
Timeforaction–implementingamasterdetailview
Whatjusthappened?
Haveagohero–addingmultiplefilterstolistproducts
Summary
4.WorkingwithSpringTagLibraries
Servingandprocessingforms
Timeforaction–servingandprocessingforms
Whatjusthappened?
Customizingdatabinding
Timeforaction–whitelistingformfields
Whatjusthappened?
Externalizingtextmessages
Timeforaction–externalizingmessages
Whatjusthappened?
UsingSpringSecuritytags
Timeforaction–addingaloginpage
Whatjusthappened?
Summary
5.WorkingwithViewResolver
Resolvingviews
Theredirectview
Timeforaction–examiningRedirectView
Whatjusthappened?
Popquiz–redirectview
Servingstaticresources
Timeforaction–servingstaticresources
Whatjusthappened?
Popquiz–staticview
Timeforaction–addingimagestotheproductdetailpage
Whatjusthappened?
Themultipartrequestinaction
Timeforaction–addingimagestotheproductpage
Whatjusthappened?
Haveagohero–uploadingproductusermanualstotheserver
UsingContentNegotiatingViewResolver
Timeforaction–configuringContentNegotiatingViewResolver
Whatjusthappened?
Workingwiththehandlerexceptionresolver
Timeforaction–addingtheresponsestatusexception
Whatjusthappened?
Timeforaction–addinganexceptionhandler
Whatjusthappened?
Summary
6.InterceptYourStorewithInterceptor
Workingwithinterceptors
Timeforaction–configuringaninterceptor
Whatjusthappened?
Popquiz–interceptor
Internationalization(i18n)
Timeforaction–addinginternationalization
Whatjusthappened?
Haveagohero–fullyinternationalizetheproductdetailpage
Auditlogging
Timeforaction–addingthedataauditinterceptor
Whatjusthappened?
Conditionalredirecting
Timeforaction–interceptingofferpagerequests
Whatjusthappened?
Summary
7.ValidateYourProductswithaValidator
Beanvalidation
Timeforaction–addingbeanvalidationsupport
Whatjusthappened?
Haveagohero–addingmorevalidationintheaddproductspage
CustomvalidationwithJSR-303/beanvalidation
Timeforaction–addingcustomvalidationsupport
Whatjusthappened?
Haveagohero–addingcustomvalidationtoacategory
Springvalidation
Timeforaction–addingSpringvalidation
Whatjusthappened?
Timeforaction–combiningSpringandbeanvalidations
Whatjusthappened?
Haveagohero–addingSpringvalidationtotheproductimage
Summary
8.GiveRESTtoYourApplicationwithAjax
IntroducingREST
Timeforaction–implementingRESTfulwebservices
Whatjusthappened?
Timeforaction–consumingRESTwebservices
Whatjusthappened?
HandlingawebserviceinAjax
Timeforaction–consumingRESTwebservicesviaAjax
Whatjusthappened?
Summary
9.ApacheTilesandSpringWebFlowinAction
WorkingwithSpringWebFlow
Timeforaction–implementingtheorder-processingservice
Whatjusthappened?
Timeforaction–implementingthecheckoutflow
Whatjusthappened?
Understandingtheflowdefinition
Understandingthecheckoutflow
Popquiz–webflow
Timeforaction–creatingviewsforeveryviewstate
Whatjusthappened?
Haveagohero–addingadecisionstate
EnhancingreusabilitythroughApacheTiles
Timeforaction–creatingviewsforeveryviewstate
Whatjusthappened?
Popquiz–ApacheTiles
Summary
10.TestingYourApplication
Unittesting
Timeforaction–unit-testingdomainobjects
Whatjusthappened?
Haveagohero–addingtestsforcart
IntegrationtestingwiththeSpringTestContextframework
Timeforaction–testingtheproductvalidator
Whatjusthappened?
Timeforaction–testingtheproductcontroller
Whatjusthappened?
Timeforaction–testingRESTcontrollers
Whatjusthappened?
Haveagohero–addingtestsfortheremainingRESTmethods
Summary
A.UsingtheGradleBuildTool
InstallingGradle
TheGradlebuildscriptforyourproject
UnderstandingtheGradlescript
B.PopQuizAnswers
Chapter2,SpringMVCArchitecture–ArchitectingYourWebStore
Popquiz–requestmapping
Popquiz–thewebapplicationcontext
Popquiz–webapplicationcontextconfiguration
Chapter3,ControlYourStorewithControllers
Popquiz–class-levelrequestmapping
Popquiz–requestpathvariable
Popquiz–therequestparameter
Chapter5,WorkingwithViewResolver
Popquiz–redirectview
Popquiz–staticview
Chapter6,InterceptYourStorewithInterceptor
Popquiz–interceptor
Chapter9,ApacheTilesandSpringWebFlowinAction
Popquiz–webflow
Popquiz–ApacheTiles
Index
SpringMVCBeginner’sGuide
SpringMVCBeginner’sGuide
Copyright©2014PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,
ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthe
publisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyofthe
informationpresented.However,theinformationcontainedinthisbookissoldwithout
warranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,andits
dealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecaused
directlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthe
companiesandproductsmentionedinthisbookbytheappropriateuseofcapitals.
However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:June2014
Productionreference:1190614
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK.
ISBN978-1-78328-487-0
www.packtpub.com
CoverimagebyAniketSawant(<aniket_sawant_photography@hotmail.com>)
Credits
Author
AmuthanG
Reviewers
RafałBorowiec
PawanChopra
RubénClementeSerna
AcquisitionEditor
VinayArgekar
ContentDevelopmentEditor
AzharuddinSheikh
TechnicalEditors
MonicaJohn
NehaMankare
ShinyPoojary
CopyEditors
GladsonMonteiro
InsiyaMorbiwala
AdityaNair
StutiSrivastava
ProjectCoordinators
KinjalBari
WendellPalmer
Proofreaders
SimranBhogal
StephenCopestake
MariaGould
AmeeshaGreen
PaulHindle
Indexer
HemanginiBari
Graphics
DishaHaria
AbhinashSahu
ProductionCoordinator
AparnaBhagat
CoverWork
AparnaBhagat
AbouttheAuthor
AmuthanGhasoversixyearsofexperienceasaprofessionalsoftwaredeveloper.He
currentlyworksforalargecloudplatformcompanyandhasstrongproductdevelopment
experienceinJava,Spring,JPA,andmanyotherenterprisetechnologies.Inhisfreetime,
heenjoysbloggingonhissite(http://www.madebycode.in).Hecanbecontactedat
<mr.amuthan@gmail.com>.
IwouldliketogratefullyandsincerelythankMr.VincentKokforhisguidance,
understanding,patience,andmostimportantly,hisfriendshipduringmyfirstjobat
EducatorInc.Hismentorshiphasshapedmetobecomeawell-roundedprofessional.He
encouragedmetonotonlygrowasadeveloper,butalsoasanindependentthinker.
IwanttotakeamomentandexpressmygratitudetotheentireteamatPacktPublishing
fortheirpatienceandcooperation.WhenIsignedupforthisbook,Ireallyhadnoidea
howthingswouldturnout.Icouldn’thavepulledthisoffwithouttheirguidance.
Iwouldliketoexpressmygratitudetoallmyfriendsandfamilyforprovidingmewith
unendingencouragementandsupport.Ioweeverychallengeandaccomplishmenttoall
mylovelycolleagueswhotaughtmealotovertheyears.
AspecialthankstoDivyaandArunfortheirencouragement,friendship,andsupport.
Theywereastrongshouldertoleanoninthemostdifficulttimesduringthewritingof
thisbook.
Finally,andmostimportantly,IwouldliketothankmywifeManjuwhobelievesmemore
thanmyself.Hersupport,encouragement,quietpatience,andunwaveringlovewere
undeniablythebedrockuponwhichmylifehasbeenbuilt.
AbouttheReviewers
RafałBorowiecisanITspecialistwithabouteightyearsofcommercialexperience,
specializinginsoftwaretestingandqualityassurance,softwaredevelopment,project
management,andteamleadership.
HecurrentlyholdsthepositionofaTeamLeaderatGoyello,whereheismainly
responsibleforbuildingandmanagingteamsofprofessionaldevelopersandtesters.Heis
alsoresponsibleformaintainingrelationswithcustomersandacquiringnewones,mainly
throughconsultancy.
Hebelievesinagileprojectmanagementandisabigfanoftechnology,especially
technologythatisJavarelated(butnotlimitedtoit).Helikessharingknowledgeabout
softwaredevelopmentandpracticesthroughhisblog(blog.codeleak.pl)andTwitter
account(@kolorobot)andalsoatinternalandexternaleventssuchasconferencesor
workshops.
PawanChopraisanAgiledeveloperwitheightyearsofexperienceinthesoftware
industry.HecurrentlyworksatWebners(http://www.webnersolutions.com/)onsomecool
JavaScript,Java,HTML5,Node,andAngularJSprojects.Heisanopensourceenthusiast.
Helovessharingknowledgethroughtrainingandblogging.Heisalsoverystrongonthe
serversidewithvastexperienceinSpringandHibernatetools.Heblogsat
www.itspawan.com.
RubénClementeSernaisasoftwareengineerbyprofessionwithovereightyearsof
experienceinsoftwaredevelopment.HerecentlymovedtotheUKandiscurrently
workingasaJavaDeveloperatPiksel,acompanythatcreatesandmanagesOTTvideo
solutionsforsomeoftheworld’sleadingmediabrands.PriortoPiksel,hehasworkedat
GFIInformáticainSpainonmanyJavadevelopmentprojects,mainlyfortelecomand
governmentservicecustomers.
Moredetailedinformationabouthisskillsandexperiencecanbefoundat
http://www.linkedin.com/in/rubenclementeserna.Hecanbecontactedat
<rubenclemente@gmail.com>.
www.PacktPub.com
Supportfiles,eBooks,discountoffers,and
more
Youmightwanttovisitwww.PacktPub.comforsupportfilesanddownloadsrelatedto
yourbook.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFand
ePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandas
aprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwith
usat<service@packtpub.com>formoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signup
forarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooks
andeBooks.
http://PacktLib.PacktPub.com
DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigital
booklibrary.Here,youcanaccess,readandsearchacrossPackt’sentirelibraryofbooks.
Whysubscribe?
FullysearchableacrosseverybookpublishedbyPackt
Copyandpaste,printandbookmarkcontent
Ondemandandaccessibleviawebbrowser
Preface
Thisbookhasaveryclearaim:tointroduceyoutotheincrediblesimplicityandpowerof
SpringMVC.IstillrememberfirstlearningabouttheSpringframeworkbackin2009.
Thebestwaytotestwhetherornotyoureallyunderstandaconceptistotrytoteachitto
someoneelse.Inmycase,IhavetaughtSpringMVCtoMVC;areyouconfused?Imean
thatbackin2009,ItaughtittomywifeManjuViswambaranChandrika(MVC).During
thatcourse,Iwasabletounderstandthekindofdoubtsthatariseinabeginner’smind.I
havegatheredallmyteachingknowledgeandputitinthisbookinanelegantwaysothat
itcanbeunderstoodwithoutconfusion.
Thisbookfollowsathemeofdevelopingasimplee-commercesitestep-by-step.Inevery
successivechapter,youwilllearnanewconceptofSpringMVC.Obviously,theaimisto
teachyouhowyoucanuseSpringMVCeffectively.Developingafull-blown,production-
readye-commercesiteisnotthepurposeofthisbook.
Whatthisbookcovers
Chapter1,ConfiguringaSpringDevelopmentEnvironment,willgiveyouaquick
overviewofSpringMVCanditsarchitectureandguideyouthroughdetailednotesand
step-by-stepinstructionstosetupyourdevelopmentenvironment.Afterinstallingthe
requiredprerequisites,youwilltryoutaquickexampleofhowtodevelopanapplication
withSpringMVC.Althoughthechapterdoesn’texplainallthecodeindetail,you’llpick
upafewthingsintuitively.
Chapter2,SpringMVCArchitecture–ArchitectingYourWebStore,willlaydownthe
groundworkforthesampleapplicationthatwearegoingtobuildalongtheway,chapter
bychapter.Thischapterwillintroduceyoutoconceptssuchasrequestmapping,web
applicationcontext,SpringMVCrequestflow,andthelayeredarchitectureofatypical
webapplication.
Chapter3,ControlYourStorewithControllers,willtakeyouthroughtheconceptofa
controller;youwilllearnmoreabouthowtodefineacontroller,anduseURItemplate
patterns,matrixvariables,andrequestparameters.
Chapter4,WorkingwithSpringTagLibraries,willteachyouhowtouseSpringand
Springformtaglibrariesinwebformhandling.Youwilllearnhowtobinddomainobjects
withviewsandhowtousemessagebundlestoexternalizelabelcaptiontexts.Attheend
ofthischapter,youwillseehowtoaddaloginform.
Chapter5,WorkingwithViewResolver,willpresenttheinnermechanicsofhow
InternalResourceViewResolverresolvesaviewandtakesyouthroughhowtouse
variousviewtypes,suchasredirectviewandstaticview.Youwillalsolearnaboutthe
multipartresolverandcontentnegotiationviewresolver.Finally,youwilllearnhowtouse
exceptionhandlerresolvers.
Chapter6,InterceptYourStorewithInterceptor,willpresenttheconceptofaninterceptor
toyou.Youwilllearnhowtoleveragetheinterceptortohandleortransformrequestsand
responsesflexibly.Thischapterwillteachyouhowtomakeyourwebpagesupport
internalizationwiththehelpofLocaleChangeInterceptor.Thischapteralsointroduces
howtoperformauditlogginginalogfileusingtheinterceptorconcept.
Chapter7,ValidateYourProductswithaValidator,willgiveyouanoverviewofthe
validationconcept.Youwilllearnaboutbeanvalidation,andyouwilllearnhowto
performcustomvalidationalongwiththestandardbeanvalidation.Youwillalsolearn
abouttheclassicSpringvalidationandhowtocombineitwithbeanvalidation.
Chapter8,GiveRESTtoYourApplicationwithAjax,willteachyouthebasicprinciplesof
RESTandAjax.YouwilllearnhowtodevelopanapplicationinRESTfulservices.The
basicconceptofHTTPverbsandhowtheyarerelatedtostandardCRUDoperationswill
beexplained,andyouwilllearnhowtofireanAjaxrequestandhandleitfromaweb
page.
Chapter9,ApacheTilesandSpringWebFlowinAction,willteachyouhowtousethe
Springwebflowtodevelopworkflow-basedwebpages.Youwilllearnmoreaboutstates
andtransitionsinwebflowandhowtodefineaflowdefinition.Thischapteralsoteaches
youhowtodecomposeapageusingApachetiles.Youwillalsolearnmoreabout
TileViewResolverandhowtodefinereusableApachetilestemplates.
Chapter10,TestingyourApplication,willteachyouhowtoleveragetheSpringtesting
capabilitytotestyourcontrollers.Youwilllearnhowtoloadthetestcontextandhowto
mocktheserviceandrepositorylayers.ThischapteralsointroducesyoutotheSpring
MVCtestmoduleandteachesyouhowtousethat.
AppendixA,UsingtheGradleBuildTool,introducesyoutousingtheGradlebuildtool
foroursampleapplication.YouwilllearnabouttheGradlescriptthatisrequiredtobuild
ourprojectusingGradlebuildtool.
Whatyouneedforthisbook
Toruntheexamplesinthebook,thefollowingsoftwarewillberequired:
JavaSEDevelopmentKit7u45ornewer
Maven3.1.0
ApacheTomcat7.0
STS3.4.0release(SpringToolSuite)
Whothisbookisfor
Thisbookisdesignedtobefollowedfrombeginningtoend,althoughthosewithexisting
knowledgeofSpringMVCwillbeabletojumpintothelaterchaptersandpickoutthings
thatareimportanttothem.YouarenotexpectedtobeexperiencedwiththeSpring
framework.Someknowledgeofservletprogramminganddependencyinjectionwillbe
helpfulbutnotessential.Inanutshell,thebookprovidesclearpictures,illustrations,
concepts,andisideallysuitedforbeginnersandintermediatedevelopers.
Conventions
Inthisbook,youwillfindseveralheadingsappearingfrequently.
Togiveclearinstructionsofhowtocompleteaprocedureortask,weuse:
Timeforaction–heading
1. Action1
2. Action2
3. Action3
Instructionsoftenneedsomeextraexplanationsothattheymakesense,sotheyare
followedwith:
Whatjusthappened?
Thisheadingexplainstheworkingoftasksorinstructionsthatyouhavejustcompleted.
Youwillalsofindsomeotherlearningaidsinthebook,including:
Popquiz–heading
Theseareshortmultiple-choicequestionsintendedtohelpyoutestyourown
understanding.
Haveagohero–heading
Thesepracticalchallengesgiveyouideasforexperimentingwithwhatyouhavelearned.
Youwillalsofindanumberofstylesoftextthatdistinguishbetweendifferentkindsof
information.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,
pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Once
thedownloadisfinished,gotothedownloadeddirectoryandextractthe.zipfileintoa
convenientdirectoryofyourchoice.”
Ablockofcodeissetasfollows:
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>${greeting}</h1>
<p>${tagline}</p>
</div>
</div>
</section>
</body>
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevant
linesoritemsaresetinbold:
<servlet>
<servlet-name>DefaultServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
Anycommand-lineinputoroutputiswrittenasfollows:
C:\>mvn-version
ApacheMaven3.2.1(ea8b2b07643dbb1b84b6d16e1f08391b666bc1e9;2014-02-
14T12:37:52-05:00)
Mavenhome:C:\ProgramFiles\apache-maven-3.2.1
Javaversion:1.7.0_51,vendor:OracleCorporation
Javahome:C:\ProgramFiles\Java\jdk1.7.0_51\jre
Defaultlocale:en_SG,platformencoding:Cp1252
OSname:"windows7",version:"6.1",arch:"amd64",family:"windows"
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,in
menusordialogboxesforexample,appearinthetextlikethis:“ASystemProperties
windowwillappear;inthiswindow,selecttheAdvancedtabandclickonthe
EnvironmentVariablesbuttontoopentheenvironmentvariableswindow.”
Note
Warningsorimportantnotesappearinaboxlikethis.
Tip
Tipsandtricksappearlikethis.
Readerfeedback
Feedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthis
book—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforusto
developtitlesthatyoureallygetthemostoutof.
Tosendusgeneralfeedback,simplysendane-mailto<feedback@packtpub.com>,and
mentionthebooktitlethroughthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingor
contributingtoabook,seeourauthorguideonwww.packtpub.com/authors.
Customersupport
NowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelp
youtogetthemostfromyourpurchase.
Errata
Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdo
happen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthe
code—wewouldbegratefulifyouwouldreportthistous.Bydoingso,youcansave
otherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.If
youfindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-
errata,selectingyourbook,clickingontheerratasubmissionformlink,andenteringthe
detailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedand
theerratawillbeuploadedtoourwebsite,oraddedtoanylistofexistingerrata,underthe
Erratasectionofthattitle.
Piracy
PiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.At
Packt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucome
acrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswith
thelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<copyright@packtpub.com>withalinktothesuspectedpirated
material.
Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluable
content.
Chapter1.ConfiguringaSpring
DevelopmentEnvironment
Inthischapter,wearegoingtakealookathowwecancreateabasicSpringMVC
application.InordertodevelopaSpringMVCapplication,weneedsomeprerequisite
softwareandtools.First,wearegoingtolearnhowtoinstallalltheprerequisitesthatare
requiredtosetupourdevelopmentenvironmentsothatwecanstartdevelopingthe
application.
ThesetupandinstallationstepsgivenhereareforWindowsoperatingsystems,butdon’t
worry,asthestepsmaychangeonlyslightlyforotheroperatingsystems.Youcanalways
refertotherespectivetools/softwarevendor’swebsitestoinstalltheminotheroperating
systems.Inthischapter,wewilllearnhowtosetupJavaandconfiguretheMavenbuild
tool,installtheTomcatwebserver,installandconfiguretheSpringtoolsuite,andcreate
andrunourfirstSpringMVCproject.
SettingupJava
Obviously,thefirstthingthatweneedtodoisgetstartedwithJava.Themoretechnical
nameforJavaisJavaDevelopmentKit(JDK).JDKincludesaJavacompiler(javac),a
Javavirtualmachine,andavarietyofothertoolstocompileandrunJavaprograms.
Timeforaction–installingJDK
WearegoingtouseJava7butJava6oranyhigherversionisalsosufficient.Let’stakea
lookathowwecaninstallJDKonWindowsoperatingsystems:
1. GototheJavaSEdownloadpageontheOraclewebsitebyenteringthefollowing
URLinyourbrowser:
http://www.oracle.com/technetwork/java/javase/downloads/index.html.
2. ClickontheJavaplatformJDK7downloadlink;thiswilltakeyoutothelicense
agreementpage.Acceptthelicenseagreementbyselectingthatoptioninradio
button.
3. Now,clickonthelisteddownloadlinkthatcorrespondstoyourWindowsoperating
systemarchitecture;forinstance,ifyouroperatingsystemisoftype32bit,clickon
thedownloadlinkthatcorrespondstoWindowsx86.Or,ifyouroperatingsystemis
oftype64bit,clickonthedownloadlinkthatcorrespondstoWindowsx64.
4. Now,itwillstartdownloadingtheinstaller.Oncethedownloadisfinished,gotothe
downloadeddirectoryanddouble-clickontheinstaller.Thiswillopenupthe
followingwizardwindow;justclickontheNextbuttoninthewizard,leavingthe
defaultoptionsalone,andclickontheClosebuttonattheendofthewizard:
JDKinstallationwizard
Tip
Additionally,aseparatewizardalsopromptsyoutoinstallJavaRuntime
Environment(JRE).GothroughthatwizardaswelltoinstallJREinyoursystem.
5. NowyoucanseetheinstalledJDKdirectoryinthedefaultlocation;inourcase,the
defaultlocationisC:\ProgramFiles\Java\jdk1.7.0_25.
Timeforaction–settingupenvironment
variables
AfterinstallingJDK,westillneedtoperformsomemoreconfigurationstouseJava
convenientlyfromanydirectoryonourcomputer.Bysettinguptheenvironmentvariables
forJavaintheWindowsoperatingsystem,wecanmaketheJavacompilerandtools
availabletotheentireoperatingsystem:
1. NavigatetoControlPanel|System|Advancedsystemsettings.
2. ASystemPropertieswindowwillappear;inthiswindow,selecttheAdvancedtab
andclickontheEnvironmentVariablesbuttontoopentheenvironmentvariables
window.
3. Now,clickontheNewbuttonintheSystemvariablespanel,enterJAVA_HOMEasthe
variablename,andentertheinstalledJDKdirectorypathasthevariablevalue;inour
case,thisisC:\ProgramFiles\Java\jdk1.7.0_51.Incaseyoudonothaveproper
rightsfortheoperatingsystem,youwillnotbeabletoeditSystemvariables;inthat
case,youcancreatetheJAVA_HOMEvariableundertheUservariablespanel.
4. Now,inthesameSystemvariablespanel,double-clickonthePATHvariableentry;
anEditSystemVariablewindowwillappear.
SettingPATHEnvironmentvariable
5. EditVariablevalueofPathbyappendingthe;%JAVA_HOME%\bintexttoitsexisting
value.
Tip
Editthepathvariablecarefully;youshouldonlyappendthetextattheendofexisting
value.Don’tdeleteordisturbtheexistingvalues;makesureyouhaven’tmissedthe;
(semicolon)markasthatisthefirstletterinthetextthatyouwillappend.
6. NowclickontheOKbutton.
NowwehaveinstalledJavainourcomputer.Toverifywhetherourinstallationhasbeen
carriedoutcorrectly,openanewcommandwindowandtypejava–versionandpress
Enter;youwillseetheinstalledversionofJavaonthescreen:
C:\>java-version
javaversion"1.7.0_51"
Java(TM)SERuntimeEnvironment(build1.7.0_51-b13)
JavaHotSpot(TM)64-BitServerVM(build24.51-b03,mixedmode)
Configuringabuildtool
Buildingasoftwareprojecttypicallyincludessomeactivitiessuchasthefollowing:
Compilingallthesourcecode
Generatingthedocumentationfromthesourcecode
PackagingthecompiledcodeintoaJARorWARarchivefile
Installingthepackagedarchivesfilesonaserver
Manuallyperformingallthesetasksistimeconsumingandispronetoerrors.Therefore,
wetakethehelpofabuildtool.Abuildtoolisatoolthatautomateseverythingrelatedto
buildingasoftwareproject,fromcompilingtodeploying.
Timeforaction–installingtheMaven
buildtool
ManybuildtoolsareavailableforbuildingaJavaproject.WearegoingtouseMaven
3.2.1asourbuildtool.Let’stakealookathowwecaninstallMaven:
1. GotoMaven’sdownloadpagebyenteringthefollowingURLonyourbrowser:
http://maven.apache.org/download.cgi
2. Clickontheapache-maven-3.2.1-bin.zipdownloadlink,andstartthedownload.
3. Oncethedownloadisfinished,gotothedownloadeddirectoryandextractthe.zip
fileintoaconvenientdirectoryofyourchoice.
4. Nowweneedtocreateonemoreenvironmentvariable,calledM2_HOME,inawaythat
issimilartothewayinwhichwecreatedJAVA_HOME.EntertheextractedMavenzip
directory’spathasthevaluefortheM2_HOMEenvironmentvariable.
5. Createonemoreenvironmentvariable,calledM2,withthevalue%M2_HOME%\bin,as
showninthefollowingscreenshot:
SettingtheM2environmentvariable
6. FinallyappendtheM2variabletothePATHenvironmentvariableaswellbysimply
appendingthe;%M2%texttothePATHvariable’svalue.
NowwehaveinstalledtheMavenbuildtoolinourcomputer.Toverifywhetherour
installationhasbeencarriedoutcorrectly,weneedtofollowstepsthataresimilartothe
Javainstallationverification.Openanewcommandwindow,typemvn–version,and
pressEnter;youwillseethefollowingdetailsoftheMavenversion:
C:\>mvn-version
ApacheMaven3.2.1(ea8b2b07643dbb1b84b6d16e1f08391b666bc1e9;2014-02-
14T12:37:52-05:00)
Mavenhome:C:\ProgramFiles\apache-maven-3.2.1
Javaversion:1.7.0_51,vendor:OracleCorporation
Javahome:C:\ProgramFiles\Java\jdk1.7.0_51\jre
Defaultlocale:en_SG,platformencoding:Cp1252
OSname:"windows7",version:"6.1",arch:"amd64",family:"windows"
Installingawebserver
Sofar,wehavelearnedhowtoinstallJDKandMaven.Usingthesetools,wecancompile
theJavasourcecodeintothe.classfilesandpackagethese.classfilesintothe.jaror
.wararchives.However,howdowerunourpackagedarchives?Todothis,wetakethe
helpofawebserver;awebserverwillhostourpackagedarchivesasarunning
application.
Timeforaction–installingtheTomcat
webserver
ApacheTomcatisapopularJavawebservercumservletcontainer.Wearegoinguse
ApacheTomcatVersion7.0.Let’stakealookathowwecaninstalltheTomcatweb
server:
1. GototheApacheTomcathomepageusingthefollowingURLlink:
http://tomcat.apache.org/
2. ClickontheTomcat7.0downloadlink,anditwilltakeyoutothedownloadpage.
3. Clickonthe32-bit/64-bitWindowsServiceInstallerlink;itwillstartdownloading
theinstaller.
4. Oncethedownloadisfinished,gotothedownloadeddirectoryanddouble-clickon
theinstaller;thiswillopenupawizardwindow.
5. Justclickthroughthenextbuttonsinthewizard,leavingthedefaultoptionsalone,
andclickontheFinishbuttonattheendofthewizard.Notethatbeforeclickingon
theFinishbutton,justensurethatyouhaveuncheckedRunApacheTomcat
checkbox.
InstallingApacheTomcatwiththedefaultoptionworkssuccessfullyonlyifyouhave
installedJavainthedefaultlocation.Otherwise,youhavetocorrectlyprovidetheJRE
pathaccordingtothelocationofyourJavainstallationduringtheinstallationofTomcat,
asshowninthefollowingscreenshot:
TheJavaruntimeselectionfortheTomcatinstallation
Configuringadevelopmentenvironment
WeinstalledJavaandMaventocompileandpackageJavasourcecode,andweinstalled
Tomcattodeployandrunourapplication.However,priortoallthis,wehavetowritethe
SpringMVCcodesothatwecancompile,package,andrunthecode.
Wecanuseanysimpletexteditoronourcomputertowriteourcode,butthatwon’thelp
usmuchwithfeaturessuchasfindingsyntaxerrorsaswetype,autosuggestingimportant
keywords,syntaxhighlighting,easynavigation,andsoon.
IntegratedDevelopmentEnvironment(IDE)canhelpuswiththesefeaturestodevelop
thecodefasteranderrorfree.WearegoingtouseSpringToolSuite(STS)asourIDE.
Timeforaction–installingSpringTool
Suite
STSisthebestEclipse-powereddevelopmentenvironmenttobuildSpringapplications.
Let’stakealookathowwecaninstallSTS:
1. GototheSTSdownloadpageathttp://spring.io/tools/sts/all.
2. ClickontheSTSinstaller.exelinktodownloadthefilethatcorrespondstoyour
windowsoperatingsystemarchitecturetype(32bitor62bit);thiswillstartthe
downloadoftheinstaller.TheSTSstablereleaseversionatthetimeofwritingthis
bookisSTS3.4.0.RELEASEbasedonEclipse4.3.1.
3. Oncethedownloadisfinished,gotothedownloadeddirectoryanddouble-clickon
theinstaller;thiswillopenupawizardwindow.
4. Justclickthroughthenextbuttonsinthewizard,leavingthedefaultoptionsalone;if
youwanttocustomizetheinstallationdirectory,youcanspecifythatinthestepsyou
performinthewizard.
Tip
Downloadingtheexamplecode
YoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchased
fromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbook
elsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethe
filese-maileddirectlytoyou.
5. Instep5ofthewizard,youhavetoprovidetheJDKpath;justentertheJDKpath
thatyouconfiguredfortheJAVA_HOMEenvironmentvariable,asshowninthe
followingscreenshot:
SettingtheJDKpathduringtheSTSinstallation
WehavealmostinstalledallthetoolsandsoftwarerequiredtodevelopaSpringMVC
application,sonow,wecancreateourSpringMVCprojectonSTS.However,before
jumpingintocreatingaproject,weneedtoperformafinalconfigurationforSTS.
Timeforaction–configuringTomcaton
STS
AsIalreadymentioned,wecanusetheTomcatwebservertodeployourapplication,but
wehavetoinformSTSaboutthelocationoftheTomcatcontainersothatwecaneasily
deployourprojectfromSTStoTomcat.Let’sconfigureTomcatonSTS:
1. OpenSTSfromthestartmenuoptionorthedesktopicon.
2. STSwillaskyoutoprovideaworkspacedirectorylocation;provideaworkspace
directorypathasyouwishandclickontheOKbutton.
3. Now,STSwillshowyouawelcomescreen.Closethewelcomescreenandgotothe
menubarandnavigatetoWindow|preferences|Server|RuntimeEnvironments.
4. Youcanseetheavailableserverslistedontheright-handside;youmayalsosee
VMwarevFabrictcServerlistedundertheavailableservers,whichcomesalong
withtheSTSinstallation.
5. NowclickontheAddbuttontoaddourTomcatwebserver.
6. Awizardwindowwillappear;typetomcatintheSelectthetypeofruntime
environment:textbox,andalistofavailableTomcatversionswillbeshown.Just
selectTomcatv7.0andselecttheCreateanewlocalservercheckbox.Finally,click
ontheNextbutton,asshowninthefollowingscreenshot:
SelectingtheservertypeduringtheTomcatconfigurationonSTS
7. Inthenextwindow,clickontheBrowsebuttonandlocateTomcat’sinstalled
directory,andclickontheOKbutton.YoucanfindTomcat’sinstalleddirectory
underC:\ProgramFiles\ApacheSoftwareFoundation\Tomcat7.0ifyouhave
installedTomcatinthedefaultlocation.Then,clickontheFinishbutton,asshownin
thefollowingscreenshot:
SelectingtheTomcatlocationduringtheTomcatconfigurationonSTS
Whatjusthappened?
Instep2,weprovidedaworkspacepathforSTS.WhenyouopenSTSfortheveryfirst
timeafterinstallingSTS,itwillaskyoutoprovideaworkspacelocation.Thisisbecause
whenyoucreateaprojectonSTS,allyourprojectfileswillbecreatedunderthislocation
only.
OnceweenterSTS,weshouldinformSTSwheretheTomcathasbeeninstalled.Only
thencanSTSuseyourTomcatwebservertodeploytheproject.Thisisalsoaone-time
configuration;youneednotperformthisconfigurationeverytimeyouopenSTS.Wedid
thisbycreatinganewserverruntimeenvironmentinstep5.AlthoughSTSmightcome
withaninternalVMwarevFabrictcServer,wechosetousetheTomcatwebserveras
ourserverruntimeenvironment.
Timeforaction–configuringMavenon
STS
WelearnedhowtoconfigureTomcatonSTS.Similarly,tobuildourproject,STSwilluse
Maven.ButwehavetotellSTSwhereMavenhasbeeninstalledsothatitcanusethe
Maveninstallationtobuildourprojects.Let’stakealookathowwecanconfigureMaven
onSTS:
1. OpenSTSifitisnotalreadyopen.
2. NavigatetoWindow|Preferences|Maven|Installations.
3. Ontheright-handside,youcanseetheAddbutton,tolocateMaven’sinstallation.
4. ClickontheAddbuttonandchooseMaven’sinstalleddirectory,asshowninthe
followingscreenshot:
SelectingMaven’slocationduringtheMavenconfigurationonSTS
5. NowclickontheOKbuttoninthePreferenceswindowandcloseit.
CreatingourfirstSpringMVCproject
Sofar,wehavelearnedhowwecaninstallalltheprerequisitetoolsandsoftware.Nowwe
aregoingtodevelopourfirstSpringMVCapplicationusingSTS.STSprovidesaneasy-
to-useprojecttemplate.Usingthesetemplates,wecanquicklycreateourprojectdirectory
structureswithoutmanyproblems.
Timeforaction–creatingaSpringMVC
projectinSTS
Let’screateourfirstspringMVCprojectinSTS:
1. InSTS,navigatetoFile|New|Project;aNewProjectwizardwindowwillappear.
2. SelectMavenProjectfromthelistandclickontheNextbutton,asshowninthe
followingscreenshot:
Mavenproject’stemplateselection
3. Now,aNewMavenProjectdialogwindowwillappear;justselectthecheckboxthat
hastheCreateasimpleproject(skiparchetypeselection)caption,andclickonthe
Nextbutton.
4. Thewizardwillaskyoutospecifyartifact-relatedinformationforyourproject;just
enterGroupIdascom.packt,ArtifactIdaswebstore.Then,selectPackagingas
warandclickontheFinishbutton,asshowninthefollowingscreenshot:
Whatjusthappened?
Wejustcreatedthebasicprojectstructure.AnyJavaprojectfollowsacertaindirectory
structuretoorganizeitssourcecodeandstaticresources.Insteadofmanuallycreatingthe
wholedirectoryhierarchybyourselves,wejusthandedoverthatjobtoSTS.Bycollecting
somebasicinformationaboutourproject,suchasGroupId,ArtifactId,andthe
Packagingstylefromus,itisclearthatSTSissmartenoughtocreatethewholeproject
directorystructurewiththehelpoftheMavenplugin.Actually,whatishappeningbehind
thescreenisthatSTSisinternallyusingMaventocreatetheprojectstructure.
Wewantourprojecttobedeployableinanyservletcontainer-basedwebserver,suchas
Tomcat,andthat’swhyweselectedthePackagingstyleaswar.Afterexecutingstep4,
youwillseetheprojectstructureinPackageExplorer,asshowninthefollowing
screenshot:
Theprojectstructureoftheapplication
SpringMVCdependencies
AswearegoingtouseSpringMVCAPIsheavilyinourproject,weneedtheSpringjars
inourprojectduringthedevelopment.AsIalreadymentioned,Mavenwilltakecareof
managingdependenciesandpackagingtheproject.
Timeforaction–addingSpringjarsto
theproject
Let’stakealookathowwecanaddthespring-relatedjarsviatheMavenconfiguration:
1. Openpom.xml;youcanfindpom.xmlundertherootdirectoryoftheprojectitself.
2. Youwillseesometabsatthebottomofthepom.xmlfile.Ifyoudonotseethesetabs,
thenright-clickonpom.xmlandselecttheOpenWithoptionfromthecontextmenu
andchooseMavenPOMeditor.SelecttheDependenciestabandclickontheAdd
buttonintheDependenciessection.Don’tgetconfusedwiththeAddbuttonofthe
DependenciesManagementsection.YoushouldchoosetheAddbuttonintheleft-
handsidepane.
3. ASelectDependencywindowwillappear;enterGroupIdas
org.springframework,ArtifactIdasspring-webmvc,andVersionas
4.0.3.RELEASE.SelectScopeascompileandthenclickontheOKbutton,asshown
inthefollowingscreenshot:
4. Similarly,addthedependencyforJavaServerPagesStandardTagLibrary(JSTL)
byclickingonthesameAddbutton;thistime,enterGroupIdasjavax.servlet,
ArtifactIdasjstl,Versionas1.2,andselectScopeascompile.
5. Finally,addonemoredependencyforservlet-api;repeatthesamestepwithGroup
Idasjavax.servlet,ArtifactIdasjavax.servlet-api,andVersionas3.1.0,but
thistime,selectScopeasprovidedandthenclickontheOKbutton.
6. Asalaststep,don’tforgettosavethepom.xmlfile.
Whatjusthappened?
IntheMavenworld,pom.xml(ProjectObjectModel)istheconfigurationfilethatdefines
therequireddependencies.Whilebuildingourproject,Mavenwillreadthatfileandtryto
downloadthespecifiedjarsfromtheMavencentralbinaryrepository.YouneedInternet
accessinordertodownloadjarsfromMaven’scentralrepository.Mavenusesan
addressingsystemtolocateajarinthecentralrepository,whichconsistsofGroupId,
ArtifactId,andVersion.
Everytimeweaddadependency,anentrywillbemadewithinthe<dependencies></
dependencies>tagsinthepom.xmlfile.Forexample,ifyougotothepom.xmltabafter
finishingstep3,youwillseeanentryforspring-mvcasfollowswithinthe
<dependencies></dependencies>tag:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>
Weaddedthedependencyforspring-mvcinstep3,andinstep4,weaddedthe
dependencyforJSTL.JSTLisacollectionofusefulJSPtagsthatcanbeusedtowriteJSP
pageseasily.Finally,weneedaservlet-apijarinordertouseservlet-relatedcode;thisis
whatweaddedinstep5.
However,thereisalittledifferenceinthescopeoftheservlet-apidependencycompared
totheothertwodependencies.Weonlyneedservlet-apiwhilecompilingourproject.
Whilepackagingourprojectaswar,wedon’twanttotheshipservlet-apijaraspartofour
project.ThisisbecausetheTomcatwebserverwouldprovidetheservlet-apijarwhile
deployingourproject.Thisiswhyweselectedthescopeasprovidedfortheservlet-api.
Afterfinishingstep6,youwillseeallthedependentjarsconfiguredinyourproject,as
showninthefollowingscreenshot,undertheMavenDependencieslibrary:
Weaddedonlythreejarsasourdependencies,butifyounoticeinourMavendependency
librarylist,youwillseemorethanthreejarentries.Canyouguesswhy?Whatifour
dependentjarshaveadependencyonotherjarsandsoon?
Forexample,ourspring-mvcjarisdependentonthespring-core,spring-context,and
spring-aopjars,butwehavenotspecifiedthosejarsinourpom.xmlfile;thisiscalled
transitivedependenciesintheMavenworld.Inotherwords,wecansaythatourproject
istransitivelydependentonthesejars.Mavenwillautomaticallydownloadallthese
transitivedependentjars;thisisthebeautyofMaven.Itwilltakecareofallthe
dependencymanagementautomatically;weneedtoinformMavenonlyaboutthefirst
leveldependencies.
Timeforaction–addingJavaversion
propertiesinpom.xml
Wesuccessfullyaddedalltherequiredjarstoourproject,butweneedtoperformone
smallconfigurationinourpom.xmlfile,thatis,tellingMaventouseJavaVersion7while
buildingourproject.HowdowetellMaventodothis?Simplyaddtwopropertyentriesin
pom.xml.Let’sdothis.
1. Openpom.xml.Youwillseesometabsatthebottomofpom.xml;selecttheOverview
tabfromthebottomofpom.xml,expandthepropertiesaccordion,andclickonthe
Createbutton.
2. Now,anAddpropertywindowwillappear;enterNameasmaven.compiler.source
andValueas1.7.
AddingtheJavacompilerversionpropertiestoPOM
3. Similarly,createonemorepropertywithNameasmaven.compiler.targetand
Valueas1.7.
4. Finally,savepom.xml.
Ajump-starttoMVC
Wecreatedourprojectandaddedalltherequiredjars,sowearereadytocode.Weare
goingtoincrementallybuildanonlinewebstorethroughoutthisbook,chapterbychapter.
Asafirststep,let’screateahomepageinourprojecttowelcomeourcustomers.
Ouraimissimple;whenweenterthehttp://localhost:8080/webstore/URLonthe
browser,wewouldliketoshowawelcomepagethatissimilartothefollowing
screenshot:
Don’tworryifyouarenotabletounderstandsomeofthecode;wearegoingtotakea
lookateachconceptindetailintheupcomingchapters.Asofnow,ouraimistohave
quickhands-onexperienceofdevelopingasimplewebpageusingSpringMVC.
Timeforaction–addingawelcomepage
Tocreateandaddawelcomepage,weneedtoexecutethefollowingsteps:
1. CreateaWEB-INF/jsp/directorystructureunderthesrc/main/webapp/directory;
createajspviewfilecalledwelcome.jspunderthesrc/main/webapp/WEB-INF/jsp/
directory,andaddthefollowingcodesnippetsintoitandsaveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-8859-
1">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Welcome</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>${greeting}</h1>
<p>${tagline}</p>
</div>
</div>
</section>
</body>
</html>
2. CreateaclasscalledHomeControllerunderthecom.packt.webstore.controller
packageinthesourcedirectorysrc/main/java,andaddthefollowingcodeintoit:
packagecom.packt.webstore.controller;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.RequestMapping;
@Controller
publicclassHomeController{
@RequestMapping("/")
publicStringwelcome(Modelmodel){
model.addAttribute("greeting","WelcometoWebStore!");
model.addAttribute("tagline","Theoneandonlyamazingwebstore");
return"welcome";
}
}
Whatjusthappened?
Instep1,wejustcreatedaJSPview;theimportantthingweneedtonoticehereisthe
<h1>tagandthe<p>tag.Boththetagshavesomeexpressionthatissurroundedbycurly
bracesandprefixedbythe$symbol:
<h1>${greeting}</h1>
<p>${tagline}</p>
So,whatisthemeaningof${greeting}?Itmeansthatgreetingisakindofvariable;
duringtherenderingofthisJSPpage,thevaluestoredinthegreetingvariablewillbe
shownintheheader1style,andsimilarly,thevaluestoredinthetaglinevariablewillbe
shownasaparagraph.
Sonow,thenextquestionofwherewewillassignvaluestothosevariablesarises.Thisis
wherethecontrollerwillbeofhelp;withinthewelcomemethodoftheHomeController
class,takealookatthefollowinglinesofcode:
model.addAttribute("greeting","WelcometoWebStore!");
model.addAttribute("tagline","Theoneandonlyamazingwebstore");
Youcanobservethatthetwovariablenames,greetingandtagline,arepassedasafirst
parameteroftheaddAttributemethodandthecorrespondingsecondparameteristhe
valueforeachvariable.Sowhatwearedoinghereissimplyputtingtwostrings,"Welcome
toWebStore!"and"Theoneandonlyamazingwebstore",intothemodelwiththeir
correspondingkeysasgreetingandtagline.Asofnow,simplyconsiderthefactthat
modelisakindofmap.Folkswithknowledgeofservletprogrammingcanconsiderthe
factthatmodel.addAttributeworksexactlylikerequest.setAttribute.
So,whatevervalueweputintothemodelcanberetrievedfromtheview(jsp)usingthe
correspondingkeywiththehelpofthe${}placeholderexpressionnotation.
Thedispatcherservlet
Wecreatedacontrollerthatcanputvaluesintothemodel,andwecreatedtheviewthat
canreadthosevaluesfromthemodel.So,themodelactsasanintermediatebetweenthe
viewandthecontroller;withthis,wehavefinishedallthecodingpartrequiredtopresent
thewelcomepage.Sowillwebeabletorunourprojectnow?No;atthisstage,ifwerun
ourprojectandenterthehttp://localhost:8080/webstore/URLonthebrowser,we
willgetanHTTPStatus404error.Thisisbecausewehavenotperformedanyservlet
mappingyet.InaSpringMVCproject,wemustconfigureafrontservletmapping.The
frontservlet(sometimescalledthefrontcontroller)mappingisadesignpatternwhereall
requestsforaparticularwebapplicationaredirectedtothesameservlet.Onesuchfront
servletgivenbySpringMVCframeworkisthedispatcherservlet
(org.springframework.web.servlet.DispatcherServlet).Wehavenotconfigureda
dispatcherservletforourprojectyet;thisiswhywegettheHTTPStatus404error.
Timeforaction–configuringthe
dispatcherservlet
ThedispatcherservletiswhatexaminestheincomingrequestURLandinvokestheright
correspondingcontrollermethod.Inourcase,thewelcomemethodfromthe
HomeControllerclassneedstobeinvokedifweenterthe
http://localhost:8080/webstore/URLonthebrowser.Solet’sconfigurethe
dispatcherservletforourproject:
1. Createweb.xmlunderthesrc/main/webapp/WEB-INF/directoryinyourprojectand
enterthefollowingcontentinsideweb.xmlandsaveit:
<web-appversion="3.0"xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>DefaultServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DefaultServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2. NowcreateonemorexmlfilecalledDefaultServlet-servlet.xmlunderthesame
src/main/webapp/WEB-INF/directoryandenterthefollowingcontentintoitand
saveit:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<mvc:annotation-driven/>
<context:component-scanbase-package="com.packt.webstore"/>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver
">
<propertyname="prefix"value="/WEB-INF/jsp/"/>
<propertyname="suffix"value=".jsp"/>
</bean>
</beans>
Whatjusthappened?
Ifyouknowaboutservletprogramming,youmightbequitefamiliarwiththeservlet
configurationandweb.xml.Inweb.xml,weconfiguredaservletnamedDefaultServlet,
whichismoreorlesssimilartoanyothernormalservletconfiguration.Theonly
differenceisthatwehavenotcreatedanyservletclassforthatconfiguration.Instead,the
servletclass(org.springframework.web.servlet.DispatcherServlet)isprovidedby
theSpringMVCframework,andwemakeuseofitinweb.xml.Afterthisstep,our
configuredDispatcherServlet(DefaultServlet)willbereadytohandleanyrequests
thatcometoourapplicationonruntimeandwilldispatchtherequesttothecorrect
controller’smethod.
However,DispatcherServletshouldknowwhereourcontrollersandviewfilesare
locatedinourproject,andonlythencanitproperlydispatchtherequesttothecorrect
controllers.SowehavetogivesomehinttoDispatcherServlettolocatethecontrollers
andviewfiles.Thisiswhatweconfiguredinstep2throughtheDispatcherServlet-
servlet.xmlfile.
Don’tworryifyouarenotabletounderstandeachandeveryconfigurationinweb.xmland
DispatcherServlet-servlet.xml;wewilltakealookattheseconfigurationfilesinnext
chapter.Asofnow,justrememberthatthisisaone-timeconfigurationthatisneededto
runourprojectsuccessfully.
Deployingourproject
Wesuccessfullycreatedtheprojectinthelastsection,soyoumightbecurioustoknow
whatwouldhappenifwerunourprojectnow.Asourprojectisawebproject,weneeda
webservertorunit.
Timeforaction–runningtheproject
AswealreadyconfiguredtheTomcatwebserverinourSTS,let’suseTomcattodeploy
andrunourproject:
1. Right-clickonyourprojectfromPackageExplorerandnavigatetoRunAs|Run
onServer.
2. Aserverselectionwindowwillappearwithalltheavailableserverslisted;justselect
theserverthatwehaveconfigured,Tomcatv7.0.
3. Atthebottomofthewindow,youcanseeacheckboxwiththecaptionthatsays
Alwaysusethisserverwhenrunningthisproject;selectthischeckboxandenter
theFinishbutton,asshowninthefollowingscreenshot:
ConfiguringthedefaultserverforaSpringMVCproject
4. Nowyouwillseeawebpagethatwillshowyouawelcomemessage.
Summary
Inthischapter,wesawhowtoinstallalltheprerequisitesthatareneededtogetstartedand
runourfirstSpringMVCapplication,forexample,installingJDK,theMavenbuildtool,
theTomcatservletcontainer,andSTSIDE.
WealsolearnedhowtoperformvariousconfigurationsinourSTSIDEforMavenand
Tomcat,createdourfirstSpringMVCproject,andaddedallSpring-relateddependentjars
throughtheMavenconfiguration.
Wehadaquickhands-onexperienceofdevelopingawelcomepageforourwebstore
application.Duringthatcourse,welearnedhowtoputvaluesintoamodelandhowto
retrievethesevaluesfromthemodel.
WhateverwehaveseensofarisjustaglimpseofSpringMVC,butthereismuchmoreto
uncover,forexample,howthemodelandviewcontrollerareconnectedtoeachotherand
howtherequestflowoccurs.Wearegoingtoexplorethesetopicsinthenextchapter,so
seeyouthere!
Chapter2.SpringMVCArchitecture–
ArchitectingYourWebStore
WhatwesawinthefirstchapterisnothingbutaglimpseofSpringMVC;intheprevious
chapter,ourtotalfocuswasjustongettingittorunaSpringMVCapplication.Now,it’s
timeforustodeep-diveintoSpringMVCarchitecture.
Bytheendofthischapter,youwillhaveaclearunderstandingof:
Thedispatcherservletandrequestmapping
Thewebapplicationcontextandconfiguration
TheSpringMVCrequestflowandWebMVC
Thewebapplicationarchitecture
Thedispatcherservlet
Inthefirstchapter,wewereintroducedtothedispatcherservletandsawhowtodefinea
dispatcherservletinweb.xml.Welearnedthateverywebrequestfirstcomestothe
dispatcherservlet.Thedispatcherservletistheonethatdecidesthecontrollermethodthat
itshoulddispatchthewebrequestto.Inthepreviouschapter,wecreatedawelcomepage
thatwillbeshownwheneverweentertheURLhttp://localhost:8080/webstore/on
thebrowser.MappingaURLtotheappropriatecontrollermethodistheprimarydutyofa
dispatcherservlet.
SothedispatcherservletreadsthewebrequestURLandfindstheappropriatecontroller
methodthatcanservethatwebrequestandinvokesit.Thisprocessofmappingaweb
requesttoaspecificcontrollermethodiscalledrequestmapping,andthedispatcher
servletisabletodothiswiththehelpofthe@RequestMappingannotation
(org.springframework.web.bind.annotation.RequestMapping).
Timeforaction–examiningrequest
mapping
Let’sobservewhatwillhappenwhenyouchangethevalueattributeofthe
@RequestMappingannotationbyexecutingthefollowingsteps:
1. OpenyourSTSandrunthewebstoreproject;justright-clickonyourprojectand
chooseRunAs|RunonServer.Youwillbeabletoviewthesamewelcome
messageonthebrowser.
2. Now,gototheaddressbarofthebrowserandentertheURL,
http://localhost:8080/webstore/welcome.
3. YouwillseetheHTTPStatus404errorpageonthebrowser,andyouwillalsosee
thefollowingwarningintheconsole:
WARNING:NomappingfoundforHTTPrequestwithURI[/webstore/welcome]
inDispatcherServletwithname'DefaultServlet'
Anerrorlogdisplayingthe“Nomappingfound”warningmessage
4. Now,opentheHomeControllerclass,changethe@RequestMappingannotation’s
valueattributeto/welcome,andsaveit.Basically,yournewrequestmapping
annotationwilllooklike@RequestMapping("/welcome").
5. Again,runtheapplicationandenterthesameURLthatyouenteredinstep2;now
youwillbeabletoseethesamewelcomemessageonthebrowser,withoutany
requestmappingerror.
6. Finally,opentheHomeControllerclassandrevertthechangesthatweremadetothe
@RequestMappingannotation’svalue;justmakeit@RequestMapping("/")againand
saveit.
Whatjusthappened?
Afterstartingourapplication,whenweentertheURL
http://localhost:8080/webstore/welcomeonthebrowser,thedispatcherservlet
(org.springframework.web.servlet.DispatcherServlet)immediatelytriestofinda
matchingcontrollermethodfortherequestpath,/welcome.
Tip
InaSpringMVCapplication,theURLcanlogicallybedividedintofiveparts(seethe
followingfigure);the@RequestMappingannotationonlymatchesagainsttheURLrequest
path.Itomitsthescheme,hostname,applicationname,andsoon.
The@RequestMappingannotationhasonemoreattributecalledmethodtofurthernarrow
downthemappingbasedontheHTTPrequestmethodtypes(GET,POST,HEAD,OPTIONS,
PUT,DELETE,andTRACE).Ifwedonotspecifythemethodattributeinthe
@RequestMappingannotation,thedefaultmethodwillbeGET.Wewilllearnmoreabout
themethodattributeofthe@RequestMappingannotationinChapter4,WorkingwithSpring
TagLibraries,underthesectiononformprocessing.
ThelogicalpartsofatypicalSpringMVCapplicationURL
Sincewedon’thaveacorrespondingrequestmappingforthegivenURLpath,/welcome,
wegettheHTTPStatus404erroronthebrowserandthefollowingerrorlogonthe
console:
WARNING:NomappingfoundforHTTPrequestwithURI[/webstore/welcome]in
DispatcherServletwithname'DefaultServlet'
Fromtheerrorlog,wecanclearlyunderstandthatthereisnorequestmappingforthe
URLpath,/webstore/welcome.So,wetrytomapthisURLpathtotheexistingcontroller
method;that’swhy,instep4,weputonlytherequestpathvalue,/welcome,inthe
@RequestMappingannotationasthevalueattribute.Noweverythingworksperfectlyfine.
Finally,werevertedour@RequestMappingannotation’svalueto/againinstep6.Whydid
wedothis?BecausewewantittoshowthewelcomepageunderthewebrequestURL
http://localhost:8080/webstore/again.Observecarefullythatherethelastsingle
character/istherequestpath.Wewillseemoreaboutrequestmappinginupcoming
chapters.
Popquiz–requestmapping
Q1.IfwehaveaSpringMVCapplicationforlibrarymanagementcalledBookPediaand
wanttomapawebrequestURL,
http://localhost:8080/BookPedia/category/fiction,toacontrollermethod,how
willweformthe@RequestMappingannotation?
1. @RequestMapping("/fiction").
2. @RequestMapping("/category/fiction").
3. @RequestMapping("/BookPedia/category/fiction").
Thewebapplicationcontext
InaSpring-basedapplication,ourapplicationobjectslivewithinanobjectcontainer.This
containercreatesobjectsandassociationsbetweenobjects,andmanagestheircomplete
lifecycle.ThesecontainerobjectsarecalledSpring-managedbeans(orsimplybeans),and
thecontaineriscalledanapplicationcontextintheSpringworld.
ASpringcontainerusesdependencyinjection(DI)tomanagethebeansthatmakeupan
application.Anapplicationcontext
(org.springframework.context.ApplicationContext)createsbeansandassociate
beanstogetherbasedonthebeanconfigurationanddispensesbeansonrequest.Abean
configurationcanbedefinedviaanXMLfile,annotation,orevenviaJavaconfiguration
classes.WewilluseonlyXML-andannotation-basedbeanconfigurationsinourchapters.
Awebapplicationcontextistheextensionofanapplicationcontext,designedtowork
withthestandardservletcontext(javax.servlet.ServletContext).Awebapplication
contexttypicallycontainsfrontend-relatedbeans,suchasviewsandviewresolvers.Inthe
firstchapter,wecreatedanXMLfilecalledDefaultServlet-servlet.xml,whichis
nothingbutabeanconfigurationfileforourwebapplicationcontext.
Timeforaction–understandingtheweb
applicationcontext
Youhavereceivedenoughofanintroductiononthewebapplicationcontext;now,tweaka
littlebitwiththenameandlocationofthewebapplicationcontextconfigurationfile
(DefaultServlet-servlet.xml)andobservetheeffect.Performthefollowingsteps:
1. RenametheDefaultServlet-servlet.xmlfiletoDispatcherServlet-
servlet.xml;youcanfindDefaultServlet-servlet.xmlunderthe
src/main/webapp/WEB-INF/directory.
2. Then,runyourwebstoreprojectagainandentertheURL,
http://localhost:8080/webstore/;youwillseeanHTTPStatus500error
messageonyourwebpageandaFileNotFoundExceptionerrorinthestacktraceas
follows:
java.io.FileNotFoundException:CouldnotopenServletContextresource
[/WEB-INF/DefaultServlet-servlet.xml]
AnerrormessagedisplayingFileNotFoundExceptionforDefaultServlet-servlet.xml
3. Tofixthiserror,changethenameofDefaultServlettoDispatcherServletin
web.xml;basically,afterchangingthenametoDispatcherServlet,yourservlet
configurationwilllooklikethefollowingintheweb.xmlfile:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-
class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
4. Now,runyourapplicationandentertheURL,http://localhost:8080/webstore/;
youwillseethewelcomemessageagain.
5. RenameyourDispatcherServlet-servlet.xmlfiletoDispatcherServlet-
context.xmloncemore.
6. Next,createadirectorystructurespring/webcontext/undertheWEB-INFdirectory
andmovetheDispatcherServlet-context.xmlfiletothesrc/main/webapp/WEB-
INF/spring/webcontext/directory.
7. Then,runyourapplication,andyouwillseeanHTTPStatus500errormessageon
yourwebpageagainandaFileNotFoundExceptionerrormessageinthestacktrace:
java.io.FileNotFoundException:CouldnotopenServletContextresource
[/WEB-INF/DispatcherServlet-servlet.xml]
8. Tofixthiserror,addthefollowingtagswithinthe<servlet>and</servlet>tags
inweb.xmlasshowninthefollowingcode:
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/webcontext/DispatcherServlet-context.xml
</param-value>
</init-param>
9. Now,runtheapplicationagainandentertheURL,
http://localhost:8080/webstore/;youwillbeabletoseethewelcomemessage
again.
Whatjusthappened?
So,whatwedidfirstwasrenamedtheDefaultServlet-servlet.xmlfileto
DispatcherServlet-servlet.xml,andwegotaFileNotFoundExceptionerrorat
runtime,asfollows:
java.io.FileNotFoundException:CouldnotopenServletContextresource
[/WEB-INF/DefaultServlet-servlet.xml]
Tofixtheerror,wechangedourdispatcherservletconfiguration,asfollows,inthe
web.xmlfile:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
WechangedtheservletnametoDispatcherServletinordertoalignwiththeweb
applicationcontextconfigurationfilenamedDispatcherServlet-servlet.xml.So,based
onthisexercise,wecanlearnthatduringthestart-upofanySpringMVCproject,the
dispatcherservletwilllookforawebapplicationcontextconfigurationfileofthepattern
<ConfigureddispatcherServletName>-servlet.xmlundertheWEB-INFdirectory.It
isourresponsibilitytokeepthewebapplicationcontextconfigurationfileundertheWEB-
INFdirectorywiththerightname.However,whatifwewishtokeepthefileinsomeother
directory?
Tip
Oneoftheimportantthingstobenotedin<servlet-mapping>isthevalueofthe<url-
pattern>/</url-pattern>tag.Byassigning/astheURLpatternforthedispatcher
servlet,wemakeDispatcherServletthedefaultservletforourwebapplication.So,
everywebrequestcomingtoourwebapplicationwillbehandledbyDispatcherServlet.
Forinstance,insteps5and6,werenamedthewebapplicationcontextconfigurationfile
andmovedittoacompletelynewdirectory(src/main/webapp/WEB-
INF/spring/webcontext/).Inthatcase,howdidwefixtheHTTPStatus500error?The
answerlieswithinapropertycalledcontextConfigLocation.Forthedispatcherservletto
locatethewebcontextconfigurationfileeasily,wegavethelocationofthisfiletothe
dispatcherservletthroughapropertycalledcontextConfigLocation.That’swhywe
addedthispropertytothedispatcherservletinstep8,asfollows:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-
class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/webcontext/DispatcherServlet-context.xml
</param-value>
</init-param>
</servlet>
Now,weareabletorunourapplicationwithoutanyproblem.Okay,weplayedalotwith
thewebapplicationcontextconfigurationfileandlearnedthatthedispatcherservlet
shouldknowaboutthewebapplicationcontextconfigurationfileduringthestart-upof
ourproject.Sothenextquestionis:whyisthedispatcherservletlookingforthisweb
contextconfigurationfile,andwhatisdefinedinsidethisfile?Let’sfindouttheanswer,
butbeforethat,youmayanswerthefollowingpopquizquestionstomakesureyou
understandtheconceptofthewebapplicationcontextconfiguration.
Popquiz–thewebapplicationcontext
Q1.IfthecontextConfigLocationpropertywasnotconfiguredinourdispatcherservlet
configuration,underwhichlocationwouldSpringMVClookforthewebapplication
contextconfigurationfile?
1. IntheWEB-INFdirectory
2. InWEB-INF/spring
3. InWEB-INF/spring/appServlet
Q2.IfwedonotwanttoprovidecontextConfigLocationtothefollowingdispatcher
servletconfiguration,howdoweavoidtheHTTPStatus500error?
<servlet>
<servlet-name>FrontController</servlet-name>
<servlet-
class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
1. BycreatingacontextfilecalledFrontController-context.xmlintheWEB-INF
directory
2. BycreatingafilecalledDispatcherServlet-context.xmlinWEB-INF
3. BycreatingafilecalledFrontController-servlet.xmlinWEB-INF
Thewebapplicationcontextconfiguration
Thewebapplicationcontextconfigurationfile(DispatcherServlet-context.xml)is
nothingbutasimpleSpringbeanconfigurationfile.Springwillcreatebeans(objects)for
everybeandefinitionmentionedinthisfileduringbootupofourapplication.Ifyouopen
thiswebapplicationcontextconfigurationfile(/WEB-
INF/spring/webcontext/DispatcherServlet-context.xml),youwillfindsome
configurationandbeandefinitionasfollows:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<mvc:annotation-driven/>
<context:component-scanbase-package="com.packt.webstore"/>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<propertyname="prefix"value="/WEB-INF/jsp/"/>
<propertyname="suffix"value=".jsp"/>
</bean>
</beans>
Thefirsttagwithinthe<beans>definitionis<mvc:annotation-driven/>.Bythistag,
wetellSpringMVCtoconfiguretheDefaultAnnotationHandlerMapping,
AnnotationMethodHandlerAdapter,andExceptionHandlerExceptionResolverbeans.
ThesebeansarerequiredforSpringMVCtodispatchrequeststothecontrollers.
Actually<mvc:annotation-driven/>doesmanythingsbehindthescreen.Italso
enablessupportforvariousconvenientannotationssuchas@NumberFormatand
@DateTimeFormattoformattheformbeanfieldsduringformbinding.Similarly,wehave
the@Validannotationtovalidatethecontrollermethod’sparameters.ItalsosupportsJava
objectsto/fromanXMLorJSONconversionviathe@RequestBodyand@ResponseBody
annotationsinthe@RequestMappingor@ExceptionHandlermethodduringformbinding.
Wewillseetheusageoftheseannotationsinlaterchapters.Asofnow,justrememberthat
the<mvc:annotation-driven/>tagisneededtoenableannotationssuchas
@controllerand@RequestMapping.
Whatisthepurposeofthesecondtag,<context:component-scan>?Youneedabitof
backgroundinformationtounderstandthepurposeofthe<context:component-scan>tag.
The@Controllerannotationindicatesthataparticularclassservestheroleofacontroller.
Wealreadylearnedthatthedispatcherservletsearchessuchannotatedclassesformapped
methods(the@RequestMappingannotatedmethods)toserveawebrequest.Inorderto
makethecontrolleravailableforsearching,weneedtocreateabeanforthiscontrollerin
ourwebapplicationcontext.
Wecancreatebeansforcontrollersexplicitlyviathebeanconfiguration(usingthe<bean>
tag—youcanseehowwecreatedabeanfortheInternalResourceViewResolverclass
usingthe<bean>taginthenextsection),orwecanhandoverthattasktoSpringviathe
autodetectionmechanism.Toenabletheautodetectionofthe@Controllerannotated
classes,weneedtoaddcomponentscanningtoourconfigurationusingthe
<context:component-scan>tag.Now,youfinallyunderstandthepurposeofthe
<context:component-scan>tag.
Springwillcreatebeans(objects)forevery@Controllerclassatruntime.Thedispatcher
servletwillsearchforthecorrectrequestmappingmethodinevery@Controllerbean
basedonthe@RequestMappingannotation,toserveawebrequest.Thebase-package
propertyofa<context:component-scan>tagindicatesthepackageunderwhichSpring
shouldsearchforcontrollerclassestocreatebeans:
<context:component-scanbase-package="com.packt.webstore"/>
TheprecedinglineinstructsSpringtosearchforcontrollerclasseswithinthe
com.packt.webstorepackageanditssubpackages.
Tip
The<context:component-scan>tagnotonlyrecognizescontrollerclasses,italso
recognizesotherstereotypessuchasservicesandrepositoryclassesaswell.Wewilllearn
moreaboutservicesandrepositorieslater.
Popquiz–webapplicationcontextconfiguration
Q1.WhatneedstobedonetoidentifyaclassbySpringasacontroller?
1. Thatparticularclassshouldhavethe@Controllerannotation.
2. The<mvc:annotation-driven/>and<context:component-scan>tagsshouldbe
specifiedinthewebapplicationcontextconfigurationfile.
3. Thatparticularclassshouldbeputupinapackageorsubpackagethathasbeen
specifiedasabasepackageinthe<context:component-scan>tag.
4. Alloftheabove.
Viewresolvers
Wesawthepurposeofthefirsttwotagsthatarespecifiedwithinthewebapplication
contextconfigurationfile:
<mvc:annotation-driven/>
<context:component-scanbase-package="com.packt.webstore"/>
Basedonthesetags,Springcreatesthenecessarybeanstohandleawebrequestandalso
createsbeansforallthe@Controllerclasses.However,torunaSpringMVCapplication
successfully,Springneedsonemorebean;thisbeaniscalledaviewresolver.
Aviewresolverhelpsthedispatcherservletidentifytheviewsthathavetoberenderedas
theresponseforaspecificwebrequest.SpringMVCprovidesvariousviewresolver
implementationstoidentifyviews,andInternalResourceViewResolverisonesuch
implementation.Thefinaltaginthewebapplicationcontextconfigurationisthebean
definitionfortheInternalResourceViewResolverclassasfollows:
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<propertyname="prefix"value="/WEB-INF/jsp/"/>
<propertyname="suffix"value=".jsp"/>
</bean>
Throughtheprecedingbeandefinitioninthewebapplicationcontextconfiguration,we
instructSpringMVCtocreateabeanfortheInternalResourceViewResolverclass
(org.springframework.web.servlet.view.InternalResourceViewResolver).Wewill
learnmoreabouttheviewresolverinChapter5,WorkingwithViewResolver.
Timeforaction–understanding
InternalResourceViewResolver
WeinstructSpringtocreateabeanforanInternalResourceViewResolverclass,but
why?Whoisgoingtousethisbean?Whatistheroleofthe
InternalResourceViewResolverbeaninSpringMVC?Findtheanswertothese
questionsthroughthefollowingexercise:
1. OpenDispatcherServlet-context.xml;youcanfindthisfileunderthe
src/main/webapp/WEB-INF/spring/webcontext/directoryinyourproject.
2. ChangetheprefixpropertyvalueoftheInternalResourceViewResolverbeanas
follows:
<propertyname="prefix"value="/WEB-INF/views/"/>
3. Now,runyourwebstoreprojectagainandentertheURL
http://localhost:8080/webstore/.YouwillseeanHTTPStatus404error
messageinyourbrowserasshowninthefollowingscreenshot:
Anerrorpagedisplayingthenoresourcefoundmessage
4. Then,renamethejspdirectory(/src/main/webapp/WEB-INF/jsp)toviews.
5. Finally,runyourapplicationandentertheURL,
http://localhost:8080/webstore/.Youwillseethewelcomemessageagain.
Whatjusthappened?
AfterchangingtheprefixpropertyvalueoftheInternalResourceViewResolverbean,
wegotanHTTPStatus404errorwhenweenteredtheURL,
http://localhost:8080/webstore/,inthebrowser.TheHTTPStatus404errormeans
thattheservercouldnotfindthewebpagethatweaskedfor.Ifthatisthecase,thenwhich
webpagedidweaskfor?
Asamatteroffact,wedidn’taskforanywebpagefromtheserverdirectly;instead,the
dispatcherservletasksaparticularwebpagefromtheserver.Whatwealreadylearnedis
thatthedispatcherservletinvokesamethodinanyofthecontrollerbeansthatcanserve
thiswebrequest.Inourcase,thismethodisnothingbutthewelcomemethodofour
HomeControllerclass,becausethisistheonlyrequestmappingmethodthatcanmatch
therequestpathofthegivenURL,http://localhost:8080/webstore/,inits
@RequestMappingannotation.
Now,observethefollowing:
TheprefixpropertyvalueoftheInternalResourceViewResolverbeandefinitionin
DispatcherServlet-context.xml;thatis,/WEB-INF/views/
ThereturnvalueofthewelcomemethodfromtheHomeControllerclass;thatis,
welcome
Finally,thesuffixpropertyvalueoftheInternalResourceViewResolverbean,that
is,.jsp
Ifyoucombinethesethreevaluestogether,youwillgetawebpagerequestURL:/WEB-
INF/views/welcome.jsp.Now,notetheerrormessageinthepreviousscreenshot,
showingtheHTTPStatus404errorforthesamewebpageURL:/WEB-
INF/views/welcome.jspundertheapplicationname,webstore/.
So,theconclusionisthatInternalResourceViewResolverresolvestheactualviewfile
pathbyprependingtheconfiguredprefixvalueandappendingthesuffixvaluewiththe
viewname—theviewnameisthevalueusuallyreturnedbythecontrollermethod.So,the
controllermethoddoesn’treturnthepathoftheactualviewfile;itreturnsonlythelogical
viewname.ItisthejobofInternalResourceViewResolvertoformtheURLoftheactual
viewfilecorrectly.
WhoisgoingtousethisfinalformedURL?Theansweristhedispatcherservlet.After
gettingthefinalformedURLoftheviewfilefromtheviewresolver,thedispatcherservlet
willtrytogettheviewfilefromtheserver.Duringthistime,iftheformedURLisfound
tobewrong,thenyouwillgettheHTTPStatus404error.
Usually,afterinvokingthecontrollermethod,thedispatcherservletwillwaittogetthe
logicalviewnamefromit.Oncethedispatcherservletgetsthelogicalviewname,itgives
thisnametotheviewresolver(InternalResourceViewResolver)togettheURLpathof
theactualviewfile;oncetheviewresolverreturnstheURLpathtothedispatcherservlet,
therenderedviewfileisservedtotheclientbrowserasawebpagebythedispatcher
servlet.
However,whydidwegettheerrorinstep3?Sincewechangedtheprefixpropertyofthe
InternalResourceViewResolverbeaninstep2,theURLpathvaluereturnedfrom
InternalResourceViewResolverbecame/WEB-INF/views/welcome.jspinstep3,which
isaninvalidpathvalue(thereisnodirectorycalledviewsunderWEB-INF).That’swhy,we
renamedthedirectoryjsptoviewsinstep4toalignitwiththepathgeneratedby
InternalResourceViewResolversothateverythingworksfineagain.
ModelViewController
Sofar,wehaveseenlotsofconcepts,suchasthedispatcherservlet,requestmapping,
controllers,andviewresolver;itwouldbegoodtoseetheoverallpictureoftheSpring
MVCrequestflowsothatwecanunderstandeachcomponent’sresponsibilities.However,
beforethat,weneedtounderstandtheModelViewController(MVC)conceptsome
more.Everyenterprise-levelapplication’spresentationlayercanlogicallybedividedinto
thefollowingthreemajorparts:
Thepartthatmanagesthedata(Model)
Thepartthatcreatestheuserinterfaceandscreens(View)
Thepartthathandlesinteractionsbetweentheuser,userinterface,anddata
(Controller)
Thefollowingdiagramwillhelpyouunderstandtheeventflowandcommandflowwithin
anMVCpattern:
TheclassicMVCpattern
Wheneverauserinteractswiththeviewbyclickingonalinkorbutton,theviewissuesan
eventnotificationtothecontroller,andthecontrollerissuesacommandnotificationtothe
modeltoupdatethedata.Similarly,wheneverthedatainthemodelgetsupdatedor
changed,achangenotificationeventisissuedtotheviewbythemodelinresponse,and
theviewissuesastatequerycommandtothemodeltogetthelatestdatafromthemodel.
Here,themodelandviewcaninteractdirectly;thispatterniscalledtheclassicMVC
pattern.However,whatSpringMVCemploysissomethingcalledawebMVCpattern
duetothelimitationsintheHTTPprotocol.
Tip
WebapplicationsrelyontheHTTPprotocol,whichisastatelesspullprotocol.Thismeans
thatnorequestimpliesnoreply;everytime,weneedtorequesttheapplicationtoknowits
state.TheMVCdesignpatternrequiresapushprotocolfortheviewstobenotifiedbythe
model.SoinwebMVC,thecontrollertakesmoreresponsibilityforthestatechanging,
statequerying,andchangenotification.
InwebMVC,everyinteractionbetweenthemodelandviewistakenthroughthe
controlleronly.So,thecontrolleractsasabridgebetweenthemodelandview.Thereisno
directinteractionbetweenthemodelandview,asintheclassicMVCpattern.
AnoverviewoftheSpringMVCrequest
flow
ThemainentrypointforawebrequestinaSpringMVCapplicationisviathedispatcher
servlet.Thedispatcherservletactsasthefrontcontrolleranddispatchestherequeststothe
othercontroller.Thefrontcontroller’smaindutyistofindtheappropriatecontrollerto
handovertherequestforfurtherprocessing.Thefollowingdiagramshowsanoverviewof
therequestflowinaSpringMVCapplication:
TheSpringMVCrequestflow
Now,let’sreviewtheSpringMVCrequestflowinshort:
1. WhenweenteraURLinthebrowser,therequestcomestothedispatcherservlet.
Thedispatcherservletthenactsasacentralizedentrypointtothewebapplication.
2. Thedispatcherservletdeterminesasuitablecontrollerthatiscapableofhandlingthe
requestanddispatchingthisrequesttothecontroller.
3. Thecontrollermethodupdatesobjectsinthemodelandreturnsthelogicalviewname
andupdatedmodeltothedispatcherservlet.
4. Thedispatcherservletconsultswiththeviewresolvertodeterminewhichviewto
renderandpassesthemodeldatatothatview.
5. Theviewfurnishesthedynamicvaluesinthewebpageusingthemodeldata,renders
thefinalwebpage,andreturnsthiswebpagetothedispatcherservlet.
6. Attheend,thedispatcherservletreturnsthefinal,renderedpageasaresponsetothe
browser.
Thewebapplicationarchitecture
Now,weunderstandtheoverallrequestflowandresponsibilityofeachcomponentina
typicalSpringMVCapplication.However,thisisnotenoughforustobuildanonlineweb
storeapplication.Wealsoneedtoknowthebestpracticestodevelopanenterprise-level
webapplication.Oneofthebestpracticesinatypicalwebapplicationistoorganize
sourcecodeintolayers,whichwillimprovereusabilityandloosecoupling.Atypicalweb
applicationnormallyhasfourlayers:thepresentation,domain,services,andpersistence.
Sofar,whateverwehaveseen,suchasthedispatcherservlet,controllers,viewresolvers,
andsoon,isconsideredapartofthepresentationlayercomponents.Let’sunderstandthe
remaininglayersandcomponentsonebyone.
Thedomainlayer
Let’sstartwiththedomainlayer.Adomainlayertypicallyconsistsofadomainmodel.So,
whatisadomainmodel?Adomainmodelisarepresentationofthedatastoragetypes
requiredbythebusinesslogic.Itdescribesthevariousdomainobjects(entities);their
attributes,roles,andrelationships;plustheconstraintsthatgoverntheproblemdomain.
Takealookatthefollowingdomainmodeldiagramfororderprocessingtogetaquick
ideaaboutthedomainmodel:
Sampledomainmodel
Eachblockintheprecedingdiagramrepresentsabusinessentity,andthelinesrepresent
theassociationsbetweentheentities.Basedontheprecedingdomainmodeldiagram,we
canunderstandthat,inanorderprocessingdomain,acustomercanhavemanyorders,
eachordercanhavemanyorderitems,andeachorderitemrepresentsasingleproduct.
Duringcoding,thedomainmodelwillbeconvertedintocorrespondingdomainobjects
andassociationsbyadeveloper.Adomainobjectisalogicalcontainerofpuredomain
information.Sincewearegoingtobuildanonlinewebstoreapplication,inourdomain,
theprimarydomainobjectmightbeaproduct.So,let’sstartwiththedomainobjectto
representaproduct.
Timeforaction–creatingadomainobject
Sofar,inyourwebstore,youhaveshowedonlyawelcomemessage.Itisnowtimefor
youtoshowyourfirstproductonthewebpage.Dothisbycreatingadomainobject,as
follows,torepresenttheproductinformation:
1. CreateaclasscalledProductunderthecom.packt.webstore.domainpackageinthe
sourcefoldersrc/main/java.Now,addthefollowingcodeintoit:
packagecom.packt.webstore.domain;
importjava.math.BigDecimal;
publicclassProduct{
privateStringproductId;
privateStringname;
privateBigDecimalunitPrice;
privateStringdescription;
privateStringmanufacturer;
privateStringcategory;
privatelongunitsInStock;
privatelongunitsInOrder;
privatebooleandiscontinued;
privateStringcondition;
publicProduct(){
super();
}
publicProduct(StringproductId,Stringname,BigDecimalunitPrice){
this.productId=productId;
this.name=name;
this.unitPrice=unitPrice;
}
//addsettersandgettersforallthefieldshere
@Override
publicbooleanequals(Objectobj){
if(this==obj)
returntrue;
if(obj==null)
returnfalse;
if(getClass()!=obj.getClass())
returnfalse;
Productother=(Product)obj;
if(productId==null){
if(other.productId!=null)
returnfalse;
}elseif(!productId.equals(other.productId))
returnfalse;
returntrue;
}
@Override
publicinthashCode(){
finalintprime=31;
intresult=1;
result=prime*result
+((productId==null)?0:productId.hashCode());
returnresult;
}
@Override
publicStringtoString(){
return"Product[productId="+productId+",name="+name+"]";
}
}
Addsettersandgettersforallofthefieldsintheprecedingclass.Ihaveomitteditto
makethecodecompact,butitisamust,sopleasedoaddit.
2. Now,createonemoreclasscalledProductControllerunderthe
com.packt.webstore.controllerpackageinthesourcefoldersrc/main/javaand
addthefollowingcodeintoit:
packagecom.packt.webstore.controller;
importjava.math.BigDecimal;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.RequestMapping;
importcom.packt.webstore.domain.Product;
@Controller
publicclassProductController{
@RequestMapping("/products")
publicStringlist(Modelmodel){
Productiphone=newProduct("P1234","iPhone5s",new
BigDecimal(500));
iphone.setDescription("AppleiPhone5ssmartphonewith4.00-inch
640x1136displayand8-megapixelrearcamera");
iphone.setCategory("SmartPhone");
iphone.setManufacturer("Apple");
iphone.setUnitsInStock(1000);
model.addAttribute("product",iphone);
return"products";
}
}
3. Finally,addonemoreJSPviewfilecalledproducts.jspunderthedirectory
src/main/webapp/WEB-INF/views/,addthefollowingcodesnippetsintoit,andsave
it:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-8859-
1">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Products</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Products</h1>
<p>Alltheavailableproductsinourstore</p>
</div>
</div>
</section>
<sectionclass="container">
<divclass="row">
<divclass="col-sm-6col-md-3"style="padding-bottom:15px">
<divclass="thumbnail">
<divclass="caption">
<h3>${product.name}</h3>
<p>${product.description}</p>
<p>${product.unitPrice}USD</p>
<p>Available${product.unitsInStock}unitsinstock</p>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
4. Finally,runtheapplicationandentertheURL
http://localhost:8080/webstore/products.Youwillbeabletoseeawebpage
displayingtheproductinformationasshowninthefollowingscreenshot:
TheProductspagedisplayingtheproductinformation
Whatjusthappened?
Ouraimistoshowthedetailsofaproductonourwebpage;thus,inordertodothis,we
firstneedadomainobjecttoholdthedetailsoftheproduct.That’swhatwedidinstep1;
wejustcreatedaclasscalledProduct(Product.java)tostoreinformationaboutthe
product,suchasthename,description,price,andsoon.
AswehavealreadylearnedfromtheAnoverviewoftheSpringMVCrequestflowsection,
toshowanydynamicdataonawebpage,priortothis,weneedtoputthisdatainamodel;
onlythenwilltheviewbeabletoreadthisdatafromthemodelandrenderitontheweb
page.So,toputtheproductinformationinamodel,wejustcreatedonemorecontroller
calledProductController(ProductController.java)instep3.
IntheProductControllerclass,wejusthaveasinglemethodcalledlistwhose
responsibilityistocreateaproductdomainobjecttoholdtheinformationabouttheApple
iPhone5sandaddthatobjecttothemodel.Andfinally,wereturntheviewnameas
products.That’swhatweweredoingthroughthefollowinglinesinthelistmethodof
ProductController:
model.addAttribute("product",iphone);
return"products";
SinceweconfiguredInternalResourceViewResolverasourviewresolverintheweb
applicationcontextconfigurationfile,intheprocessofresolvingtheviewfileforthe
givenviewname(inourcase,theviewnameisproducts),theviewresolverwilltryto
lookforafileproducts.jspunder/WEB-INF/views/.That’swhy,wecreated
products.jspinstep4.Ifyouskipstep4,youwillgettheHTTPStatus404errorwhen
runningtheproject.
Forabettervisualexperience,products.jspcontainslotsofdivtagswithBootstrapCSS
stylesapplied(BootstrapisanopensourceCSSframework),sodon’tthinkthat
products.jspisverycomplex;asamatteroffact,itisverysimple.Youneednotbother
aboutthedivtags.Thesearepresentjustfortheappeal.Youonlyneedtoobservethe
followingfourtagscarefullyinproducts.jsptounderstanddataretrievalfromthemodel:
<h3>${product.name}</h3>
<p>${product.description}</p>
<p>${product.unitPrice}USD</p>
<p>Available${product.unitsInStock}unitsinstock</p>
Notethe${product.unitPrice}expressioncarefully;thetextproductintheexpression
isthenameofthekeythatweusedtostoretheiphoneobjectinthemodel.(Remember
thislinemodel.addAttribute("product",iphone);fromtheProductController
class.)ThetextunitPriceisnothingbutoneofthefieldsfromtheProductdomainclass
(Product.java).Similarly,weshowsomeimportantfieldsoftheproductdomainclassin
theproducts.jspfile.
Tip
WhenIsaythatpriceisthefieldname,Iamactuallymakinganassumptionherethatyou
havefollowedthestandardJavabeannamingconventionsforthegettersandsettersof
yourdomainclass.
Thisisbecause,whenSpringevaluatestheexpression${product.unitPrice},itis
actuallytryingtocallthegettermethodofthefieldtogetthevalue,soitwillexpecta
getUnitPrice()methodintheProduct.javafile.
Aftercompletingstep4,ifwerunourapplicationandentertheURL
http://localhost:8080/WebStore/products,wewillbeabletoseeawebpage
displayingtheproductinformationasshowninthepreviousscreenshot.
So,wehavecreatedadomainclasstoholdinformationaboutaproduct,createdasingle
productobjectinthecontroller,andaddedittothemodel.Finally,weshowedtheproduct
informationintheview.
Thepersistencelayer
Sincewehadasingleproduct,wejustinstantiateditinthecontrolleritselfanddisplayed
thisproductinformationonourwebpagesuccessfully.However,atypicalwebstore
containsthousandsofproducts;alltheinformationfortheseproductsisusuallystoredina
database.So,weneedtomakeourProductControllerclasssmartenoughtoloadallthe
productinformationfromthedatabaseintothemodel.However,ifwewriteallthedata
retrievallogicintheProductControllerclassitselftoretrieveproductinformationfrom
thedatabase,ourProductControllerclasswillblowdownintoabigchunkoffile.
Logicallyspeaking,dataretrievalisnotthedutyofthecontrollerbecausethecontrolleris
apresentationlayercomponent.Moreover,weneedtoorganizedataretrievalcodeina
separatelayersothatwecanreusethislogicasmuchaspossiblefromothercontrollers
andlayers.
HowdoweretrievedatafromthedatabasetheSpringMVCway?Therecomesthe
conceptofthepersistencelayer.Apersistencelayerusuallycontainsrepositoryobjectsto
accessdomainobjects.Arepositoryobjectmakesqueriestothedatasourceforthedata,
thereaftermapsthedatafromthedatasourcetoadomainobject,andfinally,persiststhe
changesinthedomainobjecttothedatasource.So,arepositoryobjectistypically
responsibleforCRUDoperations(Create,Read,Update,andDelete)ondomainobjects.
The@Repositoryannotation(org.springframework.stereotype.Repository)isan
annotationthatmarksaspecificclassasarepository.The@Repositoryannotationalso
indicatesthattheSQLexceptionsthrownfromtherepositoryobject’smethodsshouldbe
translatedintoSpring’sDataAccessExceptions.Let’screatearepositorylayerforour
application.
Timeforaction–creatingarepository
object
Performthefollowingstepstocreatearepositoryclasstoaccessyourproductdomain
objects:
1. CreateaninterfacecalledProductRepositoryunderthepackage
com.packt.webstore.domain.repositoryinthesourcefoldersrc/main/java.Add
asinglemethoddeclarationinit,asfollows:
List<Product>getAllProducts();
2. CreateaclasscalledInMemoryProductRepositoryunderthepackage
com.packt.webstore.domain.repository.implinthesourcefolder
src/main/java.Now,addthefollowingcodeintoit:
packagecom.packt.webstore.domain.repository.impl;
importjava.math.BigDecimal;
importjava.util.ArrayList;
importjava.util.List;
importorg.springframework.stereotype.Repository;
importcom.packt.webstore.domain.Product;
importcom.packt.webstore.domain.repository.ProductRepository;
@Repository
publicclassInMemoryProductRepositoryimplementsProductRepository{
privateList<Product>listOfProducts=newArrayList<Product>();
publicInMemoryProductRepository(){
Productiphone=newProduct("P1234","iPhone5s",new
BigDecimal(500));
iphone.setDescription("AppleiPhone5ssmartphonewith4.00-inch
640x1136displayand8-megapixelrearcamera");
iphone.setCategory("SmartPhone");
iphone.setManufacturer("Apple");
iphone.setUnitsInStock(1000);
Productlaptop_dell=newProduct("P1235","DellInspiron",new
BigDecimal(700));
laptop_dell.setDescription("DellInspiron14-inchLaptop(Black)
with3rdGenerationIntelCoreprocessors");
laptop_dell.setCategory("Laptop");
laptop_dell.setManufacturer("Dell");
laptop_dell.setUnitsInStock(1000);
Producttablet_Nexus=newProduct("P1236","Nexus7",new
BigDecimal(300));
tablet_Nexus.setDescription("GoogleNexus7isthelightest7inch
tabletWithaquad-coreQualcommSnapdragon™S4Proprocessor");
tablet_Nexus.setCategory("Tablet");
tablet_Nexus.setManufacturer("Google");
tablet_Nexus.setUnitsInStock(1000);
listOfProducts.add(iphone);
listOfProducts.add(laptop_dell);
listOfProducts.add(tablet_Nexus);
}
publicList<Product>getAllProducts(){
returnlistOfProducts;
}
}
3. OpenProductControllerfromthepackagecom.packt.webstore.controllerin
thesourcefoldersrc/main/java,andaddaprivatereferencetoProductRepository
withthe@Autowiredannotation
(org.springframework.beans.factory.annotation.Autowired),asfollows:
@Autowired
privateProductRepositoryproductRepository;
4. Now,alterthebodyofthelistmethod,asfollows,inProductController:
@RequestMapping("/products")
publicStringlist(Modelmodel){
model.addAttribute("products",productRepository.getAllProducts());
return"products";
}
5. Then,opentheviewfileproducts.jspfromsrc/main/webapp/WEB-INF/views/,
andremovealloftheexistingcodeandreplaceitwiththefollowingcodesnippet:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-
8859-1">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Products</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Products</h1>
<p>Alltheavailableproductsinourstore</p>
</div>
</div>
</section>
<sectionclass="container">
<divclass="row">
<c:forEachitems="${products}"var="product">
<divclass="col-sm-6col-md-3"style="padding-bottom:15px">
<divclass="thumbnail">
<divclass="caption">
<h3>${product.name}</h3>
<p>${product.description}</p>
<p>$${product.unitPrice}</p>
<p>Available${product.unitsInStock}unitsinstock</p>
</div>
</div>
</div>
</c:forEach>
</div>
</section>
</body>
</html>
6. Finally,runtheapplicationandentertheURL
http://localhost:8080/webstore/products.Youwillseeawebpagedisplaying
theproductinformationasshowninthefollowingscreenshot:
TheProductspagedisplayingalloftheproductinformationfromthein-memory
repository
Whatjusthappened?
Sincewedon’twanttowriteallofthedataretrievallogicinsideProductController
itself,wedelegatedthistasktoanotherclasscalledInMemoryProductRepository.The
InMemoryProductRepositoryclasshasasinglemethodcalledgetAllProducts(),which
returnsalistofproductdomainobjects.
Asthenameimplies,InMemoryProductRepositoryisjustadummy,in-memoryproduct
repository.Itdoesnotretrieveanyrealproductdomainobjectinformationfromany
databaseassuch;rather,itjustinstantiatesalistofproductdomainobjectsinits
constructor.So,instep2,wejustcreatedtheInMemoryProductRepositoryclass,addeda
singlemethodgetAllProducts()toit,andinstantiatedsomeproductsintheconstructor.
Youmaywonder,then,whatwedidinstep1.Instep1,wejustcreatedaninterfacecalled
ProductRepository,whichdefinestheexpectedbehaviorofaproductrepository.Asof
now,theonlyexpectedbehaviorofaProductRepositoryinterfaceistoreturnalistof
productdomainobjects(getAllProducts),andourInMemoryProductRepositoryclassis
justanimplementationofthisinterface.
Writingrealdataretrievalcodeisbeyondthescopeofthisbook,soIhavecreated
InMemoryProductRepositoryjustfordemonstrationpurposes.However,itispossibleto
replacetheInMemoryProductRepositoryclasswithanyotherrealimplementationthat
canretrieverealdatafromthedatabase.
Whydowehaveaninterfaceandanimplementationfortheproductrepository?
Rememberthatweareactuallycreatingapersistencelayerforourapplication.Whois
goingtouseourpersistencelayerrepositoryobject?Itwillpossiblybeusedbya
controllerobject(inourcase,ProductController)fromthecontrollerlayer,soitisnot
thebestpracticetoconnecttwolayers(controllerandpersistence)withadirectreference.
Instead,wecan,infuture,haveaninterfacereferenceinthecontrollersothatwecan
easilyswitchtodifferentimplementationsoftherepositorywithoutdoinganycode
changesinthecontrollerclass,ifwewant.
That’sthereasonwhywehadtheProductRepositoryreferenceinour
ProductControllerclassinstep3,andnottheInMemoryProductRepositoryclass
reference.NotethefollowinglinesinProductController:
@Autowired
privateProductRepositoryproductRepository;
Whatistheneedofthe@Autowiredannotationhere?Ifyouobservethe
ProductControllerclasscarefully,youmaywonderwhywedidn’tinstantiateanyobject
forthereference,productRepository.Nowherecouldweseeasinglelinesaying
somethinglikeproductRepository=newInMemoryProductRepository();.
SohowcometheexecutionofthelineproductRepository.getAllProducts()worksjust
finewithoutanyNullPointerExceptionerrorinthelistmethodofthe
ProductControllerclass?
model.addAttribute("products",productRepository.getAllProducts());
WhoassignstheInMemoryProductRepositoryobjecttotheproductRepository
reference?TheansweristhattheSpringFrameworkassignsthe
InMemoryProductRepositoryobjecttotheproductRepositoryreference.
RememberwelearnedthatSpringcreatesandmanagesbeans(objects)forevery
@controllerclass?Similarly,Springcreatesandmanagesbeansfor@Repositoryclasses
aswell.AssoonasSpringseesthe@Autowiredannotationontopofthe
ProductRepositoryreference,itassignstheobjectofInMemoryProductRepositoryto
thisreferencesinceSpringalreadycreatedandholdstheInMemoryProductRepository
objectinitsobjectcontainer(thewebapplicationcontext).
Ifyouremember,weconfiguredacomponentscanthroughthefollowingtagintheweb
applicationcontextconfigurationfile:
<context:component-scanbase-package="com.packt.webstore"/>
Also,welearnedearlierthatifweconfigureourwebapplicationcontextasmentioned,it
notonlydetectscontrollers(@controller),butitalsodetectsotherstereotypessuchas
repositories(@Repository)andservices(@Service).
Sinceweaddedthe@RepositoryannotationontopoftheInMemoryProductRepository
class,SpringknowsthatifanyreferenceofthetypeproductRepositoryhasan
@Autowiredannotationontopofit,thenitshouldassigntheimplementationobject
InMemoryProductRepositorytothatreference.Thisprocessofmanagingthedependency
betweenclassesiscalleddependencyinjectionorwiringintheSpringworld.So,tomark
anyclassasarepositoryobject,weneedtoannotatethatclasswiththe@Repository
annotation(org.springframework.stereotype.Repository).
Weunderstandhowthepersistencelayerworks,butaftertherepositoryobjectreturnsa
listofproducts,howdoweshowitonthewebpage?Ifyourememberhowweaddedour
firstproducttothemodel,itisverysimilartothat.Insteadofasingleobject,thistimewe
addalistofobjectstothemodelthroughthefollowinglineinthelistmethodof
ProductController:
model.addAttribute("products",productRepository.getAllProducts());
Intheprecedingcode,productRepository.getAllProducts()justreturnsalistof
productdomainobjects(List<Product>),andwedirectlyaddthislisttothemodel.
Inthecorrespondingviewfile(products.jsp),usingthe<C:forEach>tag,weloop
throughthelistanddisplaytheinformationforeachproductinsideastyleddivtag:
<c:forEachitems="${products}"var="product">
<divclass="col-sm-6col-md-3"style="padding-bottom:15px">
<divclass="thumbnail">
<divclass="caption">
<h3>${product.name}</h3>
<p>${product.description}</p>
<p>${product.unitPrice}USD</p>
<p>Available${product.unitsInStock}unitsinstock</p>
</div>
</div>
</div>
</c:forEach>
Again,notethatthetextproductsintheexpression${products}isnothingbutthekey
thatweusedwhenaddingtheproductlisttothemodelfromtheProductController
class.
TheforeachloopisaspecialJSTLloopingtagthatwillrunthroughthelistofproducts
andassigneachproducttoavariablecalledproduct(var="product")oneachiteration.
Fromtheproductvariable,wefetchinformationsuchasthename,description,and
unitPriceoftheproductanddisplayitwithinthe<h3>and<p>tags.That’showweare
finallyabletoseethelistofproductsontheproductswebpage.
Theservicelayer
Sofarsogood;wecreatedapresentationlayerthatcontainsacontroller,dispatcher
servlet,viewresolvers,andsoon.Then,wecreatedadomainlayerthatcontainsasingle
domainclass,Product.Finally,wecreatedthepersistencelayer,whichcontainsa
repositoryinterfaceandanimplementationtoaccessourProductdomainobjects.
However,wearestillmissingonemoreconceptcalledtheservicelayer.Whydoweneed
theservicelayer?Wesawhowapersistencelayerdealswithallofthelogicrelatedtodata
access(CRUD)andthepresentationlayerdealswithalloftheactivitiesrelatedtotheweb
requestandview;thedomainlayercontainsclassestoholdinformationthatisretrieved
fromdatabaserecords/thepersistencelayer.However,wherecanweputthecodefor
businessoperations?
TheservicelayerexposesbusinessoperationsthatcouldbecomposedofmultipleCRUD
operations.TheseCRUDoperationsareusuallyperformedbytherepositoryobjects.For
example,youcouldhaveabusinessoperationtoprocessacustomerorder,andinorderto
performsuchabusinessoperation,youwouldneedtoperformthefollowingoperations:
1. First,ensurethatalloftheproductsintherequestedorderareavailableinyourstore.
2. Second,haveasufficientquantityoftheseproductsinyourstore.
3. Finally,updatetheproductinventorybyreducingtheavailablecountforeach
productthatwasordered.
Serviceobjectsaregoodcandidatesforsuchbusinessoperationslogic.Theservice
operationscouldalsorepresenttheboundariesofSQLtransactions;thismeansthatallof
theelementaryCRUDoperationsperformedinsidethebusinessoperationshouldbeinside
atransaction:eitherallofthemshouldsucceedortheyshouldrollbackincaseoferror.
Timeforaction–creatingaserviceobject
Performthefollowingstepstocreateaserviceobjectthatwillperformthesimplebusiness
operationoforderprocessing:
1. OpentheinterfaceProductRepositoryfromthepackage
com.packt.webstore.domain.repositoryinthesourcefoldersrc/main/java,and
addonemoremethoddeclarationonit,asfollows:
ProductgetProductById(StringproductID);
2. OpentheimplementationclassInMemoryProductRepositoryandaddan
implantationforthepreviouslydeclaredmethod,asfollows:
publicProductgetProductById(StringproductId){
ProductproductById=null;
for(Productproduct:listOfProducts){
if(product!=null&&product.getProductId()!=null&&
product.getProductId().equals(productId)){
productById=product;
break;
}
}
if(productById==null){
thrownewIllegalArgumentException("Noproductsfoundwiththe
productid:"+productId);
}
returnproductById;
}
3. CreateaninterfacecalledOrderServiceunderthepackage
com.packt.webstore.serviceinthesourcefoldersrc/main/java.Now,adda
methoddeclarationinitasfollows:
voidprocessOrder(StringproductId,intcount);
4. CreateaclasscalledOrderServiceImplunderthepackage
com.packt.webstore.service.implinthesourcefoldersrc/main/java.Then,add
thefollowingcodeintoit:
packagecom.packt.webstore.service.impl;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importcom.packt.webstore.domain.Product;
importcom.packt.webstore.domain.repository.ProductRepository;
importcom.packt.webstore.service.OrderService;
@Service
publicclassOrderServiceImplimplementsOrderService{
@Autowired
privateProductRepositoryproductRepository;
publicvoidprocessOrder(StringproductId,longquantity){
ProductproductById=productRepository.getProductById(productId);
if(productById.getUnitsInStock()<quantity){
thrownewIllegalArgumentException("OutofStock.AvailableUnits
instock"+productById.getUnitsInStock());
}
productById.setUnitsInStock(productById.getUnitsInStock()-
quantity);
}
}
5. Now,createonemorecontrollerclasscalledOrderControllerunderthepackage
com.packt.webstore.controllerinthesourcefoldersrc/main/java,andaddthe
followingcodeintoit:
packagecom.packt.webstore.controller;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.RequestMapping;
importcom.packt.webstore.service.OrderService;
@Controller
publicclassOrderController{
@Autowired
privateOrderServiceorderService;
@RequestMapping("/order/P1234/2")
publicStringprocess(){
orderService.processOrder("P1234",2);
return"redirect:/products";
}
}
6. Finally,runtheapplicationandentertheURL
http://localhost:8080/webstore/order/P1234/2.Youwillbeabletoseeaweb
pagedisplayingtheproductinformationasshowninthefollowingscreenshot(note
thattheavailableunitsinstockforiPhone5sshowasAvailable998unitsinstock):
TheProductspagedisplayingtheproductinformationafterthestockwasupdated
viaaservicecall
Whatjusthappened?
Beforegoingthroughthesteps,Ijustwanttoremindyouofafactregardingrepository
objects:allofthedataaccess(CRUD)operationsonadomainobjectshouldbecarried
throughrepositoryobjectsonly.Factnumbertwoisthatserviceobjectsrelyonrepository
objectstocarryoutalloperationsrelatedtodataaccess.That’swhy,beforecreatingthe
actualserviceinterface/implementation,wecreatedarepositoryinterface/
implementationmethod(getProductById)insteps1and2.
ThegetProductByIdmethodfromtheInMemoryProductRepositoryclassjustreturnsa
productdomainobjectforthegivenproductID.Weneedthismethodwhenwewritethe
logicforourserviceobjectmethod(processOrder)intheOrderServiceImplclass.Ifthe
productisnotfoundforthegivenID,thenInMemoryProductRepositorythrows
IllegalArgumentException.
Now,let’sreviewsteps3and4,wherewecreatedtheactualservicedefinitionand
implementation.Instep3,wecreatedaninterfacecalledOrderServicetodefineallofthe
expectedresponsibilityofanorderservice.Wedefinedonlyoneresponsibility,asofnow,
withinthatinterface;thatis,toprocesstheorderviathemethod,processOrder.The
processOrdermethodhastwoparameters:oneisproductIdandtheotherisquantity.In
step4,weimplementedtheprocessOrdermethodwithintheOrderServiceImplclass,
wherewereducedtheamountofstockavailableforthegivenproductIdbythequantity
parameter.
Inthepreviousexercise,withintheProductControllerclass,weconnectedcontroller
andrepositorythroughtheProductRepositoryinterfacereferencetomaximizeloose
coupling.Similarly,wehavenowconnectedtheservicelayerandrepositorylayerthrough
theProductRepositoryinterfacereference,asfollows,intheOrderServiceImplclass:
@Autowired
privateProductRepositoryproductRepository;
Aswehavealreadylearned,SpringassignedtheInMemoryProductRepositoryobjectto
theproductRepositoryreferenceinthepreviouslymentionedcodebecausethe
productRepositoryreferencehasthe@Autowiredannotation,andweknowthatSpring
createsandmanagesallofthe@Serviceand@Repositoryobjects.Notethat
OrderServiceImplhasthe@Serviceannotation
(org.springframework.stereotype.Service)ontopofit.Weusedthe
productRepositoryreferencetogettheproductforthegivenIDwithintheprocessOrder
methodoftheOrderServiceImplclassasfollows:
publicvoidprocessOrder(StringproductId,longquantity){
ProductproductById=productRepository.getProductById(productId);
if(productById.getUnitsInStock()<quantity){
thrownewIllegalArgumentException("OutofStock.AvailableUnitsin
stock"+productById.getUnitsInStock());
}
productById.setUnitsInStock(productById.getUnitsInStock()-quantity);
}
Tip
Toensuretransactionalbehavior,Springprovidesthe@Transactionalannotation
(org.springframework.transaction.annotation.Transactional).Weneedtoannotate
servicemethodswiththe@Transactionalannotationtodefinetransactionattributes,and
weneedtodosomemoreconfigurationinourapplicationcontextfortransactional
behaviortotakeeffect.
However,sinceweareusingdummy,in-memoryrepositoriestomimicdataaccess,to
annotateservicemethodswiththe@Transactionalannotationismeaningless.Toknow
moreabouttransactionmanagementinSpring,referto
http://docs.spring.io/spring/docs/current/spring-framework-
reference/html/transaction.html.
Wealreadycreatedtheservicelayer,andnowitisreadytobeconsumedfromthe
presentationlayer.Itistimeforustoconnectourservicelayerwiththecontroller.Instep
5,wecreatedonemorecontroller,OrderController,witharequestmappingmethod
calledprocessinitthatisshowninthefollowingcodesnippet:
@RequestMapping("/order/P1234/2")
publicStringprocess(){
orderService.processOrder("P1234",2);
return"redirect:/products";
}
TheprocessmethodfromtheOrderControllerclassusesourorderServicereferenceto
processtheorderfortheproductID,P1234.Aftersuccessfullyexecutingtheprocess
methodofOrderController,theavailableunitsinstockshouldgetreducedby2forthe
productwiththeID,P1234.
Youwillalsonoticethatwemappedthe/order/P1234/2URLpathtotheprocess
methodusingthe@RequestMappingannotation.So,whenwefinallytrytohittheURL
http://localhost:8080/webshop/order/P1234/2,wewillbeabletoseethatthe
availableunitsinstockgetreducedbytwofortheproduct,P1234.
Haveagohero–accessingtheproductdomain
objectviaaservice
InourProductControllerclass,weonlyhavetheProductRepositoryreferenceto
accesstheProductdomainobject.However,toaccessProductRepositorydirectlyfrom
ProductControllerisnotthebestpractice;itisalwaysgoodtoaccessthepersistence
layerrepositoryviatheserviceobject.However,wehavenotcreatedanyserviceobjectto
mediatebetweenProductControllerandProductRepository.
Whydon’tyoucreateaservicelayertomediatebetweenProductControllerand
ProductRepository?Thefollowingaresomeofthethingsyoucantryout:
1. CreateaninterfacecalledProductServicewithamethoddeclaration,List
<Products>getAllProducts();.
2. Createanimplementationclass,ProductServiceImpl,fortheProductService
interface.
3. AutowiretheProductRepositoryreferenceintheProductServiceImplclassand
usethisreferencewithinthegetAllProductsmethodtogetalloftheproductsfrom
ProductRepository.
4. ReplacetheProductRepositoryreferencewiththeProductServicereferenceinthe
ProductControllerclass.Accordingly,changethelistmethodinthe
ProductControllerclass.
5. Afterfinishingthis,youwillbeabletoseethesameproductlistingsundertheURL,
http://localhost:8080/webshop/products/.
Anoverviewofthewebapplication
architecture
Sofar,wehaveseenhowtoorganizeourcodeintolayerssothatwecanavoidtight
couplingbetweenvariouscodefiles,andimprovereusabilityandtheseparationof
concerns.Wejustcreatedonedomainclass,onerepositoryclass,andoneserviceclassfor
demonstrationpurposes,butatypical,real-worldMVCapplicationmaycontainasmany
domain,repository,andserviceclassesasrequired.Eachlayerisusuallyconnected
throughinterfacesandalwayscontrolleraccessdomainobjectsfromtherepositoryviathe
serviceinterfaceonly.
Everytypical,enterprise-levelSpringMVCapplicationwilllogicallyhavefourlayers:
presentation,domains,persistence,andservices.Thedomainlayerissometimescalledthe
modellayer.Thefollowingblockdiagramwillhelpyouconceptualizethisidea:
ThelayersofaSpringMVCapplication
So,welearnedhowtocreateaservicelayerobjectandrepositorylayerobject;whatwe
sawintheservicelayerandrepositorylayerwasjustaglimpse.Springhasextensive
supporttodealwithdatabasesandtransactions;handlingtheseisaveryvasttopicand
deservesitsownbook.Intheupcomingchapters,wewillconcentratemoreonthe
presentationlayer,whichcontainsmostoftheconceptsrelatedtoSpringMVCratherthan
thoserelatedtothedatabaseandtransaction.
Haveagohero–listingallourcustomers
ItisgreatthatyouhavelistedalloftheproductsinyourwebapplicationundertheURL,
http://localhost:8080/webstore/products,butinordertobecomeasuccessfulweb
store,maintainingonlytheproductinformationisnotenough.Youneedtomaintain
informationaboutthecustomeraswellsothatyoucanattractthembygivingspecial
discountsbasedontheirpurchasehistory.
Whydon’tyoumaintaincustomerinformationinyourapplication?Executethefollowing
stepstomakesomeimprovementstoyourapplicationtomaintaincustomerinformation:
1. AddonemoredomainclasscalledtheCustomerdomainclassinthesamepackage
wheretheproductexists.
2. AddfieldssuchascustomerId,name,address,andnoOfOrdersMadetotheCustomer
class.
3. Createapersistencelayertoreturnallcustomers.
4. CreateaninterfacecalledCustomerRepositorywithamethoddeclaration,List
<Customers>getAllCustomers();.
5. CreateanimplementationInMemoryCustomerRepositoryforCustomerRepository
andinstantiateadummycustomerintheconstructorof
InMemoryCustomerRepository,asyoudidforInMemoryProductRepository.
6. Createaservicelayertogetallofthecustomersfromtherepository.
7. CreateaninterfacecalledCustomerServicewithamethoddeclaration,List
<Customers>getAllCustomers().
8. CreateanimplementationCustomerServiceImplforCustomerService.
9. CreateonemorecontrollercalledCustomerController.
10. AddarequestmappingmethodtomaptheURL,
http://localhost:8080/webstore/customers.
11. Createaviewfilecalledcustomers.jsp.
Afterfinishingthisexercise,youwillbeabletoseeallofyourcustomersundertheURL,
http://localhost:8080/webstore/customers.Thisisverysimilartothewaywelisted
allofourproductsundertheURL,http://localhost:8080/webstore/products.
Summary
Atthestartofthischapter,welearnedthedutyofadispatcherservletandhowitmapsa
requestusingthe@RequestMappingannotation.Next,wesawwhatawebapplication
contextisandhowtoconfigureitforourwebapplication.Afterthat,wegotalittle
introductionaboutviewresolversandhowInternalResourceViewResolverresolvesthe
viewfileforthegivenlogicalviewname.WealsolearnedtheconceptofMVCandthe
overallrequestflowofaSpringMVCapplication,andthenwelearnedaboutweb
applicationarchitecture.Inthewebapplicationarchitecturesection,wesawhowtocreate
andorganizecodeunderthevariouslayersofaSpringMVCapplication,suchasthe
domainlayer,persistencelayer,andservicelayer.Atthesametime,wesawhowto
retrieveproductdomainobjectsfromtherepositoryandpresentthemonthewebpage
usingthecontroller.Wealsolearnedwhereaserviceobjectfitsin.Finally,wesawan
overviewofthewebapplicationarchitecture.
IhopeyougotagoodoverviewofSpringMVCandthevariouscomponentsinvolvedin
developingaSpringMVCapplication.Inthenextchapter,wearespecificallygoingto
learnmoreaboutcontrollersandrelatedconcepts.Meetyouinthenextchapter!
Chapter3.ControlYourStorewith
Controllers
InChapter2,SpringMVCArchitecture–ArchitectingYourWebStore,welearnedthe
overallarchitectureofaSpringMVCapplication.Wedidn’tgointoanyoftheconceptsin
detail;ourtotalaimwastounderstandtheoverallflow.Inthischapter,wearegoingto
haveanin-depthlookatthecontrollersinSpringMVCastheyhaveanimportantrole.
Thischapterwillcoverthefollowingconcepts:
Definingacontroller
URItemplatepatterns
Matrixvariables
Requestparameters
Definingacontroller
Controllersarepresentationlayercomponentsthatareresponsibleforrespondingtouser
actions.TheseactionscouldbeenteringaparticularURLonthebrowser,clickingona
link,submittingaformonawebpage,andsoon.AnyregularJavaclasscanbe
transformedintoacontrollerbysimplybeingannotatedwiththe@Controllerannotation
(org.springframework.stereotype.Controller).
Andwehadalreadylearnedthatthe@ControllerannotationsupportsSpring’s
autodetectionmechanismforauto-registeringthebeandefinitioninthewebapplication
context.Toenablesuchauto-registering,wemustaddthe<context:component-scan>tag
inthewebapplicationcontextconfigurationfile;wehaveseenhowtodothatintheThe
webapplicationcontextconfigurationsectionofChapter2,SpringMVCArchitecture–
ArchitectingYourWebStore.
Acontrollerclassismadeupofrequest-mappedmethods,alsocalledhandlermethods.
Handlermethodsareannotatedwiththe@RequestMappingannotation
(org.springframework.web.bind.annotation.RequestMapping).The@RequestMapping
annotationisusedtomapURLstoparticularhandlermethods.InChapter2,SpringMVC
Architecture–ArchitectingYourWebStore,wesawabriefintroductiononthe
@RequestMappingannotationandlearnedhowtoapplythe@RequestMappingannotation
onthehandlermethodlevel.However,inSpringMVC,wecanevenspecifythe
@RequestMappingannotationatthecontrollerclasslevel.Inthatcase,SpringMVCwill
considerthecontrollerclasslevel@RequestMappingannotationvaluebeforemappingthe
URLtothehandlermethods.Thisfeatureiscalledrelativerequestmapping.
Note
Thetermsrequestmappedmethod,mappedmethod,handlermethod,andcontroller
methodallmeanthesamething;thesetermsareusedtospecifythecontrollermethod
withan@RequestMappingannotation.Theyhavebeenusedinterchangeablyinthisbook.
Timeforaction–addingclass-level
requestmapping
Let’sadda@RequestMappingannotationonourProductControllerclasstodemonstrate
therelativerequestmappingfeature.However,beforethat,wejustwanttoensurethatyou
havealreadyreplacedtheProductRepositoryreferencewiththeProductService
referenceintheProductControllerclassaspartofthepreviouschapter’sTimeforaction
–creatingaserviceobjectsection.Becausecontactingthepersistencelayerdirectlyfrom
thepresentationlayerisnotabestpractice,allaccesstothepersistencelayershouldgo
throughtheservicelayer.Performthefollowingsteps(thosewhohavecompletedthis
exercisecandirectlystartfromstep5;otherspleasecontinuefromstep1):
1. CreateaninterfacecalledProductServiceunderthecom.packt.webstore.service
packageinsrc/main/javaandaddtwomethoddeclarationsinitasfollows:
List<Product>getAllProducts();
ProductgetProductById(StringproductID);
2. CreateaclasscalledProductServiceImplunderthe
com.packt.webstore.service.implpackageinsrc/main/javaandaddthe
followingcodeintoit:
packagecom.packt.webstore.service.impl;
importjava.util.List;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importcom.packt.webstore.domain.Product;
importcom.packt.webstore.domain.repository.ProductRepository;
importcom.packt.webstore.service.ProductService;
@Service
publicclassProductServiceImplimplementsProductService{
@Autowired
privateProductRepositoryproductRepository;
publicList<Product>getAllProducts(){
returnproductRepository.getAllProducts();
}
publicProductgetProductById(StringproductID){
returnproductRepository.getProductById(productID);
}
}
3. OpenProductController,removetheexistingProductRepositoryreference,and
addtheProductServicereferenceasfollows:
@Autowired
privateProductServiceproductService;
4. Now,alterthebodyofthelistmethodintheProductControllerclassasfollows
(notethatthistimeweusedtheproductServicereferencetogetalloftheproducts):
@RequestMapping("/products")
publicStringlist(Modelmodel){
model.addAttribute("products",productService.getAllProducts());
return"products";
}
5. IntheProductControllerclass,addthefollowingannotationontopoftheclass:
@RequestMapping("/products")
6. Fromthelistmethod’s@RequestMappingannotation,removethevalueattribute
completely;sonow,thelistmethodwillhaveaplain@RequestMappingannotation
withoutanyattributesasfollows:
@RequestMapping
publicStringlist(Modelmodel){
7. Now,addonemorehandlermethodintheProductControllerclassasfollows:
@RequestMapping("/all")
publicStringallProducts(Modelmodel){
model.addAttribute("products",productService.getAllProducts());
return"products";
}
8. Finally,runtheapplicationagainandentertheURL
http://localhost:8080/webstore/products/allinthebrowsertoviewallofthe
products.
Whatjusthappened?
Whatwehavedemonstratedhereisasimpleconceptcalledrelativerequestmapping.We
didthefollowingthreethingsintheProductControllerclass:
Weaddedan@RequestMappingannotationattheclasslevelwithavalueattribute
definedas"/products"instep5
Weremovedthevalueattributefromthe@RequestMappingannotationofthelist
methodinstep6
Finally,weaddedonemorehandlermethodcalledallProducts,whichalsoputsthe
samelistofproductsonthemodelasthelistmethod,butunderadifferentURL
mapping—@RequestMapping("/all")
Inallourpreviousexamples,weannotatedthe@RequestMappingannotationsonlyatthe
controllermethodlevel,butSpringMVCalsoallowsustospecifyrequestmappingatthe
controllerclasslevel.Inthiscase,SpringMVCmapsaspecificURLpathatthemethod
levelthatisrelativetotheclasslevel@RequestMappingURLvalue.
Instep5,wejustaddedthe@RequestMappingannotationattheProductControllerclass
levelwiththeURLmappingvalue/products.Andinstep7,weaddedanewhandler
methodcalledallProductswithaURLmappingvalue/all.So,thefinalrequestpathfor
theallProductsmethodisformedbycombiningtheclassandmethodrequestmapping
values,whichis/products/all.So,ifwedefinedanyclasslevelrequestmapping,
SpringMVCwouldconsiderthatclasslevelrequestpathbeforemappingtherequestto
themethod.
Note
Steps1to4justteachyouhowtocreateandconnectaservicelayerobjectwiththe
ProductControllerclass.Asofnow,theProductServiceImplclassdoesnothaveany
distinguishablebusinesslogicinit;rather,itsimplydelegatesthecalltothepersistence
layer’srepositoryobject(ProductRepository)toaccesstheProductdomainobject.Soas
ofnow,thereisnorealmeaningtohaveaservicelayerforProductRepository;however,
infuture,ifwedecidetoreplacetheInMemoryProductRepositoryobjectwithareal
databasebackedrepositoryobject,wewilldefinitelyneedthisservicelayertowritecode
tohandletransaction-relatedtasks.So,justtomaintaintheindustry’sbestpractices,Ihave
retainedtheservicelayersinmostoftheexamplesinthisbook.
Instep6,wesimplydidn’tspecifyanyrequestpathvalueinthe@RequestMapping
annotationofthelistmethod.Bydoingso,wemadethelistmethodthedefaultrequest
mappingmethodfortheProductControllerclass.So,wheneverarequestURLendsup
withthecontrollerclasslevelrequestpathvaluewithoutanyfurtherrelativepath,Spring
MVCinvokesthismethodasaresponsetotherequest.
So,finallyinourcase,theURLhttp://localhost:8080/webstore/productswillbe
mappedtothelistmethodandhttp://localhost:8080/webstore/products/allwill
bemappedtotheallProductsmethod.
Note
Ifyouspecifymorethanonedefaultmappingmethodinsideacontroller,youwillget
IllegalStateExceptionwiththemessageAmbiguousmappingfound.So,acontroller
canhaveonlyonedefaultrequestmappingmethodatmost.
Popquiz–class-levelrequestmapping
Q1.Inawebapplicationcalledlibrarythathasthefollowingrequestmappingata
controllerclasslevelandinthemethodlevel,whichistheappropriaterequestURLto
maptherequesttothebooksmethod?
@RequestMapping("/books")
publicclassBookController{
...
@RequestMapping(value="/list")
publicStringbooks(Modelmodel){
...
1. http://localhost:8080/library/books/list
2. http://localhost:8080/library/list
3. http://localhost:8080/library/list/books
4. http://localhost:8080/library/
Q2.IfwehaveanotherhandlermethodcalledbookDetailsunderBookControlleras
follows,whatwilltheURLthatmapstothatmethodbe?
@RequestMapping()
publicStringbookDetails(Modelmodel){
...
1. http://localhost:8080/library/books/details
2. http://localhost:8080/library/books
3. http://localhost:8080/library/details
4. http://localhost:8080/library/
TheroleofacontrollerinSpringMVC
InSpringMVC,controllermethodsarethefinaldestinationpointthatawebrequestcan
reach.Afterbeinginvoked,thecontrollermethodstartstoprocessthewebrequestby
interactingwiththeservicelayertocompletetheworkthatneedstobedone.Usually,the
servicelayerexecutessomebusinessoperationsondomainobjectsandcallsthe
persistencelayertoupdatethedomainobjects.Aftertheprocessinghasbeencompleted
bytheservicelayerobject,thecontrollerisresponsibleforupdatingandbuildingupthe
modelobjectandchoosesaviewfortheusertoseenextasaresponse.
RememberthatSpringMVCalwayskeepsthecontrollersunawareofanyview
technologyused.That’swhythecontrollerreturnsonlyalogicalviewname;later,
DispatcherServletconsultswithViewResolvertofindouttheexactviewtobe
rendered.Accordingtothecontroller,ModelisacollectionofarbitraryobjectsandViewis
specifiedwithalogicalname.
Inallourpreviousexercises,thecontrollersusedtoreturnthelogicalviewnameand
updatethemodelviathemodelparameteravailableinthecontrollermethod.Thereis
another,seldomusedwayofupdatingthemodelandreturningtheviewnamefromthe
controllerwiththehelpoftheModelAndViewobject
(org.springframework.web.servlet.ModelAndView).Lookatthefollowingcode
snippet,forexample:
@RequestMapping("/all")
publicModelAndViewallProducts(){
ModelAndViewmodelAndView=newModelAndView();
modelAndView.addObject("products",productService.getAllProducts());
modelAndView.setViewName("products");
returnmodelAndView;
}
Theprecedingcodesnippetjustshowshowwecanencapsulatethemodelandviewusing
theModelAndViewobject.
Handlermapping
WehavelearnedthatDispatcherServletistheonethatdispatchestherequesttothe
handlermethodsbasedontherequestmapping;however,inordertointerpretthe
mappingsdefinedintherequestmapping,DispatcherServletneedsaHandlerMapping
implementation(org.springframework.web.servlet.HandlerMapping).The
DispatcherServletconsultswithoneormoreHandlerMappingimplementationstofind
outwhichcontroller(handler)canhandletherequest.So,HandlerMapping
determineswhichcontrollertocall.
TheHandlerMappinginterfaceprovidestheabstractionformappingrequeststohandlers.
TheHandlerMappingimplementationsarecapableofinspectingtherequestandcoming
upwithanappropriatecontroller.SpringMVCprovidesmanyHandlerMapping
implementations,andtheoneweareusingtodetectandinterpretmappingsfromthe
@RequestMappingannotationistheRequestMappingHandlerMappingclass
(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
TostartusingRequestMappingHandlerMapping,wehavetoaddthe<mvc:annotation-
driven>elementinourwebapplicationcontextconfigurationfilesothatSpringMVC
cancreateandregisterabeanforRequestMappingHandlerMappinginourwebapplication
context.Wealreadyconfigured<mvc:annotation-driven>inChapter2,SpringMVC
Architecture–ArchitectingYourWebStore,intheThewebapplicationcontext
configurationsection.
UsingURItemplatepatterns
Inthepreviouschapters,wesawhowtomapaparticularURLtoacontrollermethod;for
example,iftheURLenteredwashttp://localhost:8080/webstore/products,we
mappedthatrequesttothelistmethodofProductControllerandlistedalltheproduct
informationonthewebpage.
Whatifwewanttolistonlyasubsetoftheproductsbasedoncategory,forinstance,we
wanttodisplayonlytheproductsthatfallunderthecategoryoflaptopsiftheuserentered
theURLhttp://localhost:8080/webstore/products/laptop?Similarly,whatifthe
URLishttp://localhost:8080/webstore/products/tabletandwewouldliketoshow
onlytabletsonthewebpage?
Onewaytodothisistohaveaseparaterequestmappingmethodinthecontrollerfor
everyuniquecategory.However,itwon’tscaleifwehavehundredsofcategories;inthat
case,we’llhavetowriteahundredrequestmappingmethodsinthecontroller.Sohowdo
wedothisinanelegantway?
WeusetheSpringMVCURItemplatepatternfeature.IfyounotethefollowingURLs,
theonlypartthatchangesintheURListhecategorytype(laptopandtablet);otherthan
that,everythingremainsthesame:
http://localhost:8080/webstore/products/laptop
http://localhost:8080/webstore/products/tablet
So,wecandefineacommonURItemplateforthepreviouslymentionedURLs,which
mightlooklikehttp://localhost:8080/webstore/products/{category}.SpringMVC
canleveragethisfactandmakethattemplateportion({category})oftheURLavariable,
calledapathvariableintheSpringworld.
Timeforaction–showingproductsbased
oncategory
Let’saddacategoryviewtotheproductspageusingthepathvariable:
1. OpentheProductRepositoryinterfaceandaddonemoremethoddeclarationonits
getProductsByCategorymethod:
List<Product>getProductsByCategory(Stringcategory);
2. OpentheimplementationclassInMemoryProductRepositoryandaddan
implementationforthepreviouslydeclaredmethodasfollows:
publicList<Product>getProductsByCategory(Stringcategory){
List<Product>productsByCategory=newArrayList<Product>();
for(Productproduct:listOfProducts){
if(category.equalsIgnoreCase(product.getCategory())){
productsByCategory.add(product);
}
}
returnproductsByCategory;
}
3. Similarly,opentheProductServiceinterfaceandaddonemoremethoddeclaration
onitsgetProductsByCategorymethod:
List<Product>getProductsByCategory(Stringcategory);
4. OpentheserviceimplementationclassProductServiceImplandaddan
implementationasfollows:
publicList<Product>getProductsByCategory(Stringcategory){
returnproductRepository.getProductsByCategory(category);
}
5. OpentheProductControllerclassandaddonemorerequestmappingmethodas
follows:
@RequestMapping("/{category}")
publicStringgetProductsByCategory(Modelmodel,
@PathVariable("category")StringproductCategory){
model.addAttribute("products",
productService.getProductsByCategory(productCategory));
return"products";
}
6. RuntheapplicationandentertheURL
http://localhost:8080/webstore/products/tablet;youwillseesomethingas
specifiedinthefollowingscreenshot:
Showingproductsbycategorywiththehelpofpathvariables
Whatjusthappened?
Step5isthemostimportantinthewholesequencefromthepreviouslist,becauseallthe
stepspriortostep5aretheprerequisitesforstep5.Whatwearedoinginstep5isnothing
butaddingalistofproductobjectstothemodellikewenormallywould:
model.addAttribute("products",productService.getProductsByCategory(ProductC
ategory));
OnethingweneedtonotehereisthegetProductsByCategorymethodfrom
productService;weneedthismethodtogetthelistofproductsforthegivencategory,
andproductServiceassuchcannotgivethelistofproductsforthegivencategory.Itwill
asktherepository.That’swhy,instep4,weusedtheproductRepositoryreferencetoget
thelistofproductsbycategorywithintheProductServiceImplclass.Notethefollowing
codesnippetfromProductServiceImpl:
returnproductRepository.getProductsByCategory(category);
Anotherimportantthingtobenotedinthecodesnippetfromstep5isthe
@RequestMappingannotation’srequestpathvalueasfollows:
@RequestMapping("/{category}")
Byenclosingaportionofarequestpathwithincurlybraces,weindicatetotheSpring
MVCthatitisaURItemplatevariable.AccordingtoSpringMVCdocumentation,aURI
templateisaURI-likestringthatcontainsoneormorevariablenames.Whenyou
substitutevaluesforthesevariables,thetemplatebecomesaURI.
Forexample,theURItemplate
http://localhost:8080/webstore/products/{category}containsthevariable
category.Assigningthevaluelaptoptothevariableyields
http://localhost:8080/webstore/products/laptop.InSpringMVC,wecanusethe
@PathVariableannotation
(org.springframework.web.bind.annotation.PathVariable)toreadaURItemplate
variable.
Sincewehavethe@RequestMapping("/products")annotationattheProductController
level,theactualrequestpathofthegetProductsByCategorymethodwillbe
/products/{category}.Soatruntime,ifwegiveawebrequestURLas
http://localhost:8080/webstore/products/laptop,thenthecategorypathvariable
willhavethevaluelaptop.Similarly,forthewebrequest
http://localhost:8080/webstore/products/tablet,thecategorypathvariablewill
havethevaluetablet.
HowdoweretrievethevaluestoredintheURItemplatepathvariablecategory?Aswe
alreadymentioned,the@PathVariableannotationwillhelpusreadthatvariable.Allwe
needtodoissimplyannotatethegetProductsByCategorymethodparameterwiththe
@PathVariableannotationasfollows:
publicStringgetProductsByCategory(@PathVariable("category")String
productCategory,Modelmodel){
So,SpringMVCwillreadwhatevervalueispresentinthecategoryURItemplate
variableandassignittothemethodparameterproductCategory.So,wehavethe
categoryvalueinavariable,andwejustpassittoproductServicetogetthelistof
productsinthatcategory.Oncewegetthatlistofproducts,wesimplyaddittothemodel
andreturnthesameviewnamethatwehaveusedtolistalltheproducts.
Thevalueattributeinthe@PathVariableannotationshouldbethesameasthevariable
nameinthepathexpressionofthe@RequestMappingannotation.Forexample,ifthepath
expressionis"/products/{identity}",thentoretrievethepathvariableidentity,you
havetoformthe@PathVariableannotationas@PathVariable("identity").
Note
Ifthe@PathVariableannotationhasbeenspecifiedwithoutanyvalueattribute,itwilltry
toretrieveapathvariablewiththesamenameasthatofthevariablethatithasbeen
annotatedwith.
Forexample,ifyouspecifysimply@PathVariableStringproductId,thenSpringwill
assumethatitshouldlookforaURItemplatevariable"{productId}"intheURL.A
requestmappingmethodcanhaveanynumberof@PathVariableannotations.
Finally,instep6,whenweentertheURL
http://localhost:8080/webstore/products/tablet,weseeinformationabout
Google’sNexus7,whichisatablet.Similarly,ifweentertheURL
http://localhost:8080/webstore/products/laptop,weseeinformationaboutDell’s
Inspironlaptop.
Popquiz–requestpathvariable
Q1.InawebapplicationcalledWebStorethathasthefollowingrequestmappingata
controllerclasslevelandinthemethodlevel,whichistheappropriaterequestURLthat
canbeused?
@RequestMapping("/items")
publicclassProductController{
...
@RequestMapping(value="/type/{type}",method=RequestMethod.GET)
publicStringproductDetails(@PathVariable("type")StringproductType,
Modelmodel){
1. http://localhost:8080/WebStore/items/electronics
2. http://localhost:8080/WebStore/items/type/electronics
3. http://localhost:8080/WebStore/items/productType/electronics
4. http://localhost:8080/WebStore/type/electronics
Q2.Forthefollowingrequestmappingannotation,whicharethecorrectmethod
signaturestoretrievethepathvariables?
@RequestMapping(value="/manufacturer/{
manufacturerId}/product/{productId}")
1. publicStringproductByManufacturer(@PathVariableString
manufacturerId,@PathVariableStringproductId,Modelmodel)
2. publicStringproductByManufacturer(@PathVariableStringmanufacturer,
@PathVariableStringproduct,Modelmodel)
3. publicStringproductByManufacturer(@PathVariable("manufacturer")
StringmanufacturerId,@PathVariable("product")StringproductId,Model
model)
4. publicStringproductByManufacturer(@PathVariable("manufacturerId")
Stringmanufacturer,@PathVariable("productId")Stringproduct,Model
model)
Usingmatrixvariables
Inthelastsection,wesawtheURItemplatefacilitytobindvariablesintheURLrequest
path.However,thereisonemorewaytobindvariablesintherequestURLinaname-
valuepairstyle;theseboundvariablesarereferredtoasmatrixvariableswithinSpring
MVC.LookatthefollowingURL:
http://localhost:8080/webstore/products/filter/price;low=500;high=1000
InthisURL,theactualrequestpathisjustupto
http://localhost:8080/webstore/products/filter/price,afterwhichwehave
somethinglikelow=500;high=1000;here,lowandhigharejustmatrixvariables.
However,whatmakesmatrixvariablessospecialistheabilitytoassignmultiplevalues
forasinglevariable;thismeansthatwecanassignalistofvaluestoaURIvariable.Take
alookatthefollowingURL:
http://localhost:8080/webstore/products/filter/ByCriteria;brand=google,dell;category=tablet,laptop
InthegivenURL,wehavetwovariables,namely,brandandcategory;bothhave
multiplevalues,brand=google,dellandcategory=tablet,laptop.Howdoweread
thesevariablesfromtheURLduringrequestmapping?Weusethespecialbinding
annotation@MatrixVariable
(org.springframework.web.bind.annotation.MatrixVariable).Onecoolthingabout
the@MatrixVariableannotationisthatitallowsustocollectthematrixvariablesina
mapofcollections(Map<String,List<String>>),whichwillbemorehelpfulwhenwe
aredealingwithcomplexwebrequests.
Timeforaction–showingtheproducts
basedonfilter
Considerasituationwherewewanttofiltertheproductlistbasedonthebrandand
categoryvariables.Forexample,youwanttolistalltheproductsthatfallunderthe
categorylaptopandtabletsandfromthemanufacturersgoogleanddell.Withthehelp
ofthematrixvariables,wecanformaURLtobindthebrandandcategoryvariables’
valuesintotheURLasfollows:
http://localhost:8080/webstore/products/filter/ByCriteria;brand=google,dell;category=tablet,laptop
Let’smapthisURLtoahandlermethodwiththehelpofthe@MatrixVariableannotation:
1. OpentheProductRepositoryinterfaceandaddonemoremethoddeclaration,
getProductsByFilter,onit:
Set<Product>getProductsByFilter(Map<String,List<String>>
filterParams);
2. Opentheimplementationclass,InMemoryProductRepository,andaddthefollowing
methodimplementationforgetProductsByFilter:
publicSet<Product>getProductsByFilter(Map<String,List<String>>
filterParams){
Set<Product>productsByBrand=newHashSet<Product>();
Set<Product>productsByCategory=newHashSet<Product>();
Set<String>criterias=filterParams.keySet();
if(criterias.contains("brand")){
for(StringbrandName:filterParams.get("brand")){
for(Productproduct:listOfProducts){
if(brandName.equalsIgnoreCase(product.getManufacturer())){
productsByBrand.add(product);
}
}
}
}
if(criterias.contains("category")){
for(Stringcategory:filterParams.get("category")){
productsByCategory.addAll(this.getProductsByCategory(category));
}
}
productsByCategory.retainAll(productsByBrand);
returnproductsByCategory;
}
3. OpentheinterfaceProductServiceandaddonemoremethoddeclarationonit
calledgetProductsByFilterasfollows:
Set<Product>getProductsByFilter(Map<String,List<String>>
filterParams);
4. Opentheserviceimplementationclass,ProductServiceImpl,andaddthefollowing
methodimplementationforgetProductsByFilter:
publicSet<Product>getProductsByFilter(Map<String,List<String>>
filterParams){
returnproductRepository.getProductsByFilter(filterParams);
}
5. OpenProductControllerandaddonemorerequestmappingmethodasfollows:
@RequestMapping("/filter/{ByCriteria}")
publicStringgetProductsByFilter(@MatrixVariable(pathVar=
"ByCriteria")Map<String,List<String>>filterParams,Modelmodel){
model.addAttribute("products",
productService.getProductsByFilter(filterParams));
return"products";
}
6. Openthewebapplicationcontextconfigurationfile(DispatcherServlet-
context.xml)fromsrc/main/webapp/WEB-INF/spring/webcontext/andenable
matrixvariablesupportbysettingenable-matrix-variablestotrueinthe
<mvc:annotation-driven>tagasfollows:
<mvc:annotation-drivenenable-matrix-variables="true"/>
7. Finally,runtheapplicationandentertheURL
http://localhost:8080/webstore/products/filter/ByCriteria;brand=google,dell;category=tablet,laptop
youwillseetheproductlistingasshowninthefollowingscreenshot:
Usageofmatrixvariablesshowingproductlistfilteredbycriteria
Whatjusthappened?
OuraimistoretrievethematrixvariablevaluesfromtheURLanddosomethinguseful;
inourcase,theURLwearetryingtomapis
http://localhost:8080/webstore/products/filter/ByCriteria;brand=google,dell;category=tablet,laptop
wherewewanttoextractthematrixvariablesbrandandcategory.Thebrandand
categoryvariableshavethevaluesgoogle,dellandtablet,laptop,respectively.Inthe
previousURL,therequestpathisupto
http://localhost:8080/webstore/products/filter/ByCriteriaonly.That’swhy,in
step5,weannotatedourgetProductsByFilterrequestmappingmethodasfollows:
@RequestMapping("/filter/{ByCriteria}")
However,youmaywonderwhywehaveaURItemplate(/{ByCriteria})inthe
@RequestMappingannotation,whichislikemappingtoapathvariable.Itisbecauseifour
requestURLcontainsthematrixvariable,thenwewillhavetoformthe@RequestMapping
annotationwithaURItemplatetoidentifythestartingofmatrixvariable’ssegments
withinURL.That’swhywedefinedByCriteriaasaURItemplateintherequestmapping
annotation(@RequestMapping("/filter/{ByCriteria}")).
Note
AURLcanhavemultiplematrixvariables;eachmatrixvariablewillbeseparatedwitha;
(semicolon).Toassignmultiplevaluestoasinglevariable,eachvaluemustbeseparated
bya“,”(comma),orwecanrepeatthevariablename.SeethefollowingURL,whichisa
repeatedvariableversionofthesameURLthatweusedinourexample:
http://localhost:8080/webstore/products/filter/ByCategory;brand=google;brand=dell;category=tablet;category=laptop
NotethatwerepeatedthevariablesbrandandcategorytwiceintheURL.
WemappedthewebrequesttothegetProductsByFiltermethod,buthowdoweretrieve
thevaluefromthematrixvariables?Theansweristhe@MatrixVariableannotation.Itis
quitesimilartothe@PathVariableannotation;ifyounoticethegetProductsByFilter
methodsignatureinstep5,weannotatedthemethodparameterfilterParamswiththe
@MatrixVariableannotationasfollows:
publicStringgetProductsByFilter(@MatrixVariable(pathVar="ByCriteria")
Map<String,List<String>>filterParams,Modelmodel)
So,SpringMVCwillreadallthematrixvariablesfoundintheURLafterthe
{ByCriteria}URItemplateandplacethosematrixvariablesintothemapofthemethod
parameterfilterParams.ThefilterParamsmapwillhaveeachmatrixvariablenameas
keyandthecorrespondinglistwillcontainthemultiplevaluesassignedforthematrix
variable.ThepathVarattributefromthe@MatrixVariableannotationisusedtoidentify
thematrixvariablesegmentintheURL;that’swhyithasthevalueByCriteria,whichis
nothingbuttheURItemplatevaluethatweusedinourrequestmappingURL.
AURLcanhavemultiplematrixvariablesegments.TakealookatthefollowingURL:
http://localhost:8080/webstore/products/filter/ByCriteria;brand=google,dell;category=tablet,laptop/BySpecification;dimention=10,20,15;color=red,green,blue
Itcontainstwomatrixvariablesegments,eachidentifiedbytheprefixesByCriteriaand
BySpecification,respectively.Soinordertocaptureeachmatrixvariablesegmentintoa
map,wehavetoformthecontrollermethodsignatureasfollows:
@RequestMapping("/filter/{ByCriteria}/{BySpecification}")
publicStringfilter(@MatrixVariable(pathVar="ByCriteria")
Map<String,List<String>>criteriaFilter,@MatrixVariable(pathVar="
BySpecification")Map<String,List<String>>specFilter,Modelmodel){
WegotthevalueofthematrixvariablesintothemethodparameterfilterParams,but
whatdidwedowiththatfilterParamsmap?Wesimplypasseditasaparametertothe
servicemethodtoretrievetheproductsbasedoncriteriaasfollows:
productService.getProductsByFilter(filterParams)
Again,theservicepassesthatmaptotherepositorytogetthelistofproductsbasedonthe
criteria.Oncewegetthelist,wesimplyaddthatlisttothemodelandreturnthesame
viewnamethatwasusedtolisttheproducts.
ToenabletheuseofmatrixvariablesinSpringMVC,wesettheenable-matrix-
variablesattributeofthe<mvc:annotation-driven>tagtotrue;wedidthisinstep6.
Finally,wewereabletoviewtheproductsbasedonthespecifiedcriteriainstep7onour
productlistingpage.
Understandingrequestparameters
MatrixvariablesandpathvariablesareagreatwayofbindingvariablesintheURL
requestpath.However,thereisonemorewaytobindvariablesintheHTTPrequest,not
onlyasapartoftheURLbutalsointhebodyoftheHTTPwebrequest,whicharetheso-
calledHTTPparameters.YoumighthaveheardabouttheGETorPOSTparameters.GET
parametershavebeenusedforyearsasastandardwaytobindvariablesintheURL,and
POSTparametersareusedtobindvariablesinthebodyoftheHTTPrequest.Wewill
learnaboutPOSTparametersinthenextchapterduringformsubmission.
Okay,nowlet’sseehowtoreadGETrequestparametersusingtheSpringMVCstyle.To
demonstratetheuseoftherequestparameter,let’saddaproductdetailspagetoour
application.
Timeforaction–addingtheproduct
detailspage
Sofarinourproductlistingpage,wehaveonlyshownproductinformationsuchasthe
product’sname,description,price,andavailableunitsinstock.However,wehaven’t
showninformationsuchasthemanufacturer’sname,category,productID,andsoon.
Let’screateaproductdetailspagedisplayingthisinformationasfollows:
1. OpentheProductControllerclassandaddonemorerequestmappingmethodas
follows:
@RequestMapping("/product")
publicStringgetProductById(@RequestParam("id")StringproductId,
Modelmodel){
model.addAttribute("product",
productService.getProductById(productId));
return"product";
}
2. AddonemoreJSPviewfilecalledproduct.jspunderthedirectory
src/main/webapp/WEB-INF/views/,andaddthefollowingcodesnippetintoitand
saveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-8859-
1">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Products</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Products</h1>
</div>
</div>
</section>
<sectionclass="container">
<divclass="row">
<divclass="col-md-5">
<h3>${product.name}</h3>
<p>${product.description}</p>
<p>
<strong>ItemCode:</strong><spanclass="labellabel-
warning">${product.productId}</span>
</p>
<p>
<strong>manufacturer</strong>:${product.manufacturer}
</p>
<p>
<strong>category</strong>:${product.category}
</p>
<p>
<strong>Availbleunitsinstock</strong>:
${product.unitsInStock}
</p>
<h4>${product.unitPrice}USD</h4>
<p>
<ahref="#"class="btnbtn-warningbtn-large"><span
class="glyphicon-shopping-cartglyphicon"></span>OrderNow
</a>
</p>
</div>
</div>
</section>
</body>
</html>
3. Now,runtheapplicationandentertheURL
http://localhost:8080/webstore/products/product?id=P1234;youwillseethe
productdetailspageasshowninthefollowingscreenshot:
Usageofrequestparametershowingproductdetailspage
Whatjusthappened?
Whatwedidinstep1isverysimilartowhatwedidinthegetProductsByCategory
methodofProductController.Wejustaddedaproductobjecttothemodelthatis
returnedbytheserviceobjectasfollows:
model.addAttribute("product",productService.getProductById(productId));
However,theimportantquestionhereis,whoisgivingthevalueoftheparameter
productId?Theanswerissimple,asyouguessed;sinceweannotatedtheparameter
productIdwiththe@RequestParam("id")annotation
(org.springframework.web.bind.annotation.RequestParam),SpringMVCwilltryto
readaGETrequestparameterwiththenameidfromtheURLandassignittothe
getProductByIdmethodparameter,productId.
The@RequestParamannotationalsofollowsthesameconventionforotherbinding
annotations;thatis,ifthenameoftheGETrequestparameterandthenameofthevariable
itisannotatedwitharethesame,thentherewillbenoneedtospecifythevalueattribute
inthe@RequestParamannotation.
Finally,instep6,weaddedonemoreviewfilecalledproduct.jspbecausewewanteda
detailedviewoftheproductsothatwecoulddisplayalltheinformationabouttheproduct.
Nothingfancyinthisproduct.jsp;asusual,wegetthevaluefromthemodelandshowit
withinHTMLtagsusingtheusualJSTLexpressionlanguagenotation${}asfollows:
<h3>${product.name}</h3>
<p>${product.description}</p>
......
WesawhowtoretrieveaGETrequestparameterfromtheURL,buthowdowepassmore
thanoneGETrequestparameterintheURL?Theansweristhatweneedtodelimiteach
parametervaluepairwithan&symbol;forexample,ifwewanttopasscategoryand
priceasGETrequestparametersinaURL,wehavetoformtheURLasfollows:
http://localhost:8080/WebStore/products/product?category=laptop&price=700
Similarly,tomaptheprecedingURLinarequestmappingmethod,ourrequestmapping
methodshouldhaveatleasttwoparameterswiththe@RequestParamannotation:
publicStringgetProducts(@RequestParamStringcategory,@RequestParam
Stringprice){
Popquiz–therequestparameter
Q1.WhichistheappropriaterequestURLforthefollowingrequestmappingmethod
signature?
@RequestMapping(value="/products",method=RequestMethod.GET)
publicStringproductDetails(@RequestParamStringrate,Modelmodel)
1. http://localhost:8080/webstore/products/rate=400
2. http://localhost:8080/webstore/products?rate=400
3. http://localhost:8080/webstore/products?rate/400
4. http://localhost:8080/webstore/products/rate=400
Timeforaction–implementingamaster
detailview
Amasterdetailviewisnothingbutthedisplayofveryimportantinformationonamaster
page.Onceweselectaniteminthemasterview,adetailedpageoftheselecteditemwill
beshowninthedetailviewpage.Let’sbuildamasterdetailviewforourproductlisting
pagesothatwhenweclickonanyproduct,weseethedetailedviewofthatproduct.
Wehavealreadyimplementedtheproductlistingpage
(http://localhost:8080/webshop/products)andproductdetailspage
(http://localhost:8080/webstore/products/product?id=P1234),sotheonlything
neededistoconnectthesetwoviewstomakeamasterdetailview.Performthefollowing
steps:
1. Openproducts.jsp;youcanfindproducts.jspundersrc/main/webapp/WEB-
INF/views/inyourprojectandaddthefollowingspringtaglibreferenceontopof
thefile:
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
2. AddthefollowinglinesaftertheAvailableunitsinstockparagraphtagin
products.jsp:
<p>
<ahref="<spring:urlvalue="/products/product?
id=${product.productId}"/>"class="btnbtn-primary">
<spanclass="glyphicon-info-signglyphicon"/></span>Details
</a>
</p>
3. Now,openproduct.jsp;youcanfindproduct.jspundersrc/main/webapp/WEB-
INF/views/inyourprojectandaddthefollowingspringtaglibreferenceontopof
thefile:
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
And,addthefollowinglinesjustbeforetheOrderNowlinkinproduct.jsp:
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-default">
<spanclass="glyphicon-hand-leftglyphicon"></span>back
</a>
4. RuntheapplicationandentertheURL
http://localhost:8080/webstore/products;youwillbeabletoseeaproductlist
pagethathasaDetailsbuttonwitheveryproduct,asspecifiedinthefollowing
screenshot:
Themasterviewoftheproductlisting
5. Finally,clickonanyproduct’sDetailsbutton,andyouwillbeabletoseethedetail
viewwiththebackbuttonlinktotheproductlistingpage.
Whatjusthappened?
Whatwedidissimple.Instep2,weaddedahyperlinkusingthefollowingtagin
products.jsp:
<ahref="<spring:urlvalue="/products/product?id=${product.productId}"
/>"htmlEscape="true"/>"class="btnbtn-primary">
<spanclass="glyphicon-info-signglyphicon"/></span>Details
</a>
Notethehrefattributeofthe<a>tagasfollows,whichhasa<spring:url>tagasthe
value:
<spring:urlvalue="/products/product?id=${product.productId}"/>
This<spring:url>tagisusedtoconstructavalidSpringURL.Weneededthis
<spring:url>tobeusedinstep2;that’swhyweaddedareferencetothespringtag
libraryinstep1.Observethevalueattributeofthe<spring:url>tag;wecannotethatfor
theidURLparameter,weassignedtheexpression${product.productId}.So,while
renderingthislink,SpringMVCwillassignthecorrespondingproductIDinthat
expression.
Forexample,whilerenderingthelinkofthefirstproduct,SpringMVCwillassignthe
valueP1234fortheproductID.So,thefinalURLvaluewithin<spring:url>willbecome
/products/product?id=P1234,whichisnothingbuttherequestmappingpathofthe
productdetailspage.So,whenyouclickonthislink,youlandonthedetailspageofthat
product.
Similarly,weneedalinktotheproductlistingpagefromtheproductdetailspage;that’s
whyweaddedanotherlinkintheproduct.jsptaginstep4asfollows:
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-default">
<spanclass="glyphicon-hand-leftglyphicon"></span>back
</a>
Notethatthespantagisjustforstylingthebuttonwiththeicon,soyouneedn’tmindit
thatmuch.Theonlyinterestingthingforusisthehrefattributeofthe<a>tag,whichhas
the<spring:url>tagwiththevalueattribute/productsonit.
Haveagohero–addingmultiplefilterstolist
products
ItisgoodthatwelearnedvarioustechniquestobindparameterswithURLs,suchasusing
pathvariables,matrixvariables,andGETparameters.Wesawhowtogetproductsofa
particularcategoryusingpathvariables,howtogetproductswithinaparticularprice
range,andfinally,wesawhowtogetaparticularproductbytheproductID.
Now,imaginethatyouwanttoapplymultiplecriteriatoviewadesiredproduct;for
example,whatifyouwanttoviewaproductthatfallsunderthetabletcategory,iswithin
thepricerangeof$200to$400,andhasbeenmanufacturedbyGoogle?
Toretrieveaproductthatcansatisfyallofthepreviouslymentionedcriteria,wecanform
aURLasfollows:
http://localhost:8080/webstore/products/tablet/price;low=200;high=400?
manufacturer="Google"
Whydon’tyouwriteacorrespondingcontrollermethodtoservetheprecedingrequest
URL?Herearesomehintstoaccomplishtherequirement:
Createarepositorylayermethodtoreturnalltheproductsbasedonmanufacturer.
Forthis,addamethoddeclarationintheproductRepositoryinterfacetogetthe
productsbymanufacturerasfollows:
List<Product>getProductsByManufacturer(Stringmanufacturer);
AddanimplementationforthegetProductsByManufacturer()methodin
InMemoryProductRepository.ItislikethegetProductsByCategory()method;the
onlydifferenceisthatinsteadofthecategory,wefetchtheproductsbymanufacturer
name.
ExtendtheproductServiceinterfacewiththegetProductsByManufacturer()
methodandimplementthesamemethodintheproductServiceImplclass.
CreateonemorerequestmappingmethodcalledfilterProductsinthe
productControllerclasstomapthefollowingURL:
http://localhost:8080/webstore/products/tablet/price;low=200;high=400?
manufacturer="Google"
RememberthatthisURLcontainsthematrixvariableslowandhightorepresentthe
pricerange,theGETparametermanufacturertoidentifythemanufacturer,and
finally,aURItemplatepathvariabletablettorepresentthecategory.
Usethesameviewfileproducts.jsptolistthefilteredproducts.
RememberthatgetProductsByCategoryfromproductServicereturnsproductsbasedon
category,thegetProductsBypriceFiltermethodreturnsproductswithinacertainprice
range,andfinally,ournewlyintroducedmethod,getProductsByManufacturer,returns
productsbelongingtoaparticularmanufacturer.Youhavetocombinethesethreemethod
resultsbeforeupdatingthemodelwiththeproductlistinthefilterProductscontroller
method.Youcanprobablyusejava.util.Settocombinetheresultsofthesethree
servicemethodstoavoidduplication.Goodluck!
Summary
Inthischapter,welearnedhowtodefineacontrollerandtheusageofthe@Controller
annotation.Afterthat,welearnedtheconceptofrelativerequestmapping,wherewesaw
howtodefinerequestmappingatthecontrollerlevelandunderstoodhowSpring
relativelymapsawebrequesttothecontrollerrequestmappingmethod.Wethenlearned
abouttheroleofacontrollerinSpringMVCandabouthowthedispatcherservletuses
handlermappingtofindouttheexacthandlermethods.Wealsosawvariousparameter
bindingtechniques,suchasURItemplatepatterns,matrixvariables,andHTTPGET
requestparameterstobindparameterswithURLs.Finally,wesawhowtoimplementa
masterdetailview.
Inthenextchapter,wearegoingtoexplorevariousSpringtagsthatareavailableinthe
springtaglibrary.Wewillalsolearnmoreaboutformprocessingandhowtobindform
datawiththeHTTPPOSTparameter.Getreadyforthenextchapter!
Chapter4.WorkingwithSpringTag
Libraries
Inpreviouschapters,welearnedhowtoputdataintothemodelfromthecontroller,but
wehaven’tseenhowtodothistheotherwayaround.Thismeansthatwehaven’tlearned
howtoputthedatafromtheviewintothemodel.InSpringMVC,theprocessofputtingan
HTMLformelement’svaluesintomodeldataiscalledformbinding.
SpringMVCprovidessomeJSPtaglibrariestomakeiteasiertobindformelementsto
modeldata.Springtaglibrariesalsosupportvariousothercommonfunctionalities,such
as,externalizingmessagesanderrorhandling.Inthischapter,wearegoingtolearnmore
abouthowtomakeuseofthesepredefinedtaglibrariesofSpring.
Afterfinishingthischapter,wewillhaveagoodideaaboutthefollowingtopics:
Servingandprocessingwebforms
Formbindingandwhitelisting
Springtaglibraries
Servingandprocessingforms
Springsupportsdifferentviewtechnologies,butifweareusingJSP-basedviews,wecan
makeuseoftheSpringtaglibrarytagstomakeupourJSPpages.Thesetagsprovide
manyuseful,commonfunctionalitiessuchasformbinding,evaluatingerrorsoutputting
internationalizedmessages,andsoon.Inordertousethesetags,wemustaddreferences
tothistaglibraryinourJSPpagesasfollows:
<%@taglibprefix="form"uri="http://www.springframework.org/tags/form"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
Inallofourpreviouschapters’examples,wesawthatthedatatransfertookplacefrom
modeltoviewviathecontroller.Thefollowinglineisatypicalexampleofhowweput
dataintothemodelfromacontroller:
model.addAttribute(greeting,"Welcome")
SimilarlythenextlineshowshowweretrievethatdataintheviewusingtheJSTL
expression:
<p>${greeting}</p>
Note
JavaServerPagesStandardTagLibrary(JSTL)isalsoataglibraryprovidedby
Oracle.AnditisacollectionofusefulJSPtagsthatencapsulatesthecorefunctionality
commontomanyJSPpages.WecanaddareferencetotheJSTLtaglibraryinourJSP
pagesas<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>.
However,whatifwewanttoputdataintothemodelfromtheview?Howdoweretrieve
thatdatafromthecontroller?Forexample,considerascenariowhereanadminofour
storewantstoaddnewproductinformationinourstorebyfillingandsubmittingan
HTMLform.HowcanwecollectthevaluesfilledintheHTMLformelementsand
processitinthecontroller?ThisiswheretheSpringtaglibrarytagshelpustobindthe
HTMLtagelement’svaluestoaform-backingbeaninthemodel.Later,thecontrollercan
retrievetheform-backingbeanfromthemodelusingthe@ModelAttributeannotation
(org.springframework.web.bind.annotation.ModelAttribute).
Note
Form-backingbeans(sometimescalledformbeans)areusedtostoreformdata.Wecan
evenuseourdomainobjectsasformbeans;thisworkswellwhenthere’saclosematch
betweenthefieldsontheformandthepropertiesonourdomainobject.Anotherapproach
istocreateseparateclassesforformbeans,whicharesometimescalledDataTransfer
Objects(DTOs).
Timeforaction–servingandprocessing
forms
TheSpringtaglibraryprovidessomespecial<form>and<input>tagsthataremoreor
lesssimilartoHTMLformandinputtags,butithassomespecialattributestobindthe
formelementsdatawiththeform-backingbean.Let’screateaSpringwebforminour
applicationtoaddnewproductstoourproductlistbyperformingthefollowingsteps:
1. WeopenourProductRepositoryinterfaceandaddonemoremethoddeclarationin
itasfollows:
voidaddProduct(Productproduct);
2. WethenaddanimplementationforthismethodintheInMemoryProductRepository
classasfollows:
publicvoidaddProduct(Productproduct){
listOfProducts.add(product);
}
3. WeopenourProductServiceinterfaceandaddonemoremethoddeclarationinitas
follows:
voidaddProduct(Productproduct);
4. And,weaddanimplementationforthismethodintheProductServiceImplclassas
follows:
publicvoidaddProduct(Productproduct){
productRepository.addProduct(product);
}
5. WeopenourProductControllerclassandaddtwomorerequestmappingmethods
asfollows:
@RequestMapping(value="/add",method=RequestMethod.GET)
publicStringgetAddNewProductForm(Modelmodel){
ProductnewProduct=newProduct();
model.addAttribute("newProduct",newProduct);
return"addProduct";
}
@RequestMapping(value="/add",method=RequestMethod.POST)
publicStringprocessAddNewProductForm(@ModelAttribute("newProduct")
ProductnewProduct){
productService.addProduct(newProduct);
return"redirect:/products";
}
6. Finally,weaddonemoreJSPviewfilecalledaddProduct.jspunder
src/main/webapp/WEB-INF/views/andaddthefollowingtagreferencedeclaration
initastheveryfirstline:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="form"uri="http://www.springframework.org/tags/form"
%>
7. Now,weaddthefollowingcodesnippetunderthetagdeclarationlineandsave
addProduct.jsp(notethatIhaveskippedthe<form:input>bindingtagsforsomeof
thefieldsoftheproductdomainobject,butIstronglyencouragethatyouaddbinding
tagsfortheskippedfieldswhenyoutryoutthisexercise):
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-8859-
1">
<link
rel="stylesheet"href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/boo
tstrap.min.css">
<title>Products</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Products</h1>
<p>Addproducts</p>
</div>
</div>
</section>
<sectionclass="container">
<form:formmodelAttribute="newProduct"class="form-horizontal">
<fieldset>
<legend>Addnewproduct</legend>
<divclass="form-group">
<labelclass="control-labelcol-lg-2col-lg-2"
for="productId">ProductId</label>
<divclass="col-lg-10">
<form:inputid="productId"path="productId"type="text"
class="form:input-large"/>
</div>
</div>
<!--Similarlybind<form:input>tagfor
name,unitPrice,manufacturer,category,unitsInStockandunitsInOrder
fields-->
<divclass="form-group">
<labelclass="control-labelcol-lg-2"
for="description">Description</label>
<divclass="col-lg-10">
form:textareaid="description"path="description"rows=
"2"/>
</div>
</div>
<divclass="form-group">
<labelclass="control-labelcol-lg-2"
for="discontinued">Discontinued</label>
<divclass="col-lg-10">
<form:checkboxid="discontinued"path="discontinued"/>
</div>
</div>
<divclass="form-group">
<labelclass="control-labelcol-lg-2"
for="condition">Condition</label>
<divclass="col-lg-10">
<form:radiobuttonpath="condition"value="New"/>New
<form:radiobuttonpath="condition"value="Old"/>Old
<form:radiobuttonpath="condition"value="Refurbished"
/>Refurbished
</div>
</div>
<divclass="form-group">
<divclass="col-lg-offset-2col-lg-10">
<inputtype="submit"id="btnAdd"class="btnbtn-primary"
value="Add"/>
</div>
</div>
</fieldset>
</form:form>
</section>
</body>
</html>
8. Now,werunourapplicationandentertheURL
http://localhost:8080/webstore/products/add.Wewillbeabletoseeaweb
pagethatdisplaysawebformwherewecanaddtheproductinformationasshownin
thefollowingscreenshot:
Addtheproduct’swebform
9. Now,weenteralltheinformationrelatedtothenewproductthatwewanttoaddand
clickontheAddbutton;wewillseethenewproductaddedintheproductlisting
pageundertheURLhttp://localhost:8080/webstore/products.
Whatjusthappened?
Inthewholesequence,steps5and6areveryimportantstepsthatneedtobeobserved
carefully.Whateverismentionedpriortostep5isfamiliaraswehaveseenitinprevious
recipes;anyhow,Iwillgiveyouabriefnoteonwhatwehavedoneinsteps1to4.
Instep1,wecreatedamethoddeclarationaddProductinourProductRepository
interfacetoaddnewproducts.Instep2,weimplementedtheaddProductmethodinour
InMemoryProductRepositoryclass;theimplementationisjusttoupdatetheexisting
listOfProductsbyaddinganewproducttothelist.Steps3and4arejustaservicelayer
extensionforProductRepository.Instep3,wedeclaredasimilarmethod,addProduct,
inourProductServiceinterfaceandimplementeditinstep4toaddproductstothe
repositoryviatheproductRepositoryreference.
Okay,comingbacktotheimportantstep;wehavedonenothingbutaddedtworequest
mappingmethods,namely,getAddNewProductFormandprocessAddNewProductForm,in
step5asfollows:
@RequestMapping(value="/add",method=RequestMethod.GET)
publicStringgetAddNewProductForm(Modelmodel){
ProductnewProduct=newProduct();
model.addAttribute("newProduct",newProduct);
return"addProduct";
}
@RequestMapping(value="/add",method=RequestMethod.POST)
publicStringprocessAddNewProductForm(@ModelAttribute("newProduct")
ProductproductToBeAdded){
productService.addProduct(productToBeAdded);
return"redirect:/products";
}
Ifyouobservethesemethodscarefully,youwillnoticeapeculiarthing,whichisthatboth
themethodshavethesameURLmappingvalueintheir@RequestMappingannotation
(value="/add").So,ifweentertheURL
http://localhost:8080/webstore/products/addinthebrowser,whichmethodwill
SpringMVCmapthatrequestto?
Theanswerliesinthesecondattributeofthe@RequestMappingannotation(method=
RequestMethod.GETandmethod=RequestMethod.POST).Ifyouwillnoticeagain,even
thoughbothmethodshavethesameURLmapping,theydifferinrequestmethod.
So,whatishappeningbehindthescreenisthatwhenweentertheURL
http://localhost:8080/webstore/products/addinthebrowser,itisconsideredasa
GETrequest.So,SpringMVCmapsthisrequesttothegetAddNewProductFormmethod,
andwithinthismethod,wesimplyattachanewemptyProductdomainobjecttothe
modelundertheattributename,newProduct.
ProductnewProduct=newProduct();
model.addAttribute("newProduct",newProduct);
Sointheviewaddproduct.jsp,wecanaccessthismodelobject,newProduct.Before
jumpingintotheprocessAddNewProductFormmethod,let’sreviewtheaddproduct.jsp
viewfileforsometimesothatweareabletounderstandtheformprocessingflowwithout
confusion.Inaddproduct.jsp,wehavejustaddeda<form:form>tagfromtheSpringtag
libraryusingthefollowinglineofcode:
<form:formmodelAttribute="newProduct"class="form-horizontal">
Sincethisspecial<form:form>tagisacquiredfromtheSpringtaglibrary,weneedtoadd
areferencetothistaglibraryinourJSPfile.That’swhywehaveaddedthefollowingline
atthetopoftheaddProducts.jspfileinstep6:
<%@taglibprefix="form"uri="http://www.springframework.org/tags/form"%>
IntheSpring<form:form>tag,oneoftheimportantattributesismodelAttribute.Inour
case,weassignedthevaluenewProductasthevalueofmodelAttributeinthe
<form:form>tag.Ifyourecallcorrectly,youwillnoticethatthisvalueofmodelAttribute
andtheattributenameweusedtostorethenewProductobjectinthemodelfromour
getAddNewProductFormmethodarethesame.So,thenewProductobjectthatweattached
tothemodelinthecontrollermethod(getAddNewProductForm)isnowboundtotheform.
Thisobjectiscalledtheform-backingbeaninSpringMVC.
Okay,nownoticeeach<form:input>taginsidethe<form:form>tagshowninthe
followingcode.Youwillobservethatthereisacommonattributeineverytag.This
attributenameispath:
<form:inputid="productId"path="productId"type="text"class="form:input-
large"/>
Thepathattributejustindicatesthefieldnamethatisrelativetotheform-backingbean.
So,thevaluethatisenteredinthisinputboxatruntimewillbeboundtothe
correspondingfieldoftheformbean.
Okay,nowisthetimetocomebackandreviewourprocessAddNewProductFormmethod.
Whenwillthismethodbeinvoked?Thismethodwillbeinvokedoncewepressthesubmit
buttonofourform.Yes,sinceeveryformsubmissionisconsideredasaPOSTrequest,
thistimethebrowserwillsendaPOSTrequesttothesameURL,thatis,
http://localhost:8080/webstore/products/add.
So,thistime,theprocessAddNewProductFormmethodwillgetinvokedsinceitisaPOST
request.InsidetheprocessAddNewProductFormmethod,wesimplycalltheservice
methodaddProducttoaddthenewproducttotherepository,asfollows:
productService.addProduct(productToBeAdded);
However,theinterestingquestionhereis,howistheproductToBeAddedobjectpopulated
withthedatathatweenteredintheform?Theanswerlieswithinthe@ModelAttribute
annotation(org.springframework.web.bind.annotation.ModelAttribute).Notethe
methodsignatureoftheprocessAddNewProductFormmethodshowninthefollowingline
ofcode:
publicStringprocessAddNewProductForm(@ModelAttribute("newProduct")
ProductproductToBeAdded)
Here,ifyounoticethevalueattributeofthe@ModelAttributeannotation,youwill
observeapattern.Thevaluesofthe@ModelAttributeannotationandmodelAttribute
fromthe<form:form>tagarethesame.So,SpringMVCknowsthatitshouldassignthe
form-boundnewProductobjecttotheproductToBeAddedparameterofthe
processAddNewProductFormmethod.
The@ModelAttributeannotationisnotonlyusedtoretrieveanobjectfromamodel,but
ifwewantto,wecanevenuseittoaddobjectstothemodel.Forinstance,werewriteour
getAddNewProductFormmethodtosomethinglikethefollowingcodewiththeuseofthe
@ModelAttributeannotation:
@RequestMapping(value="/add",method=RequestMethod.GET)
publicStringgetAddNewProductForm(@ModelAttribute("newProduct")Product
newProduct){
return"addProduct";
}
Youcannoticethatwehaven’tcreatedanynewemptyProductdomainobjectand
attachedittothemodel.AllwehavedonewasaddedaparameterofthetypeProductand
annotateditwiththe@ModelAttributeannotationsothatSpringMVCwouldknowthatit
shouldcreateanobjectofProductandattachittothemodelunderthenamenewProduct.
OnemorethingthatneedstobeobservedintheprocessAddNewProductFormmethodis
thelogicalviewname,redirect:/products,thatitreturns.So,whatarewetryingtotell
SpringMVCbyreturningastringredirect:/products?Togettheanswer,observethe
logicalviewnamestringcarefully.Ifwesplitthisstringwiththe:(colon)symbol,we
willgettwoparts;thefirstpartistheprefixredirectandthesecondpartissomething
thatlookslikearequestpath,/products.So,insteadofreturningaviewname,wesimply
instructSpringtoissuearedirectrequesttotherequestpath,/products,whichisthe
requestpathforthelistmethodofourProductControllerclass.So,aftersubmittingthe
form,welisttheproductsusingthelistmethodofProductController.
Note
Asamatteroffact,whenwereturnanyrequestpathwiththeredirect:prefixfroma
requestmappingmethod,Springusesaspecialviewobject,RedirectView
(org.springframework.web.servlet.view.RedirectView),toissuearedirectcommand
behindthescreen.WewillseemoreaboutRedirectViewintheupcomingchapter.
Insteadoflandinginawebpageafterthesuccessfulsubmissionofawebform,weare
spawninganewrequesttotherequestpath/productswiththehelpofRedirectView.
ThispatterniscalledRedirectAfterPost,whichisacommonpatterntousewithweb-
basedforms.Weareusingthispatterntoavoiddoublesubmissionofthesameform;
sometimes,ifwepressthebrowser’srefreshbuttonorbackbuttonaftersubmittingthe
form,therearechancesthatthesameformwillberesubmitted.
Customizingdatabinding
Inthelastsection,wesawhowtobinddatasubmittedbyanHTMLformorbyquery
stringparameterstoaform-backingbean.Inordertodothebinding,SpringMVC
internallyusesaspecialbindingobjectcalledWebDataBinder
(org.springframework.web.bind.WebDataBinder).
TheWebDataBinderobjectextractsthedataoutoftheHttpServletRequestobject,
convertsittoaproperdataformat,loadsitintoaform-backingbean,andvalidatesit.To
customizethebehaviorofthedatabinding,wecaninitializeandconfigurethe
WebDataBinderobjectinourcontroller.The@InitBinderannotation
(org.springframework.web.bind.annotation.InitBinder)helpsusdothis.The
@InitBinderannotationdesignatesamethodtoinitializeWebDataBinder.
Let’sseeapracticalwayofcustomizingWebDataBinder.Sinceweareusingtheactual
domainobjectitselfastheform-backingbean,duringformsubmission,thereisachance
ofsecurityvulnerability.SinceSpringautomaticallybindsHTTPparameterstoformbean
properties,anattackercouldbindsuitablynamedHTTPparameterswithformproperties
thatweren’tintendedforbinding.Toaddressthisproblem,wecanexplicitlytellSpring
whichfieldsareallowedforformbinding.Technicallyspeaking,theprocessofexplicitly
specifyingtheallowedfieldsforformbindingiscalledwhitelistingformfieldsinSpring
MVC;wecandowhitelistingusingWebDataBinder.
Timeforaction–whitelistingformfields
Inthepreviousexercise,whileaddinganewproduct,weboundeveryfieldoftheProduct
domainintheform.However,itismeaninglesstospecifytheunitsInOrderand
discontinuedvaluesduringtheadditionofanewproductbecausenobodycanmakean
orderbeforeaddingtheproducttothestore,andsimilarly,thediscontinuedproducts
neednotbeaddedinourproductlist.So,weshouldnotallowthesefieldstobeboundto
theformbeanwhileaddinganewproducttoourstore.However,alltheotherfieldsofthe
Productdomainobjectneedtobebound.Let’sseehowtodothiswiththefollowing
steps:
1. WeopenourProductControllerclassandaddamethodasfollows:
@InitBinder
publicvoidinitialiseBinder(WebDataBinderbinder){
binder.setDisallowedFields("unitsInOrder","discontinued");
}
2. WethenaddanextraparameterofthetypeBindingResult
(org.springframework.validation.BindingResult)tothe
processAddNewProductFormmethodasfollows:
publicStringprocessAddNewProductForm(@ModelAttribute("newProduct")
ProductproductToBeAdded,BindingResultresult)
3. InthesameprocessAddNewProductFormmethod,weaddthefollowingconditionjust
beforethelinewherewesavedtheproductToBeAddedobject:
String[]suppressedFields=result.getSuppressedFields();
if(suppressedFields.length>0){
thrownewRuntimeException("Attemptingtobinddisallowedfields:"
+StringUtils.arrayToCommaDelimitedString(suppressedFields));
}
4. Now,werunourapplicationandentertheURL
http://localhost:8080/webstore/products/add;wewillbeabletoseeaweb
pagethatdisplaysawebformwherewecanaddnewproductinformation.Let’sfill
everyfield,particularlyUnitsinorderandDiscontinued.
5. Now,clickontheAddbutton.YouwillseeanHTTPStatus500errorontheweb
page,asshowninthefollowingscreenshot:
Addproductpageshowingerrorfordisallowedfields
6. Now,weopenaddProduct.jspfrom/webstore/src/main/webapp/WEB-INF/views/
inourprojectandremovetheinputtagsthatarerelatedtotheUnitsinorderand
Discontinuedfields.Basically,weneedtoremovethefollowingblockofcode:
<divclass="form-group">
<labelclass="control-labelcol-lg-2"for="unitsInOrder">UnitsIn
Order</label>
<divclass="col-lg-10">
<form:inputid="unitsInOrder"path="unitsInOrder"type="text"
class="form:input-large"/>
</div>
</div>
<divclass="form-group">
<labelclass="control-labelcol-lg-2"
for="discontinued">Discontinued</label>
<divclass="col-lg-10">
<form:checkboxid="discontinued"path="discontinued"/>
</div>
</div>
7. Now,werunourapplicationagainandentertheURL
http://localhost:8080/webstore/products/add.Wewillbeabletoseeaweb
pagethatdisplaysawebformwherewecanaddanewproduct,butthistime,
withouttheUnitsinorderandDiscontinuedfields.
8. Now,weenteralloftheinformationrelatedtothenewproductandclickontheAdd
button;wewillseethenewproductaddedintheproductlistingpageundertheURL
http://localhost:8080/webstore/products.
Whatjusthappened?
OurintentionwastoputsomerestrictionsonbindingtheHTTPparameterswiththeform-
backingbean.Aswealreadydiscussed,theautomaticbindingfeatureofSpringcouldlead
toapotentialsecurityvulnerability,incaseweusedthedomainobjectitselfastheform
bean.So,wehavetoexplicitlyspecifytoSpringMVCwhattheonlyallowedfieldsare.
That’swhatwedidinstep1.
The@InitBinderannotationdesignatesacontrollermethodasahookmethodtodosome
customconfigurationregardingdatabindingonWebDataBinder.And,WebDataBinderis
theonethatdoesthedatabindingatruntime,soweneedtospecifytoWebDataBinder
onlythefieldsallowedforbinding.IfyouobserveourinitialiseBindermethodfrom
ProductController,ithasaparametercalledbinder,whichisofthetype
WebDataBinder.WesimplycallthesetAllowedFieldsmethodonthebinderobjectand
passthefields’namesthatareallowedforbinding.SpringMVCcallsthismethodto
initializeWebDataBinderbeforebindingsinceithasthe@InitBinderannotation.
Note
TheWebDataBinderclassalsohasamethodcalledsetDisallowedFieldstostrictly
specifythedisallowedfieldsforbinding.Ifyouusethismethod,SpringMVCallowsany
HTTPrequestparameterstobebound,exceptthatthesefieldnamesarespecifiedinthe
setDisallowedFieldsmethod.
Okay,weconfiguredwhichfieldsareallowedforbinding,butweneedtoverifywhether
anyotherfieldsotherthantheonesallowedareboundwiththeform-backingbean.That’s
whatwedidinsteps2and3.
WechangedprocessAddNewProductFormbyaddingoneextraparametercalledresult,
whichisofthetypeBindingResult.SpringMVCwillfillthisobjectwiththeresultofthe
binding.Ifanyattemptismadetobindanythingotherthantheallowedfields,the
getSuppressedFieldscountoftheBindingResultobjectwillbegreaterthanzero.That’s
why,wecheckedsuppressedfieldcountandthrewRuntimeExceptionasfollows:
if(suppressedFields.length>0){
thrownewRuntimeException("Attemptingtobinddisallowedfields:"+
StringUtils.arrayToCommaDelimitedString(suppressedFields));
}
Wewantedtoensurethatourbindingconfigurationisworking;that’swhy,weranour
applicationwithoutchangingtheviewfileaddProduct.jspinstep4.Asexpected,wegot
theHTTPStatus500errorsayingAttemptingtobinddisallowedfieldswhenwe
submittedtheaddproductformwiththeunitsInOrderanddiscontinuedfieldsfilled.
Werealizedthatourbinderconfigurationisworking,sowechangedourviewfiletonot
bindthedisallowedfields.That’swhatwedidinstep6;wejustremovedtheinputfield
elementsthatarerelatedtothedisallowedfieldsfromtheaddProduct.jspfile.
Afterthis,ourpageforaddingnewproductsworksjustfine,asexpected.Incaseanyof
theoutsideattackerstrytotamperthePOSTrequestandattachaHTTPparameterwiththe
samefieldnameoftheform-backingbean,theywillgetRuntimeException.
Whitelistingisjustanexampleofhowwecancustomizethebindingwiththehelpof
WebDataBinder.However,usingWebDataBinder,wecandoothertypesofbinding
customizationsaswell.Forexample,WebDataBinderinternallyusesmany
PropertyEditor(java.beans.PropertyEditor)implementationstoconvertHTTP
requestparameterstothetargetfieldoftheform-backingbean.Wecanevenregisterthe
customPropertyEditorobjectswithWebDataBindertoconvertmorecomplexdatatypes.
Forinstance,takealookatthefollowingcodesnippetthatshowshowtoregisterthe
customPropertyEditorclasstoconvertaDatetype:
@InitBinder
publicvoidinitialiseBinder(WebDataBinderbinder){
DateFormatdateFormat=newSimpleDateFormat("MMMd,YYYY");
CustomDateEditororderDateEditor=newCustomDateEditor(dateFormat,
true);
binder.registerCustomEditor(Date.class,orderDateEditor);
}
TherearemanyadvancedconfigurationswecandowithWebDataBinderintermsofdata
binding,butforabeginnerlevel,wedon’tneedtogosodeep.
Externalizingtextmessages
Sofar,inallourviewfiles,wehardcodedtextvaluesforallofthelabels.Forinstance,
takeouraddProduct.jspfilefortheproductIDinputtag;wehavealabeltagwitha
hardcodedtextvalueasProductId,asfollows:
<labelclass="control-labelcol-lg-2col-lg-2"for="productId">Product
Id</label>
Externalizingthesetextsfromaviewfileintoapropertiesfilewillhelpushavesingle,
centralizedcontrolofallthelabelmessages,andmoreover,itwillhelpusmakeourweb
pagesreadyforinternationalization.Wewillseemoreaboutinternationalizationin
Chapter6,InterceptYourStorewithInterceptor,butinordertodointernationalization,we
needtoexternalizethelabelmessagesfirst.Sonow,wearegoingtoseeonlyhowto
externalizethelocale-sensitivetextmessagesfromourwebpage.
Timeforaction–externalizingmessages
Let’sseehowtoexternalizethelabeltextsinouraddProduct.jspfile:
1. WeopenouraddProduct.jspfileandaddthefollowingtaglibreferenceatthetop:
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
2. ChangetheproductID<label>tag’svalueas<spring:message
code="addProdcut.form.productId.label"/>,asshownasfollows:
<labelclass="control-labelcol-lg-2col-lg-2"for="productId">
<spring:messagecode="addProduct.form.productId.label"/></label>
3. Wecreateafilecalledmessages.propertiesunder/src/main/resourcesinour
projectandaddthefollowinglineinit:
addProduct.form.productId.label=NewProductID
4. Now,weopenourwebapplicationcontextconfigurationfileDispatcherServlet-
context.xmlfromsrc/main/webapp/WEB-INF/spring/webcontext/andaddthe
followingbeandefinitioninit:
<beanid="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
>
<propertyname="basename"value="messages"/>
</bean>
5. Now,werunourapplicationagainandentertheURL
http://localhost:8080/webstore/products/add.Wewillbeabletoseetheadd
productpagewiththeproductIDlabelNewProductID.
Whatjusthappened?
SpringMVChasaspecialatagcalled<spring:message>toexternalizetextsfromJSP
files.Inordertousethistag,weneedtoaddareferencetotheSpringtaglibrary,whichis
whatwedidinstep1.WejustaddedareferencetotheSpringtaglibraryinour
addProduct.jspfileasfollows:
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
Instep2,weusedthistagtoexternalizethelabeltextoftheproductIdinputtag,as
follows:
<labelclass="control-labelcol-lg-2col-lg-2"for="productId">
<spring:messagecode="addProduct.form.productId.label"/></label>
Here,animportantthingthatneedstobenotedisthecodeattributeofthe
<spring:message>tagthatwehaveassignedthevalue
addProduct.form.productId.labelasthecodeforthis<spring:message>tag.This
codeattributeisakindofkey,andatruntime,Springwilltrytoreadthecorresponding
valueforthegivenkey(code)fromamessagesourcepropertyfile.
WesaidthatSpringwillreadthemessagevaluefromamessagesourcepropertyfile,so
weneedtocreatethatpropertyfile,whichiswhatwedidinstep3.Wejustcreateda
propertyfilewiththenamemessages.propertiesundertheresourcedirectory.Insidethis
file,weassignedthelabeltextvaluetothemessagetagcodeasfollows:
addProduct.form.productId.label=NewProductID
Notethatfordemonstrationpurposes,Ijustexternalizedasinglelabel,butatypicalweb
applicationwillhaveexternalizedmessagesalmostforallofthelabels.Inthatcase,the
messages.propertiesfilewillhavemanycode-valuepairentries.
Okay,wehavecreatedthemessagesourcepropertyfileandaddedthe<spring:message>
taginourJSPfile.However,toconnectthesetwo,weneedtocreateonemoreSpring
beaninourwebapplicationcontextforthe
org.springframework.context.support.ResourceBundleMessageSourceclasswiththe
namemessageSource.Wedidthisinstep4asfollows:
<beanid="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<propertyname="basename"value="messages"/>
</bean>
Oneimportantpropertythatneedstobenotedhereisthebasenameproperty.Weassigned
thevaluemessagesforthatproperty;ifyouremember,thisisthenameofthepropertyfile
thatwecreatedinstep3.
ThatisallthatwehavedonetoenabletheexternalizationofmessagesinaJSPfile.Now,
ifweruntheapplicationandopenuptheaddproductpage,wecanseethattheproductID
labelhasthesametextasthatassignedtothecodeaddProduct.form.productId.label
inthemessages.propertiesfile.
UsingSpringSecuritytags
Atthestartofthischapter,wesawhowtoserveandprocesswebforms.Inthatexercise,
wecreatedawebpagetoaddproducts.Anyonewithaccesstotheaddproductspagecan
addnewproductstoourwebstore.However,inatypicalwebstore,onlytheadministrator
canaddproducts.So,howdowerestrictotherusersfromaccessingtheaddproducts
page?TherecomesSpringSecuritytohelpus.
SpringSecurityisavasttopic,sowearenotgoingtoseeallofthecapabilitiesofSpring
Security;instead,weareonlygoingtoseehowtoaddbasicauthenticationtoourweb
pages.
Timeforaction–addingaloginpage
WearegoingtouseSpringSecurityfeaturestorestrictaccesstotheaddproductspage.
Onlyanauthorizeduserwithavalidusernameandpasswordwillbeabletoaccesstheadd
productspage.Let’sseehowwecandothisinSpringMVCwiththefollowingsteps:
1. Weopenpom.xml,whichcanbefoundundertheprojectrootfolderitself.
2. Wewillbeabletoseesometabsatthebottom,underthepom.xmlfile;weselectthe
DependenciestabandclickontheAddbuttonoftheDependenciessection.
3. ASelectDependencywindowwillappear;here,weenterGroupIdas
org.springframework.security,ArtifactIdasspring-security-config,Version
as3.1.4.RELEASE,andselectScopeascompileandclickontheOKbutton.
4. Similarly,weaddonemoredependencyGroupIdas
org.springframework.security,ArtifactIdasspring-security-web,Versionas
3.1.4.RELEASE,andselectScopeascompileandclickontheOKbutton.Andmost
importantly,wesavepom.xml.
5. Now,wegototheadjacenttab,whichistheDependencyHierarchytabinpom.xml.
WecanseetheResolvedDependenciessectionontheright,whichlistsallthe
resolveddependencyentries.
6. Wejustright-clickontheentrywiththenamespring-
asm:3.0.7.RELEASE[compile]fromtheResolvedDependencieslistandchoosethe
ExcludeMavenArtifact…optionandclickonOK.Then,wesavepom.xml.
7. Now,wecreateonemorecontrollerclasscalledLoginControllerunderthe
com.packt.webstore.controllerpackageinsrc/main/javaandaddthefollowing
codeintoit:
packagecom.packt.webstore.controller;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
@Controller
publicclassLoginController{
@RequestMapping(value="/login",method=RequestMethod.GET)
publicStringlogin(){
return"login";
}
@RequestMapping(value="/loginfailed",method=RequestMethod.GET)
publicStringloginerror(Modelmodel){
model.addAttribute("error","true");
return"login";
}
@RequestMapping(value="/logout",method=RequestMethod.GET)
publicStringlogout(Modelmodel){
return"login";
}
}
8. And,weaddonemoreJSPviewfilecalledlogin.jspundersrc/main/webapp/WEB-
INF/views/andaddthefollowingcodesnippetintoitandsaveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="form"uri="http://www.springframework.org/tags/form"
%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"
%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-8859-
1">
<link
rel="stylesheet"href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/boo
tstrap.min.css">
<title>Products</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Products</h1>
<p>Addproducts</p>
</div>
</div>
</section>
<divclass="container">
<divclass="row">
<divclass="col-md-4col-md-offset-4">
<divclass="panelpanel-default">
<divclass="panel-heading">
<h3class="panel-title">Pleasesignin</h3>
</div>
<divclass="panel-body">
<c:iftest="${notemptyerror}">
<divclass="alertalert-danger">
<spring:message
code="AbstractUserDetailsAuthenticationProvider.
badCredentials"/><br/>
</div>
</c:if>
<formaction="<c:urlvalue="/j_spring_security_check">
</c:url>"method="post">
<fieldset>
<divclass="form-group">
<inputclass="form-control"placeholder="UserName"
name='j_username'type="text">
</div>
<divclass="form-group">
<inputclass="form-control"placeholder="Password"
name='j_password'type="password"value="">
</div>
<inputclass="btnbtn-lgbtn-successbtn-block"
type="submit"value="Login">
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
9. Now,weopenouraddProduct.jspfileandaddthefollowingcodetagwithinthe
jumbotrondivtag:
<ahref="<c:urlvalue="/j_spring_security_logout"/>"class="btnbtn-
dangerbtn-minipull-right">logout</a>
10. Then,weopenourmessagesourcefilemessages.propertiesfrom
/src/main/resourcesandaddthefollowinglineinit:
AbstractUserDetailsAuthenticationProvider.badCredentials=Theusername
orpasswordyouenteredisincorrect.
11. Now,wecreateonemorebeanconfigurationfilecalledsecurity-context.xml
undersrc/main/webapp/WEB-INF/spring/webcontextandaddthefollowing
contentintoitandsaveit:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<security:httpauto-config="true">
<security:intercept-urlpattern="/products/add"access="ROLE_ADMIN"
/>
<security:form-loginlogin-page="/login"
default-target-url="/products/add"
authentication-failure-url="/loginfailed"/>
<security:logoutlogout-success-url="/logout"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:username="Admin"password="Admin123"
authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
12. Then,weaddthefollowingtagsinweb.xmlunderthe<web-app>tag:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/webcontext/security-context.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
13. Now,wealsoaddthefollowingtagsinweb.xmlunderthe<web-app>tagandsaveit:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
14. WerunourapplicationandentertheURL
http://localhost:8080/webstore/products/add.Wewillbeabletoseealogin
page,asshowninthefollowingscreenshot:
Theloginpageshowinganerrormessageforinvalidcredentials
15. Now,weenterUserNameasAdminandPasswordasAdmin123andclickonthe
Loginbutton.Finally,wewillbeabletoseetheregularaddproductspagewitha
Logoutbutton.
Whatjusthappened?
Asusual,inordertouseSpringSecurityinourproject,weneedsomeSpringSecurity
relatedjars;fromsteps1to4,wejustaddedthosejarsasMavendependencies.However,
wedidsomethingunusualinsteps5and6.Weexcludedthespring-asmdependency
(spring-asm:3.0.7.RELEASE[compile])fromtheresolveddependencieslist.
FromtheSpring3.2versiononwards,thespring-asmmodulehadalreadybeenincluded
inthespring-coremodule,sothereisnoneedtohavespring-asmasaseparate
transitivedependency.Ifyouhaveskippedsteps5and6,youwillget
java.lang.IncompatibleClassChangeErrorwhenstartinguptheproject.
Instep7,wecreatedonemorecontroller(LoginController)tohandleallourlogin-
relatedwebrequeststhatcontainsimplythreerequestmappingmethodscorrespondingly
tohandlelogin,loginfailure,andlogoutrequests.Allthreemethodsreturnthesameview
name,outofwhichtheloginerrormethodsetsamodelvariableerrortotrueinthe
model.
Sinceinstep7,alltherequestmappingmethodsreturntheviewnamelogin,weneedto
createaviewfilelogin.jsp,whichiswhatwedidinstep8.
Thelogin.jspfilecontainsmanytagswithaBootstrap-styleclassappliedtoenhancethe
lookandfeeloftheloginform;wedon’tneedtoconcentrateonthesetags.However,
therearesomeimportanttagsouttherethatcanbeusedtounderstandtheflow;thefirst
oneisthe<c:if>tag,asshowninthefollowingcode:
<c:iftest="${notemptyerror}">
<divclass="alertalert-danger">
<spring:message
code="AbstractUserDetailsAuthenticationProvider.badCredentials"/>
</div>
</c:if>
The<c:if>tagisaspecialJSTLtagusedtocheckacondition;itismorelikeanif
conditionthatweuseinourprogramminglanguage.Usingthis<c:if>tag,wesimply
checkwhetherthemodelvariableerrorcontainsanyvalue.Ifthemodelvariableerroris
notempty,wesimplyshowanerrormessagewithinthedivtagusingthe
<spring:message>tag.
RememberthatfromtheExternalizingtextmessagesexercise,wealreadylearned
howtoexternalizemessages.Inthisrecipe,wesimplyusedthepredefinederrorkey,
AbstractUserDetailsAuthenticationProvider.badCredentials,ofSpringSecurityas
themessagekey.Sincewedidthis,wejustoverrodethedefaulterrormessageinstep10.
Okay,comingbacktostep8,whataretheotherimportanttagsinthelogin.jspfile?The
nextimportanttagistheformtag,whichrepresentstheloginform.Notetheaction
attributeoftheformtagshowninthefollowingcode:
<formaction="<c:urlvalue="/j_spring_security_check"></c:url>"
method="post">
Wesimplypostourloginformvalues,suchasusernameandpassword,totheSpring
SecurityauthenticationhandlerURL,whichis/j_spring_security_check.Here,the
special<c:url>JSTLtagisusedtoformattheURL.
WhilepostingtheusernameandpasswordtotheSpringSecurityauthenticationhandler,
Springexpectsthesevaluestobeboundunderthevariablenamesj_usernameand
j_passwordcorrespondingly.That’swhy,ifyounoticetheinputtagfortheusernameand
password,itcarriesthenameattributesasj_usernameandj_password,asfollows:
<inputclass="form-control"placeholder="UserName"name='j_username'
type="text">
<inputclass="form-control"placeholder="Password"name='j_password'
type="password"value="">
Similarly,Springhandlesthelogoutoperationunderthej_spring_security_logout
URL;that’swhy,instep9,weformedthelogoutlinkontheaddproductspageasfollows:
<ahref="<c:urlvalue="/j_spring_security_logout"/>"class="btnbtn-danger
btn-minipull-right">logout</a>
WearealmostdonewiththecodingtoincorporateSpringSecurityintoourproject,but
stillweneedtodosomemoreconfigurationtogetitupandrunningwiththebasic
authenticationfortheaddproductspage.Forthefirstconfiguration,weneedtodefineour
authenticationmanagerandspecifytheauthenticatedusersandrolestoSpringSecurity.
Wedidthiswiththehelpofasecuritycontextfile.
Asecuritycontextfileismoresimilartoawebapplicationcontextconfigurationfile.
Basedontheconfigurationandbeandefinitionfoundinthisfile,Springcreatesand
managesthenecessarybeansrelatedtoSpringSecurity.Wecreatedsuchasecurity
contextfileinstep11.Thefirstconfigurationtagthatisfoundinthissecuritycontextfile
(security-context.xml)is<security:http>,asfollows:
<security:httpauto-config="true">
<security:intercept-urlpattern="/products/add"access="ROLE_ADMIN"/>
<security:form-loginlogin-page="/login"
default-target-url="/products/add"
authentication-failure-url="/loginfailed"/>
<security:logoutlogout-success-url="/logout"/>
</security:http>
The<security:http>tagcontainsalotofinformation,andwewillseethemonebyone.
Thefirstconfigurationwithinthe<security:http>tagisasfollows:
<security:intercept-urlpattern="/products/add"access="ROLE_ADMIN"/>
ThisinstructsSpringtointercepteverywebrequestthatisreceivedbytherequestpath
/products/addandonlyallowsaccesstowhicheveruserhastheroleofROLE_ADMIN.If
yourecall,/products/addisnothingbuttherequestpathforouraddproductspage.
Thenextconfigurationwithinthe<security:http>tagisasfollows:
<security:form-loginlogin-page="/login"
default-target-url="/products/add"
authentication-failure-url="/loginfailed"/>
Here,thelogin-pageattributedenotestheURLthatitshouldforwardtherequestto,to
gettheloginform;rememberthatthisrequestpathshouldbethesameastherequest
mappingofthelogin()methodofLoginController.Also,default-target-urldenotes
thedefaultlandingpageafterasuccessfullogin,andthefinalattributeauthentication-
failure-urlindicatestheURLthattherequestneedstobeforwardedtointhecaseofa
loginfailure.
Thefinalconfiguration,<security:logoutlogout-success-url="/logout"/>,denotes
wheretherequestneedstobeforwardedafteralogout.Rememberthatthisalsocarriesthe
samerequestmappingvalue,whichisthevalueofthelogoutmethod,fromthe
LoginControllerclass.
Thenextconfigurationtaginthesecuritycontextfileisthe<security:authentication-
manager>tag;refertothefollowingcode:
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:username="Admin"password="Admin123"
authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
Theimportantinformationconfiguredundertheauthenticationmanageriswhotheusers
are,whattheircorrespondingpasswordis,andwhichrolestheyhave,asfollows:
<security:username="Admin"password="Admin123"authorities="ROLE_ADMIN"/>
TheprecedingpieceofconfigurationsaysthatitisauserwiththenameAdminandhasa
passwordAdmin123andaroleROLE_ADMIN.Wecanaddasmanyrolesaswewantby
separatingthemwithacomma.
Okay,wedefinedthesecurity-relatedconfigurationinthesecuritycontextfile,butSpring
shouldknowaboutthisfileandhavetoreadthisfilebeforebootingtheapplication.Then
onlywillitbeabletocreateandmanagethesecurity-relatedbeans.Howdoweinstruct
Springtopickupthisfile?Theansweristhesame:thecontextConfigLocationlocation
propertythatwehaveusedtolocatethewebapplicationcontextconfigurationfile.
However,thistime,weloadedthesecuritycontextfilethroughthe
ContextLoaderListenerclassandnotthroughthedispatcherservlet.That’swhy,we
initiatedContextLoaderListenerinweb.xmlandgavecontextConfigLocationviathe
<context-param>taginstep12,asfollows:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/webcontext/security-context.xml</param-
value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
Basedontheprecedingconfiguration,theContextLoaderListenerclasswillloadour
securitycontextfile(/WEB-INF/spring/webcontext/security-context.xml)intothe
SpringruntimesothatSpringcancreatethenecessarybeanswhilebootingthe
application.
Asafinalstep,weneedtoconfiguretheSpringSecurityfilterinourweb.xmlfilesothat
everywebrequestcanbeexaminedforuserauthentication.Thisiswhatweconfiguredin
step13,asfollows:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Afterfinishingallthesteps,ifweaccesstheURL
http://localhost:8080/WebStore/products/add,SpringMVCwillpromptusto
providetheusernameandpassword.Sincewehaveconfiguredanadminuserinstep11
withUserNameasAdminandPasswordasAdmin123,wehavetoprovidethese
credentialstoproceedtotheaddproductspage.
Summary
Atthestartofthischapter,wesawhowtoserveandprocessforms;welearnedhowto
bindformdatawithaform-backingbeanandreadthatbeaninthecontroller.Afterthat,
wewentalittledeeperintoformbeanbindingandconfiguredthebinderinourcontroller
towhitelistsomeofthePOSTparametersfrombeingboundtotheformbean.Wesaw
howtouseonemorespecialtag,<spring:message>,ofSpringtoexternalizemessagesin
aJSPfile.Finallywealsosawhowtoincorporatespringsecuritytodobasic
authenticationtoaccessproductaddpage.
Inthenextchapter,wewilllearnmoreaboutviewandviewresolvers.
Chapter5.WorkingwithViewResolver
Inthepreviouschapter,welearnedhowwecanusesomeoftheSpringtagsthatcanonly
beusedinJSPandJSTLviews.However,Springhasexcellentsupportforotherview
technologiesaswell,suchastheXMLview,JSONview,andsoon.SpringMVCmaintains
ahighlevelofdecouplingbetweentheviewandcontroller.Thecontrollerknowsnothing
aboutviewexcepttheviewname.Itistheresponsibilityoftheviewresolvertomapthe
correctviewforthegivenviewname.
Inthischapter,wewilltakeadeeperlookintoviewsandviewresolvers.Afterfinishing
thischapter,youwillhaveaclearideaaboutthefollowingtopics:
Viewsandresolvingviews
Staticviews
Themultipartviewresolver
Contentnegotiation
Thehandlerexceptionresolver
Resolvingviews
Aswealreadymentioned,SpringMVCdoesnotmakeanyassumptionaboutanyspecific
viewtechnology.AccordingtoSpringMVC,aviewisidentifiableasanimplementation
oftheorg.springframework.web.servlet.Viewinterface,shownasfollows:
publicinterfaceView{
StringgetContentType();
voidrender(Map<String,?>model,HttpServletRequestrequest,
HttpServletResponseresponse)throwsException;
}
TherendermethodfromtheSpringMVCViewinterfacedefinesthemainresponsibility
ofaviewobject.Theresponsibilityisthatitshouldrenderofpropercontentasaresponse
(javax.servlet.http.HttpServletResponse)basedonModelandrequest
(javax.servlet.http.HttpServletRequest).
BecauseofthesimplicityofSpringMVC’sViewinterface,wecanwriteourownview
implementationifwewant.However,SpringMVCprovidesmanyconvenientview
implementationsthatarereadyforusebysimplyconfiguringitinourwebapplication’s
contextconfigurationfile.
OnesuchviewisInternalResourceView
(org.springframework.web.servlet.view.InternalResourceView),whichrendersthe
responseasaJSPpage.Similarly,thereareotherviewimplementationssuchas
RedirectView,TilesView,FreeMarkerView,andVelocityView,whichareavailablefor
specificviewtechnologies.SpringMVCdoesnotencouragecouplingtheviewobject
withthecontrollerasitwillleadthecontrollermethodtotightlycouplewithonespecific
viewtechnology.However,ifyouwanttodoso,youcandosomethingsimilartowhatis
showninthefollowingcodesnippet:
@RequestMapping("/home")
publicModelAndViewgreeting(Map<String,Object>model){
model.put("greeting","WelcometoWebStore!");
model.put("tagline","Theoneandonlyamazingwebstore");
Viewview=newInternalResourceView("/WEB-INF/views/welcome.jsp");
returnnewModelAndView(view,model);
}
Intheprecedingcodehandlermethod,wehaven’treturnedanylogicalviewname;rather,
wedirectlyinstantiatedInternalResourceViewoutofwelcome.jspandcomposeditinto
theModelAndView(org.springframework.web.servlet.ModelAndView)object.The
precedingexampleisnotencouragedsinceithastightlycoupledthegreetinghandler
methodwithInternalResourceView.Instead,whatwecandoisreturnalogicalview
nameandconfigureanappropriateviewresolverofourchoiceinourwebapplication’s
contexttocreateaviewobject.
Springcomeswithquiteafewviewresolverstoresolvevarioustypesofviews.We
alreadylearnedhowwecanconfigureInternalResourceViewResolverasourview
resolvertoresolveJSPviewsinChapter2,SpringMVCArchitecture–ArchitectingYour
WebStore,andwealsolearnedhowInternalResourceViewResolverresolvesa
particularlogicalviewnameintoaview(seetheViewresolverssection,inChapter2,
SpringMVCArchitecture–ArchitectingYourWebStore).Anyhow,Iwillrepeatitbriefly
here.
InternalResourceViewResolverwillresolvetheactualview’sfilepathbyprepending
theconfiguredprefixvalueandappendingthesuffixvaluewiththelogicalviewname;the
logicalviewnameisthevalueusuallyreturnedbythecontrollermethod.So,the
controllermethoddidn’treturnanyactualview;itjustreturnedtheviewname.Itisthe
jobofInternalResourceViewResolvertoformthecorrectURLpathofactualJSPview
fileforInternalResourceView.
Theredirectview
Inawebapplication,URLredirectionorforwardingarethetechniquestomovevisitorsto
adifferentwebpagethantheonetheyrequest.Mostofthetime,thistechniqueisused
aftersubmittingawebformtoavoidresubmissionofthesameformduetotheeventof
pressingthebrowser’sbackbuttonorrefreshbutton.SpringMVChasaspecialView
objectcalledRedirectviewtohandleredirectionandforwarding.TouseRedirectview
(org.springframework.web.servlet.view.Redirectview)withourcontroller,we
simplyneedtoreturnthetargetURLstringwiththeredirectionprefixfromthecontroller.
TherearetworedirectionprefixesavailableinSpringMVC,asshowninthefollowing
codesnippet:
returnredirect:/products/productDetail
And:
returnforward:/products/productDetail
Timeforaction–examiningRedirectView
Thoughbothredirectionandforwardingareusedtopresentadifferentwebpagethanthe
onerequested,thereisalittledifferencebetweenthem.Let’strytounderstandtheseby
examiningthem:
1. OpenourHomeControllerclassandaddonemorerequestmappingmethodas
follows:
@RequestMapping("/welcome/greeting")
publicStringgreeting(){
return"welcome";
}
2. Now,alterthereturnstatementoftheexistingwelcomerequestmappingmethod,
andsaveitasfollows:
return"forward:/welcome/greeting";
3. Now,runourapplicationandenterhttp://localhost:8080/webstore/.Youwillbe
abletoseeawelcomemessageonthewebpage.
4. Now,alterthereturnstatementoftheexistingwelcomerequestmappingmethod
againandsaveitasfollows:
return"redirect:/welcome/greeting";
5. Now,runourapplicationandenterhttp://localhost:8080/webstore/.Youwill
seeablankpagewithoutanywelcomemessage.
6. Finally,revertthereturnvalueofthewelcomemethodofHomeControllertothe
originalvalue,shownasfollows:
return"welcome";
Whatjusthappened?
So,whatwehavedemonstratedhereishowwecaninvoketheredirectviewfromthe
controllermethod.Instep1,wesimplycreatedarequestmappingmethodcalled
greetingforthewelcome/greetingrequestpath.Thismethodsimplyreturnsalogical
viewnameaswelcome.
Sincewereturnedthelogicalviewnameaswelcome,thewelcome.jspfilewillbe
renderedbyInternalResourceViewatruntime.Thewelcome.jspfileexpectstwomodel
attributes,namelygreetingandtagline,duringrendering.Instep2,wealteredthe
returnstatementoftheexitingrequestmappingmethodtoreturnaredirectedURL,as
follows:
@RequestMapping("/")
publicStringwelcome(Modelmodel){
model.addAttribute("greeting","WelcometoWebStore!");
model.addAttribute("tagline","Theoneandonlyamazingwebstore");
return"forward:/welcome/greeting";
}
Whatwehavedoneinstep2ismoreimportant;insteadofreturningalogicalviewname,
wesimplyreturntherequestpathvalueofthegreetinghandlermethodwiththe
forward:keywordprefixed.
ThemomentSpringMVCseesthis,itcanunderstandthatitisnotaregularlogicalview
name,soitwon’tsearchforanyviewfileunderthesrc/main/webapp/WEB-INF/views/
directory;rather,itwillconsiderthisrequestforittobeforwardedtoanotherrequest
mappingmethodbasedontherequestpathattachedaftertheforward:keyword.
Oneimportantthingtorememberhereisthattheforwardedrequestisstilltheactive
originalrequest,sowhatevervaluewehaveputinthemodelatthestartoftherequest
wouldstillbeavailable.ThisiswhywedidnotaddanyvaluetoModelinsidethe
greetingmethod.Wesimplyreturntheviewnameaswelcomeandthewelcome.jspfile
ontheassumptionthattherewillbemodelattributes,namelygreetingandtagline,
availableinthemodel.So,whenwefinallyrunourapplication,asmentionedinstep3,
eventhoughweissuedtherequesttotheURLhttp://localhost:8080/webstore/,the
RedirectViewwillforwardourrequestto
http://localhost:8080/webstore/welcome/greeting,andwewillabletoseethe
welcomemessageonthewebpage.
Againinstep4,wesimplychangedthereturnstatementofthewelcomemethodwiththe
redirect:prefix.Thistime,Springwillconsiderthisrequestasanewrequest,so
whatevervaluewehaveputinthemodel(insidethewelcomemethod)atthestartofthe
originalrequestwouldhavebeengone.Thisiswhyyousawanemptywelcomepagein
step6,sincethewelcome.jsppagecan’treadthegreetingandtaglinemodelattributes
fromthemodel.
So,basedonthisexercise,weunderstandthatRedirectViewwillgetintothepictureifwe
returnaredirectedURLwiththeappropriateprefixfromthecontrollermethod.
RedirectViewwillkeeptheoriginalrequestorspawnanewrequestbasedonredirection
orforwarding.
Popquiz–redirectview
Considerthefollowingcustomercontroller:
@Controller("/customers")
publicclassCustomerController{
@RequestMapping("/list")
publicStringlist(Modelmodel){
return"customers";
}
@RequestMapping("/process")
publicStringprocess(Modelmodel){
//return
}
}
Q1.IfIwanttogetredirectedtothelistmethodfromprocess,howshouldIformthe
returnstatementwithintheprocessmethod?
1. return"redirect:list";.
2. return"redirect:/list";.
3. return"redirect:customers/list";.
4. return"redirect:/customers/list";.
Servingstaticresources
Sofar,wehaveseenthateveryrequestgoesthroughthecontrollerandreturnsa
correspondingviewfilefortherequest;mostofthetime,theseviewfilescontaindynamic
content.Bydynamiccontent,Imeanthemodelvaluesthataredynamicallypopulatedin
theviewfileduringtherequestprocessing.Forexample,iftheviewfileisoftheJSPtype,
thenwepopulatemodelvaluesintheJSPfileusingtheJSPexpressionnotation,${}.
However,whatifwehavesomestaticcontentthatwewanttoservetotheclient?For
example,consideranimagethatisstaticcontent;wedon’twanttogothroughcontrollers
inordertoserve(fetch)animageasthereisnothingtoprocessorupdateanyvaluesinthe
model.Wesimplyneedtoreturntherequestedimage.
Let’ssaywehaveadirectory(/resources/images/)thatcontainssomeproductimages,
andwewanttoservetheseimagesuponrequest.Forexample,iftherequestedURLis
http://localhost:8080/webstore/resource/images/P1234.png,thenwewouldliketo
servetheimagewiththeP1234.pngname.Similarly,iftherequestedURLis
http://localhost:8080/webstore/resource/images/P1236.png,thenanimagewith
thenameP1236.pngneedstobeserved.
Timeforaction–servingstaticresources
Let’sseehowwecanservestaticimageswithSpringMVC:
1. Placesomeimagesunderthesrc/main/webapp/resources/images/directory;I
haveusedthreeproductimages,namelyP1234.png,P1235.png,andP1236.png.
2. Addthefollowingtaginourwebapplicationcontext’sconfiguration
DispatcherServlet-context.xmlfile:
<mvc:resourceslocation="/resources/"mapping="/resource/**"/>
3. Now,runourapplicationandenter
http://localhost:8080/webstore/resource/images/P1234.png(changethe
imagenameintheURLbasedontheimagesyouplacedinstep1).
4. Youarenowabletoviewtheimageyourequestedinthebrowser.
Whatjusthappened?
Whatjusthappenedwassimple;instep1,weplacedsomeimagefilesunderthe
src/main/webapp/resources/images/directory.Instep2,wejustaddedthe
<mvc:resources>taginthewebapplicationcontextconfigurationtotellSpringwhere
theseimagefilesarelocatedinourprojectsothatspringcanservethosefilesupon
request.Considerthefollowingcodesnippet:
<mvc:resourceslocation="/resources/"mapping="/resource/**"/>
Thelocationattributeofthe<mvc:resources>tagdefinesthebasedirectorylocationof
staticresourcesthatyouwanttoserve.Inourcase,wewanttoserveallimagesthatare
availableunderthesrc/main/webapp/resources/images/directory;youmaywonder
whywehavegivenonly/resources/asthelocationvalueinsteadof
src/main/webapp/resources/images/.Thisisbecauseweconsidertheresources
directoryasthebasedirectoryforallresources,wecanhavemultiplesubdirectoriesunder
resourcesdirectorytoputourimagesandotherstaticresourcefiles
Thesecondattribute,mapping,justindicatestherequestpaththatneedstobemappedto
thisresourcedirectory.Inourcase,wehaveassigned/resources/**asthemapping
value.So,ifanywebrequeststartswiththe/resourcerequestpath,thenitwillbe
mappedtotheresourcesdirectory,andthe/**symbolindicatestherecursivelookfor
anyresourcefilesunderneaththebaseresourcedirectory.
Thisiswhy,ifyounoticeinstep3,weformedtheURLas
http://localhost:8080/webstore/resource/images/P1234.png.So,whileservingthis
webrequest,SpringMVCwillconsider/resource/images/P1234.pngastherequest
path.So,itwilltrytomap/resourcetotheresourcebasedirectory,resources.Fromthis
directory,itwilltrytolookfortheremainingpathoftheURL,whichis
/images/P1234.png.Sincewehavetheimagesdirectoryundertheresourcesdirectory,
Springcaneasilylocatetheimagefilefromtheimagesdirectory.
Asamatteroffact,behindthescreen,SpringMVCuses
org.springframework.web.servlet.resource.ResourceHttpRequestHandlertoserve
theresourcesthatareconfiguredbythe<mvc:resources>tag.So,inourapplication,if
anyrequestcomeswiththerequestpath’s/resourceprefixinitsURL,thenSpringwill
lookintothelocationdirectorythatisconfiguredinthe<mvc:resources>tagandwill
returntherequestedfiletothebrowser.Remember,Springallowsyoutohostnotonly
images,butalsoanytypeofstaticfiles,suchasPDFs,Worddocuments,Excelsheets,and
sooninthisfashion.
Itisgoodthatweareabletoserveproductimageswithoutaddinganyextrarequest
mappingmethodsinthecontroller.
Popquiz–staticview
Considerthefollowingresourceconfiguration:
<mvc:resourceslocation="/pdf/"mapping="/resources/**"/>
Q1.Underthepdfdirectory,ifIhaveasubdirectorysuchasproduct/manuals/,which
containsa.pdffilecalledmanual-P1234.pdf,howcanIformtherequestpathtoaccess
that.pdffile?
1. /pdf/product/manuals/manual-P1234.pdf.
2. /resources/pdf/product/manuals/manual-P1234.pdf.
3. /product/manuals/manual-P1234.pdf.
4. /resource/pdf/product/manuals/manual-P1234.pdf.
Timeforaction–addingimagestothe
productdetailpage
Let’sextendthistechniquetoshowproductimagesinourproductlistingpageandinthe
productdetailpage.Performthefollowingsteps:
1. Openproducts.jsp;youcanfindproducts.jspunderthe/src/main/webapp/WEB-
INF/views/directoryinyourproject.Now,addthefollowing<img>tagafterthe
<divclass="thumbnail">tag:
<imgsrc="<c:urlvalue="/resource/images/${product.productId}.png">
</c:url>"alt="image"style="width:100%"/>
2. Similarly,openproduct.jspandaddthefollowing<img>tagafterthe<div
class="row">tag:
<divclass="col-md-5">
<imgsrc="<c:urlvalue="/resource/images/${product.productId}.png">
</c:url>"alt="image"style="width:100%"/>
</div>
3. Now,runourapplicationandenterhttp://localhost:8080/webstore/products.
Youwillbeabletoseetheproductlistpagewitheveryproductthathasaproduct
image,asshowninthefollowingfigure:
Productlistingswiththeimageattached
4. Now,clickontheDetailsbuttonofanyproduct,andyouwillbeabletoseethe
correspondingviewoftheproductdetailswiththeimageattachedtothedetailspage,
asfollows:
Theproductdetailpagewiththeimageattached
Whatjusthappened?
Whatwehavedoneissimple.Welearnedhowwecanservestaticresourcesandhowwe
canhostproductimages.Duringthisexercise,welearnedthatinourapplication,ifany
requestcomeswiththerequestpath’s/resourceprefix,itwouldgetmappedtothebase
resourcedirectory,andanyremainingURLpathwouldleadtothestaticfile.
Weleveragedthisfact,andformedtheimage’ssrcURLaccordingly;noticethesrc
attributeofthe<img>tagweaddedinstep1:
<imgsrc="<c:urlvalue="/resource/images/${product.productId}.png">
</c:url>"alt="image"style="width:100%"/>
Thesrcattributevaluethatweareforminginthepreceding<img>taghasanexpression
languagenotationtofetchtheproductID;aftergettingtheproductID,wesimply
concatenateittotheexistingvaluetoformavalidrequestpath,shownasfollows:
/resource/images/${product.productId}.png
Forexample,iftheproductIDisP1234,thenwewouldgetanimagerequestURLas
/resource/images/P1234.png,whichisnothingbutoneoftheimagefilenamesthatwe
havealreadyputupinthe/resources/imagesdirectory.So,Springcaneasilyreturnthe
imagefilethatweshowedusingthe<img>taginsteps1and2.
Themultipartrequestinaction
Intheprecedingexercise,welearnedhowwecanincorporatethestaticviewtoshow
productimagesintheproducts’detailspage.Wesimplyputsomeimagesinadirectoryin
theserverandperformedaconfiguration,andSpringMVCwasabletopickupthesefiles
duringtherenderingofthepagethathadtheproductdetails.Whatifweautomatethis
process?Imeaninsteadofputtingtheseimages,whatifweareabletouploadimagesto
theimagedirectory?
Howcanwedothis?Therecomesthemultipartrequest.Themultipartrequestisatypeof
HTTPrequestthatsendsthefileanddatatotheserver.SpringMVChasgoodsupportfor
amultipartrequest.Let’ssaywewanttouploadsomefilestotheserver.Toaccomplish
this,wewillhavetoformamultipartrequest.
Timeforaction–addingimagestothe
productpage
Let’saddtheimageuploadfacilitytoouraddproductspage:
1. Addabeandefinitioninourwebapplication’scontextconfigurationfile
(DispatcherServlet-context.xml)forCommonsMultipartResolver,asfollows:
<beanid="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolv
er">
<propertyname="maxUploadSize"value="10240000"/>
</bean>
2. Openpom.xml;youcanfindpom.xmlundertheprojectrootdirectoryitself.
3. Youwillbeabletoseesometabsatthebottomofthepom.xmlfile.Selectthe
DependenciestabandclickontheAddbuttonoftheDependenciessection.
4. ASelectDependencywindowwillappear;enterGroupIdascommons-fileupload,
ArtifactIdascommons-fileupload,Versionas1.2.2;selectScopeascompile;and
clickontheOKbutton.
5. Similarly,addonemoreGroupIddependencyasorg.apache.commons,ArtifactId
ascommons-io,Versionas1.3.2;selectScopeascompile;clickontheOKbutton;
andsavethepom.xmlfile.
6. Openourproduct’sdomainclass(Product.java)andaddareferenceto
org.springframework.web.multipart.MultipartFilewiththecorresponding
settersandgettersasfollows:
privateMultipartFileproductImage;
7. OpenaddProduct.jsp;youcanfindaddProduct.jspunderthe
/src/main/webapp/WEB-INF/views/directoryinyourproject.Addthefollowingset
oftagsafterthe<form:inputid="condition">taggroup:
<divclass="form-group">
<labelclass="control-labelcol-lg-2"for="productImage">
<spring:messagecode="addProdcut.form.productImage.label"/></label>
<divclass="col-lg-10">
<form:inputid="productImage"path="productImage"type="file"
class="form:input-large"/>
</div>
</div>
8. Addanentryinourmessagebundlesource(messages.properties)fortheproduct’s
imagelabel,asfollows:
addProdcut.form.productImage.label=ProductImagefile
9. Now,settheenctypeattributetomultipart/form-dataintheformtagasfollows
andsaveaddProduct.jsp:
<form:formmodelAttribute="newProduct"class="form-
horizontal"enctype="multipart/form-data">
10. OpenourProductController.javafileandmodifytheprocessAddNewProductForm
method’ssignaturebyaddinganextramethodparameteroftheHttpServletRequest
type(javax.servlet.http.HttpServletRequest);sobasically,your
processAddNewProductFormmethodsignatureshouldlooklikethefollowingcode
snippet:
publicStringprocessAddNewProductForm(@ModelAttribute("newProduct")
ProductnewProduct,BindingResultresult,HttpServletRequestrequest){
11. AddthefollowingcodesnippetinsidetheprocessAddNewProductFormmethodjust
beforeproductService.addProduct(newProduct);:
MultipartFileproductImage=productToBeAdded.getProductImage();
StringrootDirectory
=request.getSession().getServletContext().getRealPath("/");
if(productImage!=null&&!productImage.isEmpty()){
try{
productImage.transferTo(newFile(rootDirectory+"resources\\images\\"+pro
ductToBeAdded.getProductId()+".png"));
}catch(Exceptione){
thrownewRuntimeException("ProductImagesavingfailed",e);
}
}
12. WithintheinitialiseBindermethod,addtheproductImagefieldtothewhitelisting
setasfollows:
binder.setAllowedFields("productId","name","unitPrice","description","m
anufacturer","category","unitsInStock","productImage");
13. Now,runourapplicationandentertheURL
http://localhost:8080/webstore/products/add.Youwillbeabletoseeouradd
productspagewithanextrainputfieldtochooseafiletoupload.Justfillevery
informationasusualandmoreimportantly,chooseanimagefileofyourchoiceto
addanewimagefile,andclickontheAddbutton.Youwillthenbeabletoseethat
theimagehasbeenaddedtotheproductspageandtheproductdetailspage,asshown
inthefollowingscreenshot:
Addproductpagewithimageselectionoption
Whatjusthappened?
Spring’sCommonsMultipartResolver
(org.springframework.web.multipart.commons.CommonsMultipartResolver)class
determineswhetherthegivenrequestcontainsmultipartcontentornotandparsesthe
givenHTTPrequestintomultipartfilesandparameters.Thisiswhyweinitiatedthisclass
withinourservletcontextinstep1.ThroughthemaxUploadSizeproperty,wehaveseta
maximumof10240000bytesastheallowedfilesizetobeuploaded:
<bean
id="multipartResolver"class="org.springframework.web.multipart.commons.Comm
onsMultipartResolver">
<propertyname="maxUploadSize"value="10240000"/>
</bean>
Fromsteps2to5,weaddedsomeoftheorg.apache.commonslibrariesasourmaven
dependency.ThisisbecauseSpringusestheselibrariesinternallytosupportthefile
uploadingfeature.
Sincetheimagethatwewereuploadingbelongstoaproduct,itisbettertokeepthat
imageaspartoftheproductinformation;thisiswhyinstep6,weaddedareferenceto
MultipartFileinourdomainclass(Product.java)andaddedcorrespondingsettersand
getters.ThisMultipartFilereferenceholdstheactualproductimagefilethatwewere
uploading.
Wewanttoincorporatetheimageuploadingfacilityinouraddproductspage;thisiswhy,
intheaddProduct.jspviewfile,weaddedafileinputtagtochoosethedesiredimage,
shownasfollows:
<divclass="form-group">
<labelclass="control-labelcol-lg-2"for="productImage"><spring:message
code="addProdcut.form.productImage.label"/>
</label>
<divclass="col-lg-10">
<form:inputid="productImage"path="productImage"type="file"
class="form:input-large"/>
</div>
</div>
Intheprecedingsetoftag,theimportantoneisthe<form:input>tag.Ithasthetype
attributeasfilesothatitcanhavetheChooseFilebuttontodisplaythefilechooser
window.Asusual,wewantthisformfieldtobeboundwiththedomainobjectfield;thisis
whywehavesetthepathattributeasproductImage.Ifyouremembercorrectly,this
pathnameisnothingbutthesameMultipartFilereferencenamethatweaddedinstep6.
Asusual,wewanttoexternalizethelabelmessageforthisfileinputtagaswell,andthat’s
whyweaddedthe<spring:message>tag,andinstep8,weaddedthecorresponding
messageentryinthemessagesourcefile(messages.properties).
Sinceouraddproductformisnowcapableofsendingimagefilesaswellaspartofthe
request,weneedtoencodetherequestasamultipartrequest.Thisiswhy,instep9,we
addedtheenctypeattributetothe<form:form>tagandsetitsvalueasmultipart/form-
data.Theenctypeattributeindicateshowtheformdatashouldbeencodedwhenweare
submittingittotheserver.
Wewantedtosavetheimagefileintheserverunderthelocation’sresources/images
directory;thisdirectorystructurewouldbeavailabledirectlyundertherootdirectoryof
ourwebapplicationatruntime.So,inordertogettherootdirectoryofourweb
application,weneedHttpServletRequest.Seethefollowingcodesnippet:
StringrootDirectory=
request.getSession().getServletContext().getRealPath("/");
Thisiswhyweaddedanextramethodparametercalledrequestofthe
HttpServletRequesttypetoourprocessAddNewProductFormmethodinstep10.
Remember,SpringwillfillthisrequestparameterwiththeactualHTTPrequest.
Instep11,wesimplyreadtheimagefilefromthedomainobjectandwroteitintoanew
filewiththeproductIDasthename,asshowninthefollowingcodesnippet:
MultipartFileproductImage=productToBeAdded.getProductImage();
StringrootDirectory=
request.getSession().getServletContext().getRealPath("/");
if(productImage!=null&&!productImage.isEmpty()){
try{
productImage.transferTo(new
File(rootDirectory+"resources\\images\\"+
productToBeAdded.getProductId()+".png"));
}catch(Exceptione){
thrownewRuntimeException("ProductImagesavingfailed",e);
}
}
Remember,wepurposelysavetheimageswiththeproductIDnamebecausewehave
alreadydesignedourproducts(products.jsp)pageanddetail(product.jsp)page
accordinglyinordertodisplaytherightimagebasedontheproductID.
Asafinalstep,weaddedthenewlyintroducedproductImagefiletothewhitelistingsetin
thebinderconfigurationwithintheinitialiseBindermethod.
Now,ifyourunourapplicationandenter
http://localhost:8080/webstore/products/add,youwillbeabletoseeouradd
productspagewithanextrainputfieldtochoosethefiletoupload.
Haveagohero–uploadingproductusermanuals
totheserver
Itisnicethatwewereabletouploadtheproductimagetotheserverwhileaddinganew
product.Whydon’tyouextendthisfacilitytouploadaPDFfiletoserver?Forexample,
considerthateveryproducthasausermanualandyouwanttouploadtheseusermanuals
aswellwhileaddingaproduct.
HerearesomeofthethingsyoucandotouploadPDFfiles:
Createadirectorywiththepdfnameunderthesrc/main/webapp/resources/
directoryinyourproject.
AddonemoreMultipartFilereferenceinyourproductdomainclass
(Product.java)toholdthePDFfileandchangeProduct.javaaccordingly.
ExtendaddProduct.jsp.
ExtendProductController.javaaccordingly;don’tforgettoaddthenewlyadded
fieldtothewhitelist.
Sofinally,youwillbeabletoaccessthePDFunder
http://localhost:8080/webstore/resource/pdf/P1237.pdfifthenewlyadded
productidisP1237.Goodluck!
UsingContentNegotiatingViewResolver
Contentnegotiationisamechanismthatmakesitpossibletoserveadifferent
representationofthesameresource.Forexample,sofarwehavedisplayedourproduct
detailpageinaJSPrepresentation.Whatifwewanttorepresentthesamecontentinan
XMLformat,andsimilarly,whatifwewantthesamecontentinaJSONformat?There
comesSpringMVC’sContentNegotiatingViewResolver
(org.springframework.web.servlet.view.ContentNegotiatingViewResolver)tohelp
us.TheXMLandJSONformatsarepopulardatainterchangeformatsthatareusedinweb
servicecommunicationsheavily.So,usingContentNegotiatingViewResolver,wecan
incorporatemanyviewssuchasMappingJacksonJsonView(forJSON)and
MarshallingView(forXML)torepresentthesameproductinformationasthe
XML/JSONformat.
Timeforaction–configuring
ContentNegotiatingViewResolver
ContentNegotiatingViewResolverdoesnotresolveviewsitselfbutdelegatesthemto
otherviewresolversbasedontherequest.Now,let’saddthecontentnegotiationcapability
toourapplication:
1. Openpom.xml;youcanfindpom.xmlundertheprojectrootdirectoryitself.
2. Youwillbeabletoseesometabsatthebottomofpom.xmlfile.Selectthe
DependenciestabandclickontheAddbuttonoftheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdas
org.springframework,ArtifactIdasspring-oxm,Versionas4.0.3.RELEASE;
selectScopeascompile;andthenclickontheOKbutton.
4. Similarly,addonemoredependencyGroupIdasorg.codehaus.jackson,Artifact
Idasjackson-mapper-asl,Versionas1.9.10,andselectScopeascompile.Then,
clickontheOKbuttonandsavepom.xml.
5. AddthebeanconfigurationforContentNegotiatingViewResolverinourweb
application’scontextconfigurationfile,DispatcherServlet-context.xml,as
follows:
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResol
ver">
<propertyname="defaultViews">
<list>
<refbean="jsonView"/>
<refbean="xmlView"/>
</list>
</property>
</bean>
6. Now,addthebeanconfigurationfortheJSONviewasfollows:
<beanid="jsonView"
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView
">
<propertyname="prettyPrint"value="true"/>
</bean>
7. Finally,addthebeanconfigurationfortheXMLviewasfollows:
<beanid="xmlView"
class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<beanclass="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<propertyname="classesToBeBound">
<list>
<value>com.packt.webstore.domain.Product</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
8. Openourproductdomainclass(Product.java),andaddthe@XmlRootElement
annotationatthetopoftheclass.
9. Similarly,addthe@XmlTransientannotationatthetopofthegetProductImage()
methodandaddanother@JsonIgnoreannotationontopoftheproudctImagefield.
10. Now,runourapplicationandenter
http://localhost:8080/webstore/products/product?id=P1234.Youwillnowbe
abletoviewthedetailpageoftheproductwiththeP1234ID.
11. NowchangetheURLwiththe.xmlextension
(http://localhost:8080/webstore/products/product.xml?id=P1234).Youwill
beabletoseethesamecontentintheXMLformat,asshowninthefollowing
screenshot:
TheproductdetailpagethatshowstheproductinformationintheXMLformat
12. Similarly,thistimechangetheURLwiththe.jsonextension
(http://localhost:8080/webstore/products/product.json?id=P1234).Youwill
beabletoseetheJSONrepresentationofthatcontentasshowninthefollowing
screenshot:
TheproductdetailpagethatshowstheproductinformationintheJSONformat
Whatjusthappened?
SincewewantanXMLrepresentationforourmodeldatatoconvertourmodelobjects
intoXML,weneedSpring’sobject/XMLmappingsupport.Thisiswhyweaddedthe
dependencyforspring-oxm.jarinsteps1to3.Thespring-oxmnotationwillhelpus
convertanXMLdocumenttoandfromaJavaobject.
Similarly,toconvertmodelobjectsintoJSON,SpringMVCwillusejackson-mapper-
asl.jar,soweneedthatJARinourprojectaswell.Instep4,wejustaddedthe
dependencyconfigurationforthatjar.
Ifyouremember,wehavealreadydefinedInternalResourceViewResolverinourweb
applicationcontextasourviewresolvertoresolveJSP-basedviews.However,thistime,
wewantaviewresolvertoresolveXMLandJSONviews.Thisiswhy,instep6and7,we
configuredMappingJacksonJsonView(forJSON)andMarshallingView(forXML)inour
webapplicationcontext.
AsIalreadymentioned,ContentNegotiatingViewResolverdoesnotresolveviewsitself.
Instead,itdelegatestootherviewsbasedontherequest,soweneedtointroduceother
viewstoContentNegotiatingViewResolver.Wedidthatinstep5throughthe
defaultViewspropertyinContentNegotiatingViewResolver.Notethatinthe
ContentNegotiatingViewResolverbeanconfiguration,wejustaddedthebeanreference
fortheJSONviewandXMLviewunderthedefaultViewsproperty:
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
>
<propertyname="defaultViews">
<list>
<refbean="jsonView"/>
<refbean="xmlView"/>
</list>
</property>
</bean>
WeconfiguredbeanreferencesforjsonViewandxmlViewinside
ContentNegotiatingViewResolver.
ThexmlViewbeanconfiguration,especially,hasoneimportantpropertycalled
classesToBeBound,whichliststhedomainobjectsthatneedsXMLconversionduringthe
requestprocessing.SinceourproductdomainobjectneedstheXMLconversion,weadded
com.packt.webstore.domain.ProductinthelistofclassesToBeBound,shownas
follows:
<beanid="xmlView"
class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<beanclass="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<propertyname="classesToBeBound">
<list>
<value>
com.packt.webstore.domain.Product
</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
InordertoconverttoXML,weneedtogiveonemorehinttoMarshallingViewto
identifytherootXMLelementintheProductdomainobject.Thisiswhy,instep8,we
annotatedourclasswiththe@XmlRootElementannotation
(javax.xml.bind.annotation.XmlRootElement).
Instep9,weaddedthe@XmlTransientannotation
(javax.xml.bind.annotation.XmlTransient)ontopofthegetProductImage()method
andaddedanotherannotation,@JsonIgnore
(org.codehaus.jackson.annotate.JsonIgnore),ontopoftheproductImagefield.This
isbecausewedon’twanttorepresenttheproductimageaspartoftheXMLvieworJSON
view.Sincebothformatsarepurelytext-basedrepresentation,itisnotpossibleto
representimagesintexts.
Instep10,wesimplyaccessedourproductdetailpageinaregularwaybyfiringtheweb
requesthttp://localhost:8080/webstore/products/product?id=P1234fromthe
browser,andwewillbeabletoseethenormalJSPview,asexpected.
Instep11,wejustchangedtheURLslightlybyaddinga.xmlextensiontothe
http://localhost:8080/webstore/products/product.xml?id=P1234requestpath.
Thistime,wewillbeabletoseethesameproductinformationintheXMLformat.
Similarly,fortheJSONview,wechangedtheextensionbyadding.jsontothe
http://localhost:8080/webstore/products/product.json?id=P1234path,andwe
willbeabletoseetheJSONrepresentationofthesameproductinformation.
Workingwiththehandlerexception
resolver
SpringMVCprovidesseveralapproachestoexceptionhandling.InSpring,oneofthe
mainexceptionhandlingconstructsistheHandlerExceptionResolverinterface
(org.springframework.web.servlet.HandlerExceptionResolver).Anyobjectsthat
implementthisinterfacecanresolveexceptionsthatarethrownduringcontrollermapping
orexecution.TheHandlerExceptionResolverimplementersaretypicallyregisteredas
beansinthewebapplicationcontext.
SpringMVCcreatestwosuchHandlerExceptionResolverimplementationsbydefaultto
facilitateexceptionhandling:
ResponseStatusExceptionResolveriscreatedtosupportthe@ResponseStatus
annotation
ExceptionHandlerExceptionResolveriscreatedtosupportthe@ExceptionHandler
annotation
Timeforaction–addingtheresponse
statusexception
First,wewilllookatthe@ResponseStatusannotation
(org.springframework.web.bind.annotation.ResponseStatus).InChapter3,Control
YourStorewithControllers,wecreatedarequestmappingmethodtodisplayproductsby
categoryundertheURItemplate,
http://localhost:8080/webstore/products/{category}.Ifnoproductswerefound
underthegivencategory,wewouldshowanemptywebpage,whichisnotcorrect
semantically.WeshouldshowanHTTPstatuserrortoindicatethatnoproductsexist
underthegivencategory.Let’sseehowwecandothatwiththehelpofthe
@ResponseStatusannotation:
1. CreateaclasscalledNoProductsFoundUnderCategoryExceptionunderthe
com.packt.webstore.exceptionpackageinthesourcefolder,src/main/java.Now
addthefollowingcodeintoit:
packagecom.packt.webstore.exception;
importorg.springframework.http.HttpStatus;
importorg.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NOT_FOUND,reason="Noproductsfound
underthiscategory")
publicclassNoProductsFoundUnderCategoryExceptionextends
RuntimeException{
privatestaticfinallongserialVersionUID=3935230281455340039L;
}
2. Now,openourProductControllerclassandmodifythegetProductsByCategory
methodasfollows:
@RequestMapping("/{category}")
publicStringgetProductsByCategory(Model
model,@PathVariable("category")Stringcategory){
List<Product>products
=productService.getProductsByCategory(category);
if(products==null||products.isEmpty()){
thrownewNoProductsFoundUnderCategoryException();
}
model.addAttribute("products",products);
return"products";
}
3. Nowrunourapplicationandenter
http://localhost:8080/webstore/products/HeadPhones.YouwillseeanHTTP
statuserrorthatsaysNoproductsfoundunderthiscategory,shownasfollows:
TheproductcategorypagethatshowsHTTPStatus404for“Noproductsfound
underthiscategory”
Whatjusthappened?
Instep1,wejustcreatedaruntimeexceptioncalled
NoProductsFoundUnderCategoryExceptiontoindicatenoproductsfoundunderthegiven
category.Oneoftheimportantconstructsthatneedtobenoticedinthe
NoProductsFoundUnderCategoryExceptionclassisthe@ResponseStatusannotation,
whichinstructstheSpringMVCtoreturnaspecificHTTPstatusifthisexceptionhas
beenthrownfromarequest-mappingmethod.
WecanconfiguretheHTTPstatusthatneedstobereturnedviathevalueattributeofthe
@ResponseStatusannotation;inourcase,weconfiguredHttpStatus.NOT_FOUND
(org.springframework.http.HttpStatus),whichindicatesthefamiliarHTTP404
response.Thesecondattribute,reason,denotesthereasontobeusedfortheHTTP
responseerror.
Instep2,wejustmodifiedthegetProductsByCategorymethodinthe
ProductControllerclasstocheckwhethertheproductlistforthegivencategoryis
empty.Ifso,wesimplythrowtheexceptionwecreatedinstep1,whichcausestheHTTP
404statuserrortoreturntotheclientsayingNoproductsfoundunderthiscategory.
Sofinally,instep3,wefiredthewebrequest,
http://localhost:8080/webstore/products/HeadPhones,whichwouldtrytolookfor
productsundertheHeadPhonescategory,butsincewedidn’thaveanyproductsunderthe
HeadPhonescategory,wegottheHTTP404statuserror.
ItisgoodthatwehaveshowntheHTTPstatuserrorforproductsnotfoundunderagiven
category,butsometimes,youmaywishtohaveanerrorpagewhereyouwanttoshow
yourerrormessageinadetailedmanner.
Forexample,runourapplicationandenterthe
http://localhost:8080/webstore/products/product?id=P1234URL.Youwillbeable
toseeadetailedviewofIphone5s;now,changetheproductIDintheURLtoaninvalid
onesuchashttp://localhost:8080/webstore/products/product?id=P1000.Youwill
seeanerrorpage.
Timeforaction–addinganexception
handler
Wemustshowaniceerrormessagethatsaysthatnoproductswerefoundwiththegiven
productID.Let’sdothatwiththehelpof@ExceptionHandler:
1. CreateaclasscalledProductNotFoundExceptionunderthe
com.packt.webstore.exceptionpackageinthesourcefoldersrc/main/java.Now,
addthefollowingcodetoit:
packagecom.packt.webstore.exception;
publicclassProductNotFoundExceptionextendsRuntimeException{
privatestaticfinallongserialVersionUID=-694354952032299587L;
privateStringproductId;
publicProductNotFoundException(StringproductId){
this.productId=productId;
}
publicStringgetProductId(){
returnproductId;
}
}
2. Now,openourInMemoryProductRepositoryclassandmodifythegetProductById
methodasfollows:
publicProductgetProductById(StringproductId){
ProductproductById=null;
for(Productproduct:listOfProducts){
if(product!=null&&product.getProductId()!=null
&&product.getProductId().equals(productId)){
productById=product;
break;
}
}
if(productById==null){
thrownewProductNotFoundException("Noproductsfoundwiththe
productid:"+productId);
}
returnproductById;
}
3. Addanexceptionhandlermethodwiththe@ExceptionHandlerannotation
(org.springframework.web.bind.annotation.ExceptionHandler)asshowninthe
ProductControllerclass:
@ExceptionHandler(ProductNotFoundException.class)
publicModelAndViewhandleError(HttpServletRequest
req,ProductNotFoundExceptionexception){
ModelAndViewmav=newModelAndView();
mav.addObject("invalidProductId",exception.getProductId());
mav.addObject("exception",exception);
mav.addObject("url",req.getRequestURL()+"?"+req.getQueryString());
mav.setViewName("productNotFound");
returnmav;
}
4. Finally,addonemoreJSPviewfilecalledproductNotFound.jsp,underthe
src/main/webapp/WEB-INF/views/directoryandaddthefollowingcodesnippetsto
itandsaveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/htmlcharset=ISO-8859-
1">
<link
rel="stylesheet"href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/boo
tstrap.min.css">
<title>Welcome</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1class="alertalert-danger">Thereisnoproductfound
withtheProductid${invalidProductId}</h1>
</div>
</div>
</section>
<section>
<divclass="container">
<p>${url}</p>
<p>${exception}</p>
</div>
<divclass="container">
<p>
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-
primary">
<spanclass="glyphicon-hand-leftglyphicon"></span>
products
</a>
</p>
</div>
</section>
</body>
</html>
5. Now,runourapplicationandenter
http://localhost:8080/webstore/products/product?id=P1000.Youwillseean
errorpagethatsaysThereisnoproductfoundwiththeProductidP1000,shown
asfollows:
Aproductdetailpagethatshowsthecustomerrorpagefortheinvalidproductid
P1000
Whatjusthappened?
Wethoughtofshowingacustom-madeerrorpageinsteadofarawexceptionincasethe
productisnotfoundinthegivenproductID.So,inordertoachievethisinstep1,wejust
createdaruntimeexceptioncalledProductNotFoundExceptiontobethrownwhenthe
producthasnotbeenfoundforthegivenproductID.
Instep2,wejustmodifiedthegetProductByIdmethodofthe
InMemoryProductRepositoryclasstocheckwhetheranyproductswerefoundforthe
givenproductID.Ifnot,wesimplythrowtheexception(ProductNotFoundException)we
createdinstep1.
Instep3,weaddedourexceptionhandlermethodtohandleProductNotFoundException
withthehelpofthe@ExceptionHandlerannotation.WithinthehandleErrormethod,we
justcreatedaModelAndViewobject(org.springframework.web.servlet.ModelAndView)
andstoredtherequestedinvalidproductID,exception,andrequestedURL,andreturnedit
withtheviewname,productNotFound:
@ExceptionHandler(ProductNotFoundException.class)
publicModelAndViewhandleError(HttpServletRequest
req,ProductNotFoundExceptionexception){
ModelAndViewmav=newModelAndView();
mav.addObject("invalidProductId",exception.getProductId());
mav.addObject("exception",exception);
mav.addObject("url",req.getRequestURL()+"?"+req.getQueryString());
mav.setViewName("productNotFound");
returnmav;
}
SincewereturnedtheModelAndViewobjectwiththeproductNotFoundviewname,we
musthaveaviewfilewiththeproductNotFoundname.Thisiswhywecreatedsucha
viewfile(productNotFound.jsp)instep4.TheproductNotFound.jspfilejustcontainsa
CSS-styled<h1>tagtoshowtheerrormessageandalinkbuttontotheproductlisting
page.
So,wheneverwerequestedtoshowaproductwithaninvalidIDsuchas
http://localhost:8080/webstore/products/product?id=P1000,the
ProductControllerclasswouldthrowProductNotFoundException,whichwillbe
handledbythehandleErrormethodtoshowthecustomerrorpage
(productNotFound.jsp).
Summary
Inthischapter,welearnedhowInternalResourceViewResolverresolvesviews,andwe
learnedhowwecankickRedirectViewfromacontrollermethod.Welearnedthe
importantdifferencebetweenredirectandforward.Afterthat,welearnedhowwecan
hoststaticresourcefileswithoutgoingthroughthecontrollers’configuration.Wealso
learnedhowtoattachastaticimagefilewiththeproductdetailspage.Welearnedhowwe
canuploadfilestoserver.Finally,wesawhowwecanconfigure
ContentNegotiatingViewResolvertogivealternateXMLandJSONviewsforthe
productdomainobjectinourapplication.Finally,welearnedhowwecanmakeuseof
HandlerExceptionResolvertoresolveanexception.
Inthenextchapter,wewilllearnhowwecaninterceptregularwebrequestswiththehelp
ofaninterceptor.Seeyouinnextchapter!
Chapter6.InterceptYourStorewith
Interceptor
Inallthepreviouschapters,wehaveonlylearnedhowtomaparequesttoacontroller
method.Oncetherequestreachesthecontrollermethod,weexecutesomelogicandreturn
alogicalviewnamethatcanbeusedbytheviewresolvertoresolveviews.However,what
ifwewanttoexecutesomelogicbeforetheactualrequestprocessisperformed?Similarly,
whatifwewanttoexecuteanotherinstructionbeforedispatchingtheresponse?
TheSpringMVCinterceptorcanbeusedtointercepttheactualrequestandresponse.
Interceptorsareaspecialwebprogrammingtechniquewhereonecanexecuteacertain
pieceoflogicbeforeorafterawebrequestisprocessed.Inthischapter,wearegoingto
learnmoreabouttheinterceptor.Wewilllearnthefollowing:
Howtoconfigureaninterceptor
Howtoaddinternalizationsupport
Dataauditingusinganinterceptor
Conditionalredirectingusinganinterceptor
Workingwithinterceptors
AsIalreadymentioned,interceptorsareusedtointerceptactualwebrequestsbeforeor
aftertheyareprocessed.Wecanrelatetheinterceptor’sconceptinSpringMVCwiththe
filterconceptofservletprogramming.InSpringMVC,interceptorsarethespecialclasses
thatmustimplementtheorg.springframework.web.servlet.HandlerInterceptor
interface.
TheHandlerInterceptorinterfacedefinesthreeimportantmethods,asfollows:
preHandle:Thismethodgetscalledjustbeforethewebrequestreachesthecontroller
methodtobeexecuted
postHandle:Thismethodwillgetcalledjustaftertheexecutionofthecontroller
method
afterCompletion:Thismethodwillgetcalledafterthecompletionoftheentireweb
requestcycle
OncewehavecreatedourowninterceptorbyimplementingtheHandlerInterceptor
interface,weneedtoconfigureitinourwebapplicationcontextforittotakeeffect.
Timeforaction–configuringan
interceptor
Everywebrequesttakesacertainamountoftimetogetprocessedintheserver.Inorderto
findouthowmuchtimeittakestoprocessawebrequest,weneedtocalculatethetime
differencebetweenthestarttimeandendtimeofthewebrequestprocess.Wecanachieve
thisusingtheinterceptorconcept.Let’sconfigureourowninterceptorinourprojecttolog
theexecutiontimeofeachwebrequestbyperformingthefollowingsteps:
1. Openpom.xml—youcanfindpom.xmlundertherootdirectoryoftheprojectitself.
2. Youwillbeabletoseesometabsatthebottomofthepom.xmlfile;selectthe
DependenciestabandclickontheAddbuttonoftheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdaslog4j,enterArtifact
Idaslog4j,enterVersionas1.2.12,selectScopeascompile,andclickontheOK
buttontosavepom.xml.
4. CreateaclassnamedPerformanceMonitorInterceptorunderthe
com.packt.webstore.interceptorpackageinthesourcefoldersrc/main/javaand
addthefollowingcodeintoit:
packagecom.packt.webstore.interceptor;
importjava.text.DateFormat;
importjava.text.SimpleDateFormat;
importjava.util.Calendar;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.apache.log4j.Logger;
importorg.springframework.util.StopWatch;
importorg.springframework.web.servlet.HandlerInterceptor;
importorg.springframework.web.servlet.ModelAndView;
publicclassPerformanceMonitorInterceptorimplements
HandlerInterceptor{
ThreadLocal<StopWatch>stopWatchLocal=newThreadLocal<StopWatch>();
Loggerlogger=Logger.getLogger(this.getClass());
publicbooleanpreHandle(HttpServletRequest
request,HttpServletResponseresponse,Objecthandler)throwsException{
StopWatchstopWatch=newStopWatch(handler.toString());
stopWatch.start(handler.toString());
stopWatchLocal.set(stopWatch);
logger.info("AccessingURLpath:"+getURLPath(request));
logger.info("Requestprocessingstartedon:"+getCurrentTime());
returntrue;
}
publicvoidpostHandle(HttpServletRequestarg0,HttpServletResponse
response,Objecthandler,ModelAndViewmodelAndView)throwsException{
logger.info("Requestprocessingendedon"+getCurrentTime());
}
publicvoidafterCompletion(HttpServletRequest
request,HttpServletResponseresponse,Objecthandler,Exception
exception)throwsException{
StopWatchstopWatch=stopWatchLocal.get();
stopWatch.stop();
logger.info("Totaltimetakenforprocessing:"
+stopWatch.getTotalTimeMillis()+"ms");
stopWatchLocal.set(null);
logger.info("=======================================================");
}
privateStringgetURLPath(HttpServletRequestrequest){
StringcurrentPath=request.getRequestURI();
StringqueryString=request.getQueryString();
queryString=queryString==null?"":"?"+queryString;
returncurrentPath+queryString;
}
privateStringgetCurrentTime(){
DateFormatformatter=newSimpleDateFormat("dd/MM/yyyy'at'
hh:mm:ss");
Calendarcalendar=Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
returnformatter.format(calendar.getTime());
}
}
5. Now,openthewebapplicationcontextconfigurationfileDispatcherServlet-
context.xmlfromsrc/main/webapp/WEB-INF/spring/webcontext/,andaddthe
followingelementinitandsavethefile:
<mvc:interceptors>
<bean
class="com.packt.webstore.interceptor.PerformanceMonitorInterceptor"/>
</mvc:interceptors>
6. Createapropertyfilenamedlog4j.propertiesunderthedirectory
src/main/resourcesandaddthefollowingcontenttoit.Then,savethefile:
#Rootloggeroption
log4j.rootLogger=INFO,file,stdout
#Directlogmessagestoalogfile
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=C:\\webstore\\webstore-performance.log
log4j.appender.file.MaxFileSize=1MB
log4j.appender.file.MaxBackupIndex=1
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-ddHH:mm:ss}
%-5p%c{1}:%L-%m%n
#Directlogmessagestostdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-ddHH:mm:ss}
%-5p%c{1}:%L-%m%n
7. Now,runtheapplicationandenterhttp://localhost:8080/webstore/products.
Youwillbeabletoseetheperformanceloggingintheconsoleasfollows:
ThePerformanceMonitorInterceptorloggingmessageisshownintheconsole
8. JustopenC:\webstore-performance.log;youwillseethesamelogmessageinthe
loggingfileaswell.
Whatjusthappened?
Ourintentionwastorecordtheexecutiontimeofeveryrequestthatisbeingreceivedby
ourwebapplication;wedecidedtorecordtheexecutiontimeinalogfile.So,inorderto
usealogger,weneedthelog4jlibrary;weaddedthelog4jlibraryasamaven
dependencyinstep3.
Instep4,wejustdefinedaninterceptorclassnamedPerformanceMonitorInterceptor
byimplementingtheHandlerInterceptorinterface.Asmentionedpreviously,thereare
threemethodsthatneedtobeimplemented.Wewillseeeachmethodonebyone.Thefirst
methodispreHandle(),whichiscalledbeforetheexecutionofthecontrollermethod:
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponse
response,Objecthandler)throwsException{
StopWatchstopWatch=newStopWatch(handler.toString());
stopWatch.start(handler.toString());
stopWatchLocal.set(stopWatch);
logger.info("AccessingURLpath:"+getURLPath(request));
logger.info("Requestprocessingstartedon:"+getCurrentTime());
returntrue;
}
IntheprecedingpreHandlemethod,wejustinitiatedaStopWatchclasstostartrecording
thetime.Inthenextstep,weputthatstopWatchinstanceinaThreadLocalvariablecalled
thestopWatchLocalforthepurposeofretrievallateron.
Note
JavaprovidesaThreadLocalclassthatwecanset/getthreadscopedvariables.Thevalues
storedinThreadLocalarelocaltothethread,whichmeansthateachthreadwillhaveits
ownThreadLocalvariable.Onethreadcannotaccess/modifytheThreadLocalvariables
ofotherthreads.SinceSpringMVCisbasedontheservletprogrammingmodel,eachweb
requestisanindividualthread.
Finally,wejustloggedtherequestedURLpathandcurrentservertimewiththehelpof
logger.Therefore,wheneverarequestcomestoourwebapplication,itisfirstreceived
throughthispreHandlemethodandinitiatesstopWatchbeforereachingthecontroller
method.
ThesecondmethodispostHandle,whichwillgetcalledaftertheexecutionofthe
controllermethod:
publicvoidpostHandle(HttpServletRequestarg0,HttpServletResponse
response,Objecthandler,ModelAndViewmodelAndView)throwsException{
logger.info("Requestprocessingendedon"+getCurrentTime());
}
Intheprecedingmethod,wesimplylogthecurrenttime,whichisconsideredtherequest
processingfinishedtime.OurfinalmethodisafterCompletion,whichiscalledafterthe
completerequesthasbeenprocessed:
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponse
response,Objecthandler,Exceptionexception)throwsException{
StopWatchstopWatch=stopWatchLocal.get();
stopWatch.stop();
logger.info("Totaltimetakenforprocessing:"
+stopWatch.getTotalTimeMillis()+"ms");
stopWatchLocal.set(null);
logger.info("=======================================================");
}
IntheafterCompletionmethod,weretrievedthestopwatchinstancefromThreadLocal
andimmediatelystoppedit;now,thestopwatchinstancewillhavearecordofthetotal
timethatwastakenbetweenthepreHandleandafterCompletionmethods,whichis
consideredasthetotaltimetakentocompletearequest.Wesimplyloggedthisdurationin
millisecondsandremovedstopwatchfromThreadLocal.
Tip
Ifyoudon’twanttoimplementallthemethodsfromtheHandlerInterceptorinterfacein
yourinterceptorclass,youmayconsiderextendingyourinterceptorfrom
org.springframework.web.servlet.handler.HandlerInterceptorAdapter.Thisisa
convenientclassprovidedbySpringMVCasadefaultimplementationofallofthe
methodsfromtheHandlerInterceptorinterface.
AftercreatingPerformanceMonitorInterceptor,weneedtoregisterourinterceptorwith
SpringMVC,whichiswhatwedidinstep5throughSpring’sspecialinterceptor
configurationelement:
<mvc:interceptors>
<bean
class="com.packt.webstore.interceptor.PerformanceMonitorInterceptor"/>
</mvc:interceptors>
Instep6,weaddedalog4j.propertiesfileinordertospecifysomeofthelogger-related
configuration.Youcanseethatweconfiguredthelogfilelocationinlog4j.propertiesas
follows:
log4j.appender.file.File=C:\\webstore\\webstore-performance.log
Finally,instep7,weranourapplicationinordertorecordsomeoftheperformance
logging,andwewereabletoseethattheloggerisworkingjustfineviatheconsole.You
canopenthelogfiletoviewtheperformancelogs.
So,weunderstoodhowtoconfigureaninterceptorandhaveseen
PerformanceMonitorInterceptorinaction.Inthenextexercise,wewilllearnhowtouse
someofSpring’spreconfiguredinterceptors.
Popquiz–interceptor
Considerthefollowinginterceptor:
publicclassSecurityInterceptorextendsHandlerInterceptorAdapter{
@Override
publicvoidafterCompletion(HttpServletRequest
request,HttpServletResponseresponse,Objecthandler,Exceptionex)throws
Exception{
//justsomecoderelatedtoaftercompletion
}
}
Q1.IsthementionedSecurityInterceptorclassavalidinterceptor?
1. ItisnotvalidbecauseitdoesnotimplementtheHandlerInterceptorinterface.
2. ItisvalidbecauseitextendstheHandlerInterceptorAdapterclass.
Q2.Whatistheorderofexecutionwithintheinterceptormethods?
1. preHandle,afterCompletion,postHandle.
2. preHandle,postHandle,afterCompletion.
Internationalization(i18n)
Internationalizationmeansadaptingcomputersoftwaretodifferentlanguagesandregional
differences.Forexample,ifyouaredevelopingawebapplicationforaDutch-based
company,theymayexpectallthewebpagetexttobedisplayedintheDutchlanguage,
usetheEuroforcurrencycalculations,expectaspaceasathousandseparatorwhen
displayingnumbers,and“,”(comma)asadecimalpoint.Ontheotherhand,whenthe
sameDutchcompanywantstoopenamarketinAmerica,theyexpectthesameweb
applicationtobeadaptedforAmericanlocales;forexample,thewebpagesshouldbe
displayedinEnglish,dollarsshouldbeusedforcurrencycalculations,numbersshouldbe
formattedwith“,”(comma)asathousandseparator,and“.”(dot)shouldbeusedasa
decimalpoint,andsoon.
Thetechniqueofdesigningawebapplicationthatcanautomaticallyadapttodifferent
regionsandcountrieswithoutneedingtobereengineerediscalledinternationalization,
sometimesshortenedtoi18N(I-eighteenletters-N).
InSpringMVC,wecanachieveinternationalizationthroughLocaleChangeInterceptor
(org.springframework.web.servlet.i18n.LocaleChangeInterceptor).The
LocaleChangeInterceptorallowsustochangethecurrentlocaleoneverywebrequest
viaaconfigurablerequestparameter.InChapter4,WorkingwithSpringTagLibraries,we
learnedhowtoexternalizetextmessagesintheaddproductspage.Now,wearegoingto
addinternationalizationsupportforthesameaddproductspage(addProducts.jsp)
becauseinSpringMVC,priortointernationalizingalabel,wemustexternalizethatlabel
first.Sincewealreadyexternalizedallthelabelmessagesintheaddproductspage
(addProducts.jsp),weshallproceedtointernationalizetheaddproductspage.
Timeforaction–adding
internationalization
Technically,wecanaddasmuchlanguagesupportaswewantforinternationalization,but
fordemonstrationpurposes,Iamgoingtoshowyouhowtomakeanaddproductpagefor
Dutchlanguagesupport.Performthefollowingsteps:
1. Createafilecalledmessages_nl.propertiesunder/src/main/resourcesinyour
project,addthefollowinglinesinit,andsavethefile:
addProduct.form.productId.label=NieuwproductID
addProduct.form.name.label=Naam
addProduct.form.unitPrice.label=Prijsunit
addProduct.form.description.label=Beschrijving
addProduct.form.manufacturer.label=Manufacturer
addProduct.form.category.label=Fabrikant
addProduct.form.unitsInStock.label=Aantalopvoorraad
addProduct.form.condition.label=ProductStaat
addProduct.form.productImage.label=Productimage
2. OpentheaddProduct.jsppageandaddthefollowingsetoftagsrightafterthe
logoutlink:
<divclass="pull-right"style="padding-right:50px">
<ahref="?language=en">English</a>|<ahref="?language=nl">Dutch</a>
</div>
3. Now,openthewebapplicationcontextconfigurationfileDispatcherServlet-
context.xmlfromsrc/main/webapp/WEB-INF/spring/webcontext/andaddone
morebeandefinitionforthelocaleresolverasfollows:
<beanid="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<propertyname="defaultLocale"value="en"/>
</bean>
4. Now,configureonemoreinterceptorinthewebapplicationcontextconfiguration;
thatmeans,addonemoreinterceptorbeanentryintheexisting<mvc:interceptors>
elementforLocaleChangeInterceptorasfollows:
<mvc:interceptors>
<beanclass=
"com.packt.webstore.interceptor.PerformanceMonitorInterceptor"/>
<beanclass=
"org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<propertyname="paramName"value="language"/>
</bean>
</mvc:interceptors>
5. Now,runtheapplicationandenter
http://localhost:8080/webstore/products/add.Youwillbeabletoseethe
regularAddproductspagewithtwoextralinksinthetop-rightcornerwhereyou
canchoosethelanguage:
Theaddproductpagedisplayinginternationalizationsupporttochooselanguages
6. Now,clickontheDutchlink.YouwillseethattheproductIDlabelhastransformed
intotheDutchcaptionNieuwproductID.
7. SincetheconfiguredLocaleChangeInterceptorwilladdarequestparametercalled
languagetothewebrequest,youneedtoaddthislanguagerequestparametertothe
whitelistingsetinyourProductControllerpage.OpentheProductController
page,andwithintheinitialiseBindermethod,addthelanguagerequestparameter
tothewhitelistingsetasfollows:
binder.setAllowedFields("productId","name","unitPrice","description","m
anufacturer","category","unitsInStock","productImage","language");
Whatjusthappened?
Instep1,wejustcreatedapropertyfilecalledmessages_nl.properties.Thisfileactsas
aDutch-basedmessagesourceforallourexternalizedlabelmessagesinthe
addProducts.jspfile.Inordertodisplaytheexternalizedlabelmessages,weusedthe
<spring:message>tagintheaddProducts.jspfile.
However,bydefault,the<spring:message>tagwillreadthemessagesfromthe
messages.propertiesfileonly,butweneedtomakeaprovisionforourenduserto
switchtotheDutchlocalewhentheyviewthewebpagesothatthelabelmessagescan
comefromthemessages_nl.propertiesfile.Weprovidedthiskindofaprovision
thoughalocalechoosinglinkinaddProducts.jsp,asmentionedinstep2.Considerthe
followingcode:
<ahref="?language=en">English</a>|<ahref="?language=nl">Dutch</a>
Instep2,wecreatedtwolinkseachtochooseeitherEnglishorDutchasthepreferred
locale.Whentheuserclicksontheselinks,itwilladdarequestparametercalledlanguage
totheURLwiththecorrespondinglocalevalue.Forexample,whenweclickonthe
Englishlinkintheaddproductspageatruntime,itwillchangetherequestURLto
http://localhost:8080/webstore/products/add?language=en.Similarly,ifweclick
ontheDutchlink,itwillchangetherequestURLto
http://localhost:8080/webstore/products/add?language=nl.
Instep3,wecreatedaSessionLocaleResolverbeaninourwebapplicationcontextas
follows:
<beanid="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<propertyname="defaultLocale"value="en"/>
</bean>
SessionLocaleResolveristheonethatsetsthelocaleattributeintheusersession.One
importantpropertyofSessionLocaleResolverisdefaultLocale.Weassignedenasthe
valuefordefaultLocale,whichindicatesthatourpageshouldusethelanguageEnglish
asthedefaultlocale.
Instep4,wecreatedaLocaleChangeInterceptorbeanandconfigureditintheexisting
interceptorlist,asfollows:
<beanclass=
"org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<propertyname="paramName"value="language"/>
</bean>
WeassignedthenamelanguageasthevalueoftheparamNamepropertyin
LocaleChangeInterceptor.Thereasonforthisisbecause,ifyounoticeinstep2,when
wecreatedthelocalechoosinglinkintheaddproductspage(addProduct.jsp),weused
thesameparameternameastherequestparameterwithinthe<a>tag:
<ahref="?language=en">English</a>|<ahref="?language=nl">Dutch</a>
Thisway,wegaveahinttoLocaleChangeInterceptortochoosethecorrectlocale
preferredbytheuser.So,whicheverparameternameyouplannedtouseinyourURL,use
thesamenameasthevaluefortheparamNamepropertyinLocaleChangeInterceptor.
And,onemorethingtokeepinmindisthatthevalueyouhavegiventothelanguage
requestparameterinthelinkshouldmatchoneofthesuffixesofthetranslationmessage
sourcefile.Forexample,inourcase,wecreatedaDutchtranslationmessagesourcefile
andnameditmessages_nl.properties.Here,thesuffixisnl.Ifmessages.properties
waswithoutanysuffix,thedefaultensuffixwillbeconsidered.That’swhy,instep2,we
gavenlandenasthevaluesofthelanguageparameterscorrespondinglyforDutchand
English:
<ahref="?language=en">English</a>|<ahref="?language=nl">Dutch</a>
Finally,whenwerunourapplicationandenter
http://localhost:8080/webstore/products/add,wewillbeabletoseeourregular
productaddpagewithextratwolinksinthetop-rightcornerforchoosingthelanguage.
ClickingontheDutchlinkwillchangetherequestURLto
http://localhost:8080/webstore/products/add?language=nl,whichwillbring
LocaleChangeInterceptortoactionandwillreadDutch-basedlabelmessagesfrom
messages_nl.properties.
Notethatifwedidn’tgiveanylanguageparameterinourURL,Springwilluseanormal
messagesourcefile(messages.properties)fortranslation.Ifwegavealanguage
parameter,Springwillusethatparametervalueasthesuffixtoidentifythecorrect
languagemessagesourcefile(messages_nl.properties).
Haveagohero–fullyinternationalizetheproduct
detailpage
Asalreadymentioned,Ihaveinternationalizedasinglewebpage(addProducts.jsp)for
demonstrationpurposes.Iencourageyoutointernationalizetheproductdetailwebpage
(product.jsp)inyourproject.YoucanusetheGoogleTranslateservice
(https://translate.google.com/)tofindtheDutchtranslationofthelabels.Alongwiththat,
trytoaddonemorelanguagesupportoptionofyourchoice.
Auditlogging
Auditloggingmeansmaintainingalogrecordtoshowwhohadaccessedacomputer
systemandwhatoperationstheyhadperformed.Inourproject,wehaveawebpageto
addproducts;wemayneedtomaintainarecordofwhoaddedwhichproductonwhich
date.Wecancreateaninterceptortomakesuchalogrecord.
Timeforaction–addingthedataaudit
interceptor
UsingasimpleMVCinterceptor,youcanaccomplishauditloggingwithoutmaking
changestoyourapplicationcode.Createaninterceptortorecordanauditlogusingthe
followingsteps:
1. CreateaclasscalledAuditingInterceptorunderthepackage
com.packt.webstore.interceptorinthesourcefoldersrc/main/javaandaddthe
followingcodeintoit:
packagecom.packt.webstore.interceptor;
importjava.text.DateFormat;
importjava.text.SimpleDateFormat;
importjava.util.Calendar;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.apache.log4j.Logger;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
publicclassAuditingInterceptorextendsHandlerInterceptorAdapter{
Loggerlogger=Logger.getLogger("auditLogger");
privateStringuser;
privateStringproductId;
publicbooleanpreHandle(HttpServletRequest
request,HttpServletResponsearg1,Objecthandler)throwsException{
if(request.getRequestURI().endsWith("products/add")
&&request.getMethod().equals("POST")){
user=request.getRemoteUser();
productId=request.getParameterValues("productId")[0];
}
returntrue;
}
publicvoid
afterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,
Objecthandler,Exceptionarg3)throwsException{
if(request.getRequestURI().endsWith("products/add")
&&response.getStatus()==302){
logger.info(String.format("ANewproduct[%s]Addedby%son%s",
productId,user,getCurrentTime()));
}
}
privateStringgetCurrentTime(){
DateFormatformatter=newSimpleDateFormat("dd/MM/yyyy
'at'hh:mm:ss");
Calendarcalendar=Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
returnformatter.format(calendar.getTime());
}
}
2. Now,configureonemoreinterceptorinthewebapplicationcontextconfigurationfile
DispatcherServlet-context.xml;thismeans,addonemoreinterceptorbeanentry
intheexisting<mvc:interceptors>tagforAuditingInterceptorasfollows:
<mvc:interceptors>
<bean
class="com.packt.webstore.interceptor.PerformanceMonitorInterceptor"/>
<beanclass=
"org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<propertyname="paramName"value="language"/>
</bean>
<beanclass="com.packt.webstore.interceptor.AuditingInterceptor"/>
</mvc:interceptors>
3. Openthepropertyfilecalledlog4j.propertiesfromthedirectory
src/main/resourcesandaddthefollowingcontentattheendofthefileandsaveit:
#AuditingLogger
log4j.logger.auditLogger=INFO,auditLogger
log4j.appender.auditLogger=org.apache.log4j.RollingFileAppender
log4j.appender.auditLogger.File=C:\\webstore\\webstore-Audit.log
log4j.appender.auditLogger.maxFileSize=1MB
log4j.appender.file.auditLogger.MaxBackupIndex=1
log4j.appender.auditLogger.layout=org.apache.log4j.PatternLayout
log4j.appender.auditLogger.layout.ConversionPattern=%d{yyyy-MM-dd
HH:mm:ss}%c:%m%n
4. Now,runtheapplicationandenter
http://localhost:8080/webstore/products/add.Youwillbeabletoseethe
regularproductaddpage;justentersomevalidvaluesandpresstheAddbutton.
5. Now,justopentheauditlogfilefromC:\\webstore\\webstore-Audit.log.Youwill
beabletoseetheauditlogs,whicharesomewhatsimilartothefollowing:
2013-12-1712:11:54auditLogger:ANewproduct[P12345]AddedbyAdmin
on17/12/2013at12:11:54
Whatjusthappened?
Instep1,wejustcreatedtheAuditingInterceptorclassbyextendingtheabstractclass
HandlerInterceptorAdapter,andweonlyoverrodetwomethods,namely,preHandle
andafterCompletion.Let’srevieweachmethodindepth,onebyone.
Asweknow,preHandlewillbecalledbeforethecontrollermethodisexecuted.Inside
preHandle,wesimplycheckwhethertheincomingrequestisofthetypePOSTand
whetherittriestomaptherequesttotheaddproductpageproducts/add.Ifitdoes,the
remoteusernameandnewlyaddedproductIDwerestoredinthecorrespondingmember
variablesoftheAuditingInterceptorclass:
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponse
arg1,Objecthandler)throwsException{
if(request.getRequestURI().endsWith("products/add")&&
request.getMethod().equals("POST")){
user=request.getRemoteUser();
productId=request.getParameterValues("productId")[0];
}
returntrue;
}
IntheafterCompletionmethod,wearesimplyloggingtheusernameandnewlyadded
productIDaftercheckingwhethertheresponsestatusisofthetyperedirecting(the
HTTPresponsestatus302):
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponse
response,Objecthandler,Exceptionarg3)throwsException{
if(request.getRequestURI().endsWith("products/add")
&&response.getStatus()==302){
logger.info(String.format("ANewproduct[%s]Addedby%son%s",
productId,user,getCurrentTime()));
}
}
Instep2,weregisteredAuditingInterceptorwithSpringMVCthroughSpring’sspecial
interceptorconfigurationtag.Wewanttorecordourrelateddataauditinglogsina
separatefile.That’swhy,instep3,weaddedaconfigurationrelatedtodataauditinginthe
log4j.propertiesfile.Youcanseethatwehaveconfiguredthedataauditinglogfile
locationinlog4j.propertiesasfollows:
log4j.appender.auditLogger.File=C:\\webstore\\webstore-Audit.log
Finally,whenwerunourapplicationandaddsomenewproducts,wewillbeabletosee
therecordeddataauditlogentryinthewebstore-Audit.logfile.
Conditionalredirecting
Sofar,wehaveseenmanyapplicationsoftheinterceptorinSpringMVC,suchas
performancelogging,internationalization,andauditlogging.However,usinginterceptor,
notonlycanweinterceptthewebrequest,butevenbypassorredirecttheoriginalweb
request.
Timeforaction–interceptingofferpage
requests
Forexample,considerasituationwhereyouwanttoshowthespecialofferproductspage
toonlythoseuserswhohaveavalidpromocode.Theotherstryingtoaccessthespecial
offerproductspagewithaninvalidpromocodeshouldberedirectedtoanerrorpage.
Achievethispieceoffunctionalitywiththehelpoftheinterceptorbyperformingthe
followingsteps:
1. CreateaclassnamedPromoCodeInterceptorunderthe
com.packt.webstore.interceptorpackageinthesourcefoldersrc/main/javaand
addthefollowingcodeintoit:
packagecom.packt.webstore.interceptor;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
import
org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
publicclassPromoCodeInterceptorextendsHandlerInterceptorAdapter{
privateStringpromoCode;
privateStringerrorRedirect;
privateStringofferRedirect;
publicbooleanpreHandle(HttpServletRequest
request,HttpServletResponseresponse,Objecthandler)throwsException{
StringgivenPromoCode=request.getParameterValues("promo")==null
?"":request.getParameterValues("promo")[0];
if(request.getRequestURI().endsWith("products/specialOffer")){
if(givenPromoCode.equals(promoCode)){
response.sendRedirect(request.getContextPath()+"/"+offerRedirect);
}else{
response.sendRedirect(errorRedirect);
}
returnfalse;
}
returntrue;
}
publicStringgetPromoCode(){
returnpromoCode;
}
publicvoidsetPromoCode(StringpromoCode){
this.promoCode=promoCode;
}
publicStringgetErrorRedirect(){
returnerrorRedirect;
}
publicvoidsetErrorRedirect(StringerrorRedirect){
this.errorRedirect=errorRedirect;
}
publicStringgetOfferRedirect(){
returnofferRedirect;
}
publicvoidsetOfferRedirect(StringofferRedirect){
this.offerRedirect=offerRedirect;
}
}
2. Now,configureonemoreinterceptorinthewebapplicationcontext
DispatcherServlet-context.xml;thatmeans,addonemoreinterceptorbeanentry
intheexisting<mvc:interceptors>tagforPromoCodeInterceptor,asfollows:
<mvc:interceptors>
<beanclass=
"com.packt.webstore.interceptor.PerformanceMonitorInterceptor"/>
<bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<propertyname="paramName"value="language"/>
</bean>
<beanclass="com.packt.webstore.interceptor.AuditingInterceptor"/>
<beanclass="com.packt.webstore.interceptor.PromoCodeInterceptor">
<propertyname="promoCode"value="OFF3R"/>
<propertyname="errorRedirect"value="invalidPromoCode"/>
<propertyname="offerRedirect"value="products"/>
</bean>
</mvc:interceptors>
3. OpentheProductControllerclass,andaddonemorerequestmappingmethodtoit
asfollows:
@RequestMapping("/invalidPromoCode")
publicStringinvalidPromoCode(){
return"invalidPromoCode";
}
4. Finally,addonemoreJSPviewfilecalledinvalidPromoCode.jspunderthe
directorysrc/main/webapp/WEB-INF/views/,andaddthefollowingcodesnippet
intoitandsaveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-
8859-1">
<link
rel="stylesheet"href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/boo
tstrap.min.css">
<title>Invalidpromocode</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1class="alertalert-danger">Invalidpromocode</h1>
</div>
</div>
</section>
<section>
<divclass="container">
<p>
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-
primary">
<spanclass="glyphicon-hand-leftglyphicon"></span>
products
</a>
</p>
</div>
</section>
</body>
</html>
5. Now,runtheapplicationandenter
http://localhost:8080/webstore/products/specialOffer?promo=offer.You
willseeapagedisplayinganerrormessageasfollows:
6. Now,enterhttp://localhost:8080/webstore/products/specialOffer?
promo=OFF3R.Youwillberedirectedtoaspecialofferproductpage.
Whatjusthappened?
ThePromoCodeInterceptorclasswecreatedinstep1issimilarto
AuditingInterceptor;theonlydifferenceisthatweoverrodeonlythepreHandle
method.InthepreHandlemethod,wesimplycheckedwhethertheincomingrequestwas
tryingtoaccessthespecialofferproductpage(products/specialOffer):
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponse
response,Objecthandler)throwsException{
StringgivenPromoCode=request.getParameterValues("promo")==null
?"":request.getParameterValues("promo")[0];
if(request.getRequestURI().endsWith("products/specialOffer")){
if(givenPromoCode.equals(promoCode)){
response.sendRedirect(request.getContextPath()+"/"+offerRedirect);
}else{
response.sendRedirect(errorRedirect);
}
returnfalse;
}
returntrue;
}
WecheckagainwhethertherequestcontainsthecorrectpromocodeastheHTTP
parameterandredirecttherequesttotheconfiguredspecialofferpage;otherwise,we
redirectittotheconfigurederrorpage.
Okay,wecreatedthePromoCodeInterceptorclass,butwehavetoconfigurethis
interceptorwithourSpringMVCruntime,whichiswhatwedidinstep2,byaddingthe
followingbeandefinitionwithintheSpringMVCspecialinterceptorconfigurationtag:
<beanclass="com.packt.webstore.interceptor.PromoCodeInterceptor">
<propertyname="promoCode"value="OFF3R"/>
<propertyname="errorRedirect"value="invalidPromoCode"/>
<propertyname="offerRedirect"value="products"/>
</bean>
ThePromoCodeInterceptorclasshasthreeproperties,namely,promoCode,
errorRedirect,andofferRedirect.ThepromoCodepropertyisusedtoconfigurethe
validpromocode;inourcase,weassignedOFF3Rasthevalidpromocode,sowhoeveris
accessingthespecialofferpageshouldprovideOFF3RasthepromocodeintheirHTTP
parameterinordertoaccessthepage.
Thenexttwoattributes,errorRedirectandofferRedirect,areusedinredirection.The
errorRedirectpropertyindicatestheredirectURLmappinginthecaseofaninvalid
promocode,andtheofferRedirectpropertyindicatestheredirectURLmappingfor
successfulpromocoderedirection.
Tip
NotethatIdidnotcreateanyspecialofferproductpage.Justfordemonstrationpurposes,
Ireusedthesameregularproductspageasthespecialofferproductspage;that’swhy,I
assignedproductsasthevaluefortheofferRedirectattribute,sointhecaseofavalid
promocode,Iwillberedirectedtotheregularproducts/page.However,ifIcreatedany
specialofferproductJSPpage,Icanassignthatpage’sURLasthevaluefor
offerRedirect.
Instep3,weaddedonemorerequestmappingmethodcalledinvalidPromoCodetoshow
anerrorpageinthecaseofaninvalidpromocode.Andinstep4,weaddedthe
correspondingerrorviewfilecalledinvalidPromoCode.jsp.
Finally,instep5,wepurposelyentered
http://localhost:8080/webstore/products/specialOffer?promo=offerinour
runningapplicationtodemonstratethePromoCodeInterceptoraction;additionally,we
sawtheerrorpagebecausethepromocodeweprovidedintheURLisoffer(?
promo=offer),whichisincorrect.Instep6,weprovidedthecorrectpromocodeinthe
URLhttp://localhost:8080/webstore/products/specialOffer?promo=OFF3Rsothat
weareabletoseetheconfiguredspecialofferproductspage.
Summary
Inthischapter,weunderstoodtheconceptoftheinterceptorandlearnedhowtoconfigure
theinterceptorinSpringMVC.Welearnedhowtodoperformanceloggingusingthe
interceptor.WealsolearnedhowtouseSpring’sLocaleChangeInterceptortosupport
internationalization.Later,welearnedhowtodoauditloggingusingtheinterceptor.
Finally,welearnedhowtodoconditionalredirectingusingtheinterceptor.
Inthenextchapter,Iwillintroduceyoutovalidation.Youwilllearnhowtodoform
validationandothertypesofcustomvalidations.
Chapter7.ValidateYourProductswitha
Validator
Themostcommonexpectedbehaviorofanywebapplicationisthatitshouldvalidateuser
data.Everytimeausersubmitsdataintoourwebapplication,itneedstobevalidated.
Thisistopreventsecurityattacks,wrongdata,orsimpleusermistakeerrors.Wedon’t
havecontroloverwhatusersmaytypewhensubmittingdataintoourwebapplication.
Forexample,theymaytypesometextinsteadofadate,theymayforgettofillmandatory
fields,orsupposeweusedalengthof12charactersforafieldinthedatabaseandthe
userentereddatathelengthof15characters,thenthedatacannotbesavedinthe
database.Similarly,therearelotsofwaysthatausercanfeedincorrectdataintoourweb
application.Ifweacceptthesevaluesasvalid,thenitwillcreateerrorsandbugswhenwe
processsuchinputs.Thischapterwillexplainthebasicsofsettingupvalidationwith
SpringMVC.
Afterfinishingthischapter,youwillhaveaclearideaaboutthefollowing:
JSR-303beanvalidation
Customvalidation
Springvalidation
Beanvalidation
Javabeanvalidation(JSR-303)isaJavaspecificationthatallowsustoexpressvalidation
constraintsonobjectsviaannotations.ItprovidestheAPIstovalidateandreport
violations.Thehibernatevalidatoristhereferenceimplementationofthebeanvalidation
specification.Wearegoingusethehibernatevalidatorforvalidation.Youcanseethe
availablebeanvalidationannotationatthefollowingURL:
http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html
Timeforaction–addingbeanvalidation
support
Inthissection,youwilllearnhowtovalidateaformsubmissioninaSpringMVC
application.Inourproject,wehavetheaddproductsformalready.Addsomevalidationto
thisformbyperformingthefollowingsteps:
1. Openthepom.xmlfile—youcanfindpom.xmlundertherootdirectoryoftheproject
itself.
2. Youwillseesometabsatthebottomofthepom.xmlfile.SelecttheDependencies
tabandclickontheAddbuttonintheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdasorg.hibernate,enter
ArtifactIdashibernate-validator,enterVersionas4.3.1.Final,selectScopeas
compile,andclickontheOKbuttonandsavepom.xml.
4. OpentheProductdomainclassandaddthe@Patternannotation
(javax.validation.constraints.Pattern)atthetopoftheproductIdfieldas
follows:
@Pattern(regexp="P[0-9]+",message="
{Pattern.Product.productId.validation}")
privateStringproductId;
5. Similarly,addthe@Size,@Min,@Digits,and@NotNullannotations
(javax.validation.constraints.*)atthetopofthenameandunitPricefields,
respectively,asfollows:
@Size(min=4,max=50,message="{Size.Product.name.validation}")
privateStringname;
@Min(value=0,message="Min.Product.unitPrice.validation}")
@Digits(integer=8,fraction=2,message="
{Digits.Product.unitPrice.validation}")
@NotNull(message="{NotNull.Product.unitPrice.validation}")
privateBigDecimalunitPrice;
6. Openthemessagesourcefilemessages.propertiesfrom/src/main/resourcesin
yourprojectandaddthefollowingentriesinit:
Pattern.Product.productId.validation=InvalidproductID.Itshould
startwithcharacterPfollowedbynumber.
Size.Product.name.validation=Invalidproductname.Itshouldbe
minimum4characterstomaximum50characterslong.
Min.Product.unitPrice.validation=UnitpriceisInvalid.Itcannot
havenegativevalues.
Digits.Product.unitPrice.validation=UnitpriceisInvalid.Itcanhave
maximumof2digitfractionand8digitinteger.
NotNull.Product.unitPrice.validation=UnitpriceisInvalid.Itcannot
beempty.
7. OpentheProductControllerclassandchangetheprocessAddNewProductForm
requestmappingmethodbyaddingan@Validannotation
(javax.validation.Valid)infrontoftheproductToBeAddedparameter.Afteryou
aredonewiththis,yourprocessAddNewProductFormmethodsignatureshouldlook
asfollows:
publicStringprocessAddNewProductForm(@ModelAttribute("newProduct")
@ValidProductproductToBeAdded,BindingResultresult,
HttpServletRequestrequest){
8. Now,withinthebodyoftheprocessAddNewProductFormmethod,addthefollowing
conditionasthefirststatement:
if(result.hasErrors()){
return"addProduct";
}
9. OpentheaddProduct.jsppagefromsrc/main/webapp/WEB-INF/views/inyour
projectandaddthe<form:errors>tagfortheproductId,name,andunitPriceinput
elements.Forexample,theproductIDinputtagwillhavethe<form:errors>tag
besideit,asfollows:
<form:inputid="productId"path="productId"type="text"
class="form:input-large"/>
<form:errorspath="productId"cssClass="text-danger"/>
Rememberthatthepathattributevalueshouldalwaysbethesameasthe
correspondinginputtag.
10. Now,addaglobal<form:errors>tagwithinthe<form:form>tagasfollows:
<form:errorspath="*"cssClass="alertalert-danger"element="div"/>
11. AddthebeanconfigurationforLocalValidatorFactoryBeaninyourwebapplication
contextconfigurationfileDispatcherServlet-context.xmlasfollows:
<beanid="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFact
oryBean">
<propertyname="validationMessageSource"ref="messageSource"/>
</bean>
12. Finally,assignthevalidatorpropertyvaluetothe<mvc:annotation-driven>tagas
follows:
<mvc:annotation-drivenenable-matrix-variables="true"
validator="validator"/>
13. Now,runtheapplicationandenter
http://localhost:8080/webstore/products/add.Youwillseeawebpage
displayingawebformtoaddtheproductinformation;withoutfillinganyvalueinthe
form,simplyclickontheAddbutton.Youwillseevalidationmessagesatthetopof
theformasfollows:
TheAddnewproductwebformdisplayingthevalidationmessage
Whatjusthappened?
Sincewedecidedtousethebeanvalidation(JSR-303)specification,weneedan
implementationofthebeanvalidationspecification.Wedecidedtousethehibernate
validatorimplementationinourproject,soweneedtoaddthatJARfiletoourprojectasa
dependency.That’swhatwedidinsteps1to3.
Insteps4and5,weaddedsomejavax.validation.constraintsannotations,suchas
@Pattern,@Size,@Min,@Digits,and@NotNulltoourdomainclassfields
(Product.java).Usingtheseannotations,wecandefinevalidationconstraintsonfields.
Therearemorevalidationconstraintannotationsavailableunderthe
javax.validation.constraintspackage.Justfordemonstrationpurposes,Iuseda
coupleofannotations;youcancheckoutthebeanvalidationdocumentationforallthe
availablelistsofconstraints.
Forexample,takethe@PatternannotationabovetheproductIdfield;itwillcheck
whetherthegivenvalueofthefieldmatchestheregularexpressionspecifiedintheregexp
attributeofthe@Patternannotation.Inourexample,wejustenforcethatthevaluegiven
fortheproductIdfieldshouldstartwiththecharacterPandbefollowedbydigits,as
follows:
@Pattern(regexp="P[0-9]+",message="
{Pattern.Product.productId.validation}")
privateStringproductId;
Themessageattributeofeveryvalidationannotationjustactsasakeytotheactual
messagefromthemessagesourcefile(messages.properties).Inourcase,wespecified
Pattern.Product.productId.validationasthekey,soweneedtodefinetheactual
validationmessageinthemessagesourcefile.That’swhy,weaddedsomemessageentries
instep6.Ifyounoticedthecorrespondingvalueforthekey
Pattern.Product.productId.validationinthemessages.propertiesfile,youwill
noticethefollowingvalue:
Pattern.Product.productId.validation=InvalidproductID.Itshouldstart
withcharacterPfollowedbynumber.
Tip
Notethatyoucanevenaddlocalizederrormessagesinthecorrespondingmessagesource
fileifyouwant.Forexample,ifyouwanttoshowerrormessagesinDutch,simplyadd
errormessageentriesinthemessages_nl.propertiesfileaswell.Duringvalidation,this
messagesourcewillbepickedupautomaticallybySpringbasedonthechosenlocale.
Wedefinedthevalidationconstraintsinourdomainobjectandalsodefinedthevalidation
errormessagesinourmessagesourcefile;whatelsedoweneedtodo?Weneedtotellour
controllertovalidatetheformsubmissionrequest.Wedidthisinsteps7and8inthe
processAddNewProductFormmethod.Considerthefollowingcodesnippet:
@RequestMapping(value="/add",method=RequestMethod.POST)
publicStringprocessAddNewProductForm(@Valid@ModelAttribute("newProduct")
ProductproductToBeAdded,BindingResultresult){
if(result.hasErrors()){
return"addProduct";
}
if(result.getSuppressedFields().length>0){
thrownewIllegalAccessError("Attemptingtobinddisallowedfields");
}
productService.addProduct(productToBeAdded);
return"redirect:/products";
}
WefirstannotatedourmethodparameterproductToBeAddedwiththe@Validannotation
(javax.validation.Valid).Bydoingso,wedirectedSpringMVCtousethebean
validationframeworktovalidatetheproductToBeAddedobject—asyoualreadyknow,the
productToBeAddedobjectisourform-backedbean.Aftervalidatingtheincomingform
bean(productToBeAdded),Springwillstoretheresultsintheresultobject,whichagain
isanothermethodparameteroftheprocessAddNewProductFormmethod.
Instep8,wesimplycheckedwhethertheresultobjectcontainsanyerrors;ifitdoes,we
redirecttothesameaddproductpage.Otherwise,weproceedtoaddproductToBeAddedto
ourrepository.
Sofar,everythingisfine.First,wedefinedtheconstraintsonourdomainobjectandthe
errormessagesinthemessagesourcefile(messages.properties).Later,wevalidated
andcheckedthevalidationresultinthecontrollerformprocessingmethod
(processAddNewProductForm).However,wehaven’tmentionedhowtodisplaytheerror
messagesintheviewfile.WeuseSpring’sspecial<form:errors>tagforthispurpose.
WeaddedthistagfortheproductId,name,andunitPriceinputelementsinstep9.Ifany
oftheinputfieldsfailedduringvalidation,thecorrespondingerrormessagewillbepicked
upbythis<form:errors>tag:
<form:errorspath="productId"cssClass="text-danger"/>
Thepathattributeisusedtoidentifythefieldintheformbeantolookforerrors,andthe
cssClassattributeisusedtostyletheerrormessage.IhaveusedBootstrap’sstyleclass,
text-danger,butyoucanuseanyvalidCSSstyleclassthatyouprefertoapplyonthe
errormessage.
Similarly,instep10,weaddedaglobal<form:errors>tagtoshowallerrormessagesas
aconsolidatedviewatthetopoftheform,asfollows:
<form:errorspath="*"cssClass="alertalert-danger"element="div"/>
Here,weusedthe*symbolforthepathattribute;thismeansthatwewanttoshowallof
theerrors.AndelementattributesindicatewhichtypeofelementSpringshouldusetolist
alloftheerrors.
Sofar,wehaveperformedallofthecoding-relatedexercisesneededtoenablevalidation,
butwehavetodoonefinalconfigurationinourwebapplicationcontexttoenable
validation;thatis,weneedtointroducethebeanvalidationframeworktoSpringMVC.In
steps11and12,wedidjustthat;wecreatedabeanconfigurationfor
LocalValidatorFactoryBean
(org.springframework.validation.beanvalidation.LocalValidatorFactoryBean):
<beanid="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryB
ean">
<propertyname="validationMessageSource"ref="messageSource"/>
</bean>
ThisLocalValidatorFactoryBeanwillinitiatethehibernatevalidatorwhenour
applicationisbeingbooted.ThevalidationMessageSourcepropertyof
LocalValidatorFactoryBeanindicateswhichmessagesourcebeanshouldlookforerror
messages.Sincewealreadyconfiguredamessagesourcebeaninourwebapplication
contextaspartofChapter6,InterceptYourStorewithInterceptor,canmakeuseofthat
messageSourcebeanasthevalueforthevalidationMessageSourceproperty.Wewill
alreadybeabletoseeabeandefinitionwiththenamemessageSourceinourweb
applicationcontext.
Finally,weintroducedourvalidatorbeantoSpringMVCthroughthe
<mvc:annotation-driven>tagbyaddingoneextrapropertycalledvalidator,as
follows:
<mvc:annotation-drivenenable-matrix-variables="true"
validator="validator"/>
Thatisallwedidtoenablevalidation;nowifwerunourapplicationandgettheadd
productpageusingtheURLhttp://localhost:8080/webstore/products/add,wewill
seetheemptyformreadytobesubmitted.Ifwesubmitthisformwithoutfillingany
information,weseeerrormessagesinred.
Haveagohero–addingmorevalidationintheadd
productspage
Ijustaddedvalidationforthefirstthreefieldsintheproductdomainclass;youcanextend
thevalidationfortheremainingfields.Trytoaddlocalizederrormessagesforthe
validationthatyouaredefining.
Thefollowingaresomehintsthatyoucantryout:
Addavalidationtoshowavalidationmessageifthecategoryfiledisempty
TrytoaddavalidationtotheunitsInStockfieldtovalidatethattheminimum
allowednumberofunitsinstockiszero
CustomvalidationwithJSR-303/bean
validation
InthepreviousTimeforactionsection,welearnedhowtousestandardJSR-303bean
validationannotationstovalidatethefieldsofourdomainobject.Thisworksgreatfor
simplevalidations,butsometimes,weneedtovalidatesomecustomrulesthataren’t
availableinstandardannotations.Forexample,whatifweneedtovalidatethatthenewly
addedproductIDisnotthesameasanyoftheexistingproductIDs?Toaccomplishsuch
kindsofvalidations,wecanusecustomvalidationannotations.
Timeforaction–addingcustom
validationsupport
Inthissection,youwilllearnhowtocreatecustomvalidationannotationsandusethem.
AddacustomproductIDvalidationtoyouraddproductpagetovalidateduplicateproduct
IDsbyperformingthefollowingsteps:
1. CreateanannotationinterfacecalledProductId(ProductId.java)underthe
packagecom.packt.webstore.validatorinthesourcefoldersrc/main/java.Then,
addthefollowingcodesnippetinit:
packagecom.packt.webstore.validator;
importstaticjava.lang.annotation.ElementType.ANNOTATION_TYPE;
importstaticjava.lang.annotation.ElementType.FIELD;
importstaticjava.lang.annotation.ElementType.METHOD;
importstaticjava.lang.annotation.RetentionPolicy.RUNTIME;
importjava.lang.annotation.Documented;
importjava.lang.annotation.Retention;
importjava.lang.annotation.Target;
importjavax.validation.Constraint;
importjavax.validation.Payload;
@Target({METHOD,FIELD,ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy=ProductIdValidator.class)
@Documented
public@interfaceProductId{
Stringmessage()default"
{com.packt.webstore.validator.ProductId.message}";
Class<?>[]groups()default{};
publicabstractClass<?extendsPayload>[]payload()default{};
}
2. Now,createaclasscalledProductIdValidatorunderthepackage
com.packt.webstore.validatorinthesourcefoldersrc/main/java.Then,addthe
followingcodeintoit:
packagecom.packt.webstore.validator;
importjavax.validation.ConstraintValidator;
importjavax.validation.ConstraintValidatorContext;
importorg.springframework.beans.factory.annotation.Autowired;
importcom.packt.webstore.domain.Product;
importcom.packt.webstore.service.ProductService;
publicclassProductIdValidatorimplements
ConstraintValidator<ProductId,String>{
@Autowired
privateProductServiceproductService;
publicvoidinitialize(ProductIdconstraintAnnotation){
//intentionallyleftblank;thisistheplacetoinitializethe
constraintannotationforanysensibledefaultvalues.
}
publicbooleanisValid(Stringvalue,ConstraintValidatorContext
context){
Productproduct;
try{
product=productService.getProductById(value);
}catch(ProductNotFoundExceptione){
returntrue;
}
if(product!=null){
returnfalse;
}
returntrue;
}
}
3. Openthemessagesourcefilemessages.propertiesfrom/src/main/resourcesin
yourprojectandaddthefollowingentryinit:
com.packt.webstore.validator.ProductId.message=Aproductalready
existswiththisproductid.
4. Finally,opentheProductdomainclass(Product.java)andannotatetheproductId
fieldwiththenewlycreatedProductIdannotationasfollows:
@Pattern(regexp="P[0-9]+",message="
{Pattern.Product.productId.validation}")
@ProductId
privateStringproductId;
5. Now,runtheapplicationandenter
http://localhost:8080/webstore/products/add.Youwillseeawebpage
displayingawebformtoaddtheproductinformation.Fillthecompletevalueinthe
form;particularly,filltheproductIDfieldwiththevalueP1234andsimplyclickon
theAddbutton.Youwillseevalidationmessagesatthetopoftheform,asfollows:
TheAddnewproductwebformdisplayingcustomvalidation
Whatjusthappened?
Instep1,wejustcreatedourcustomvalidationannotationcalledProductId.Every
customvalidationannotationwecreateshouldneedtobeannotatedwiththe@Constraint
annotation(javax.validation.Constraint).The@Constraintannotationhasan
importantpropertycalledvalidatedBy,whichindicatestheclassthatisperformingthe
actualvalidation.Inourcase,wegaveavalueProductIdValidator.classforthe
validatedByproperty.So,ourProductIdvalidationannotationwillexpectaclasscalled
ProductIdValidator.That’swhy,instep2,wecreatedtheProductIdValidatorclassby
implementingtheConstraintValidatorinterface
(javax.validation.ConstraintValidator).
WeannotatedtheProductIdValidatorclasswiththe@Componentannotation
(org.springframework.stereotype.Component);the@Componentannotationisanother
stereotypeannotationthatisavailableinSpring.Itissimilartothe@Repositoryor
@Serviceannotation.Whenourapplicationisbeingbooted,Springcreatesandmaintains
anobjectfortheProductIdValidatorclass.So,ProductIdValidatorbecomesa
managedbeaninourwebapplicationcontext,whichisthereasonweareabletoautowire
theproductServicebeaninProductIdValidator.
Next,weautowiredtheProductServiceobjectintheProductIdValidatorclass;whydid
wedothis?ItisbecauseinsidetheisValidmethodoftheProductIdValidatorclass,we
usedproductServicetocheckwhetheranyproductexiststhathasthegivenID.Consider
thefollowingcodesnippet:
publicbooleanisValid(Stringvalue,ConstraintValidatorContextcontext){
Productproduct;
try{
product=productService.getProductById(value);
}catch(ProductNotFoundExceptione){
returntrue;
}
if(product!=null){
returnfalse;
}
returntrue;
}
IfanyproductexiststhathasthegivenproductID,weinvalidatethevalidationby
returningfalse;otherwise,wepassthevalidationbyreturningtrue.
Instep3,wejustaddedourdefaulterrormessageforourcustomvalidationannotationin
themessagesourcefile(messages.properties).Ifyouobservedcarefully,thekey
(com.packt.webstore.validator.ProductId.message)weusedinourmessagesource
fileisthesameasthedefaultkeythatwedefinedintheProductId(ProductId.java)
validationannotation:
Stringmessage()default"
{com.packt.webstore.validator.ProductId.message}";
Finally,instep4,weusedthenewlycreatedProductIdvalidationannotationinour
domainclass(Product.java).ItactsinawaysimilartoanyotherJSR-303validation
annotation.
Thus,youwillbeabletoseetheerrormessageonthescreenwhenyouentertheexisting
productIDastheproductIDforthenewlyaddedproduct.
Haveagohero–addingcustomvalidationtoa
category
Createacustomvalidationannotationcalled@Categorythatwillallowonlysomeofthe
predefinedconfiguredcategoriestobeentered.Considerthefollowingthingswhile
implementingyourcustomannotation:
CreateanannotationinterfacecalledCategoryValidatorunderthe
com.packt.webstore.validatorpackage
CreateacorrespondingconstraintvalidatorcalledCategoryValidatorunderthe
packagecom.packt.webstore.validator
Addthecorrespondingerrormessageinthemessagesourcefile
YourCategoryValidatorinterfaceshouldmaintainalistofallowedcategories
(List<String>allowedCategories)tocheckwhetherthegivencategoryexists
underthelistofallowedcategories
Don’tforgettoinitializetheallowedCategorieslistintheconstructorofthe
CategoryValidatorclass
AnnotatethecategoryfieldoftheProductdomainclasswiththe@Category
annotation
Afterapplyingyourcustomvalidationannotation,@category,onthecategoryfieldofthe
Productdomainclass,youraddproductpageshouldrejecttheproductsofother
categoriesthathavenotbeenconfiguredinCategoryValidator.
Springvalidation
WehaveseenhowtoincorporatetheJSR-303beanvalidationwithSpringMVC.In
additiontobeanvalidation,Springhasitsownclassicmechanismtoperformvalidationas
wellwhatiscalledSpringvalidation.TheJSR-303beanvalidationismuchmoreelegant,
expressive,and,ingeneral,simplertousecomparedtotheclassicSpringvalidation.
However,theclassicSpringvalidationisveryflexibleandextensible.Forexample,
consideracross-fieldvalidationwherewewanttocomparetwoormorefieldstoseeif
theirvaluescanbeconsideredasvalidwhencombined.Insuchacase,wecanuseSpring
validation.
Inthelastsection,whereweelaboratedontheuseoftheJSR-303beanvalidation,we
validatedsomeoftheindividualfieldsonourproductdomainobject;wehaven’tdoneany
validationthatcombinestwoormorefields.Wedon’tknowwhetherthecombinationof
differentfieldsmakessense.
Timeforaction–addingSpring
validation
Ifyouhaveaconstraintthatdoesn’tallowanyonetoaddmorethan99unitsofany
productiftheunitpriceisgreaterthan1000USDforthatproduct,addsuchavalidation
usingSpringvalidationintheproject.Performthefollowingsteps:
1. CreateaclasscalledUnitsInStockValidatorunderthe
com.packt.webstore.validatorpackageinthesourcefoldersrc/main/java.Add
thefollowingcodeintoit:
packagecom.packt.webstore.validator;
importjava.math.BigDecimal;
importorg.springframework.stereotype.Component;
importorg.springframework.validation.Errors;
importorg.springframework.validation.Validator;
importcom.packt.webstore.domain.Product;
@Component
publicclassUnitsInStockValidatorimplementsValidator{
publicbooleansupports(Class<?>clazz){
returnProduct.class.isAssignableFrom(clazz);
}
publicvoidvalidate(Objecttarget,Errorserrors){
Productproduct=(Product)target;
if(product.getUnitPrice()!=null&&
newBigDecimal(10000).compareTo(product.getUnitPrice())<=0
&&product.getUnitsInStock()>99){
errors.rejectValue("unitsInStock","com.packt.webstore.validator.UnitsIn
StockValidator.message");
}
}
}
2. Openthemessagesourcefilemessages.propertiesfrom/src/main/resourcesin
theprojectandaddthefollowingentryinit:
com.packt.webstore.validator.UnitsInStockValidator.message=Youcannot
addmorethan99unitsiftheunitpriceisgreaterthan10000.
3. OpentheProductControllerclassandautowireareferencetothe
UnitsInStockValidatorclassasfollows:
@Autowired
privateUnitsInStockValidatorunitsInStockValidator;
4. Now,insidetheinitialiseBindermethodintheProductControllerclass,addthe
followingline:
binder.setValidator(unitsInStockValidator);
5. Now,runtheapplicationandenter
http://localhost:8080/webstore/products/add.Youwillbeabletoseeaweb
pageshowingawebformtoaddproductinformation.Fillallthevaluesintheform;
inparticular,filltheUnitPricefieldwiththevalue10000andtheUnitsInStock
fieldwiththevalue100;nowsimplyclickontheAddbutton.Youwillseevalidation
messagesonthetopoftheform,shownasfollows:
TheAddnewproductwebformdisplayingcross-fieldvalidation
Whatjusthappened?
InclassicSpringvalidation,themainvalidationconstructistheValidatorinterface
(org.springframework.validation.Validator).TheSpringValidatorinterfacedefines
twomethodsforvalidationpurposes,namely,supportsandvalidate.Thesupports
methodindicateswhetherthevalidatorcanvalidateaspecificclass.Ifitcan,thevalidate
methodcanbecalledtovalidateanobjectofthatclass.
EverySpring-basedvalidatorwecreateshouldimplementthisinterface.Instep1,wedid
justthat;wesimplycreatedaclasscalledUnitsInStockValidator,whichimplementsthe
SpringValidatorinterface.
InsidethevalidatemethodoftheUnitsInStockValidatorclass,wesimplycheck
whetherthegivenProductobjecthasaunitpricegreaterthan1000andthenumberof
unitsinstockismorethan99;ifitdoes,werejectthatvaluewithacorrespondingerror
keytoshowtheerrormessagefromthemessagesourcefile,shownasfollows:
@Override
publicvoidvalidate(Objecttarget,Errorserrors){
Productproduct=(Product)target;
if(product.getUnitPrice()!=null&&
newBigDecimal(10000).compareTo(product.getUnitPrice())<=0
&&product.getUnitsInStock()>99){
errors.rejectValue("unitsInStock","com.packt.webstore.validator.UnitsInStoc
kValidator.message");
}
}
Instep2,wesimplyaddedtheactualerrormessagefortheerrorkey
com.packt.webstore.validator.UnitsInStockValidator.messageinthemessage
sourcefile(messages.properties).
Wecreatedthevalidator,buttokickinthevalidation,weneedtoassociatethatvalidator
withthecontroller.That’swhatwedidinsteps3and4.Instep3,wesimplyaddedand
autowiredthereferencetoUnitsInStockValidatorintheProductControllerclass.We
alsoassociatedunitsInStockValidatorwithWebDataBinderintheinitialiseBinder
methodasfollows:
@InitBinder
publicvoidinitialiseBinder(WebDataBinderbinder){
binder.setAllowedFields("productId","name","unitPrice","description","manuf
acturer","category","unitsInStock","productImage");
binder.setValidator(unitsInStockValidator);
}
That’sit!WecreatedandconfiguredourSpring-basedvalidatortodothevalidation.Now,
werunourapplicationandenterhttp://localhost:8080/webstore/products/addto
showthewebformusedforaddingtheproductinformation.Fillthevalueintheform;in
particular,filltheUnitPricefieldwiththevalue10000andtheUnitsInStockfieldwith
thevalue100.Then,clickontheAddbutton.Youwillseevalidationmessagesatthetop
oftheformstatingYoucannotaddmorethan99unitsiftheunitpriceisgreaterthan
10000.
ItisgoodthatwehaveaddedSpring-basedvalidationintoourapplication.However,since
weconfiguredourSpring-basedvalidator(unitsInStockValidator)with
WebDataBinder,thebeanvalidationthatweconfiguredearlierwillnottakeeffect.Spring
MVCsimplyignorestheseJSR-303beanvalidationannotations(@Pattern,@Size,@Min,
@Digits,@NotNull,andsoon).
Timeforaction–combiningSpringand
beanvalidations
YouneedtowritethepreviousbeanvalidationsagaininaclassicSpring-basedvalidation,
whichisnotagoodidea,butthankstotheflexibilityandextensibilityofSpring
validation,youcancombinebothaSpring-basedvalidationandbeanvalidationtogether
withalittleextracode.Performthefollowingsteps:
1. CreateaclasscalledProductValidatorunderthecom.packt.webstore.validator
packageinthesourcefoldersrc/main/java.Then,addthefollowingcodeintoit:
packagecom.packt.webstore.validator;
importjava.util.HashSet;
importjava.util.Set;
importjavax.validation.ConstraintViolation;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.validation.Errors;
importorg.springframework.validation.Validator;
importcom.packt.webstore.domain.Product;
publicclassProductValidatorimplementsValidator{
@Autowired
privatejavax.validation.ValidatorbeanValidator;
privateSet<Validator>springValidators;
publicProductValidator(){
springValidators=newHashSet<Validator>();
}
publicvoidsetSpringValidators(Set<Validator>springValidators){
this.springValidators=springValidators;
}
publicbooleansupports(Class<?>clazz){
returnProduct.class.isAssignableFrom(clazz);
}
publicvoidvalidate(Objecttarget,Errorserrors){
Set<ConstraintViolation<Object>>constraintViolations
=beanValidator.validate(target);
for(ConstraintViolation<Object>constraintViolation
:constraintViolations){
StringpropertyPath
=constraintViolation.getPropertyPath().toString();
Stringmessage=constraintViolation.getMessage();
errors.rejectValue(propertyPath,"",message);
}
for(Validatorvalidator:springValidators){
validator.validate(target,errors);
}
}
}
2. Now,openthewebapplicationcontextconfigurationfileDispatcherServlet-
context.xmlandaddthefollowingbeandefinitiontoit:
<bean
id="productValidator"class="com.packt.webstore.validator.ProductValidat
or">
<propertyname="springValidators">
<set>
<refbean="unitsInStockValidator"/>
</set>
</property>
</bean>
3. CreateonemorebeandefinitionfortheUnitsInStockValidatorclass,asfollows,
andsaveDispatcherServlet-context.xml:
<beanid="unitsInStockValidator"
class="com.packt.webstore.validator.UnitsInStockValidator"/>
4. OpentheProductControllerclassandreplacetheexistingreferenceofthe
UnitsInStockValidatorclasswiththenewlycreatedProductValidatorclass,as
follows:
@Autowired
privateProductValidatorproductValidator;
5. Now,insidetheinitialiseBindermethodoftheProductControllerclass,replace
thebinder.setValidator(unitsInStockValidator);statementwiththefollowing
statement:
binder.setValidator(productValidator);
6. Now,runtheapplicationandenter
http://localhost:8080/webstore/products/addtocheckwhetherallthe
validationsareworkingfine.JustclickontheAddbuttonwithoutfillinganythingon
theform;youwillnoticebeanvalidationtakingplace.Similarly,filltheUnitPrice
fieldwiththevalue10000andtheUnitsInStockfieldwiththevalue100tosee
Springvalidation.Considerthefollowingscreenshot:
TheAddnewproductwebformdisplayingbeanvalidationandSpringvalidation
together
Whatjusthappened?
OuraimwastocombinebeanvalidationandSpring-basedvalidation
(unitsInStockValidator)together.Toachievethis,wecreatedacommonadapter
validatorcalledProductValidatorinstep1.Ifyou’lllookclosely,theProductValidator
classisnothingbutanimplementationofaregularSpringvalidator.
WeautowiredourexistingbeanvalidatorintotheProductValidatorclassthroughthe
followingline:
@Autowired
privatejavax.validation.ValidatorbeanValidator;
Later,weusedthisbeanValidatorreferenceinsidethevalidatemethodofthe
ProductValidatorclass,asfollows,tovalidateallofthebeanvalidationannotations:
Set<ConstraintViolation<Object>>constraintViolations
=beanValidator.validate(target);
for(ConstraintViolation<Object>constraintViolation:constraintViolations)
{
StringpropertyPath=constraintViolation.getPropertyPath().toString();
Stringmessage=constraintViolation.getMessage();
errors.rejectValue(propertyPath,"",message);
}
ThebeanValidator.validate(target);statementreturnsalloftheconstraintviolations.
Then,usingtheerrorsobject,wethrewalloftheinvalidconstraintsaserrormessages.
So,everybeanvalidationannotationthatwespecifiedintheProductdomainclasswill
gethandledwithinforloop.
Similarly,wehaveonemoreforlooptohandlealloftheSpringvalidationsinthe
validatemethodoftheProductValidatorclass,asshownasfollows:
for(Validatorvalidator:springValidators){
validator.validate(target,errors);
}
ThisforloopiteratesthroughthesetofSpringvalidatorsandvalidatestheentriesoneby
one;however,ifyounotice,wehaven’tinitiatedthespringValidatorsreference,soyou
maywonderwherewehaveinitiatedthespringValidatorsset.Youcanfindtheanswer
instep2;wecreatedabeanfortheProductValidatorclassinourwebapplication
context(DispatcherServlet-context.xml)andinstantiatedthespringValidatorssetas
follows:
<bean
id="productValidtor"class="com.packt.webstore.validator.ProductValidator">
<propertyname="springValidators">
<set>
<refbean="unitsInStockValidator"/>
</set>
</property>
</bean>
WereferredabeancalledunitsInStockValidatorwithinthespringValidatorssetof
theproductValidatorbean,sowehavetocreateabeanfortheUnitsInStockValidator
class,whichiswhatwedidinstep3.
Then,wecreatedacommonadaptervalidatorthatcanadoptbeanvalidationandSpring
validationandvalidatesallSpring-andbean-basedvalidationstogether.Now,wehaveto
replacetheUnitsInStockValidatorreferencewiththeProductValidatorreferencein
ourProductControllerclasstokickinournewProductValidatorreference.Wedid
thatinsteps4and5.WesimplyreplacedUnitsInStockValidatorwith
ProductValidatorinthebinder,asfollows:
@InitBinder
publicvoidinitialiseBinder(WebDataBinderbinder){
binder.setAllowedFields("productId","name","unitPrice","description","manuf
acturer","category","unitsInStock","productImage","language");
binder.setValidator(productValidator);
}
WesuccessfullyconfiguredournewlycreatedProductValidatorwith
ProductController.Toseeitinaction,wecanjustrunourapplicationandenter
http://localhost:8080/webstore/products/add.Then,weentersomeinvalidvalues
suchastheexistingproductIDorfilltheUnitPricefieldwiththevalue10000andthe
UnitsInStockfieldwiththevalue100;youwillnoticethebeanandSpringvalidation
errormessagesonthescreen.
Haveagohero–addingSpringvalidationtothe
productimage
CreateaSpringvalidationclasscalledProductImageValidatorthatwillvalidatethesize
oftheproductimage.Itshouldonlyallowanimageofasizethatislessthanorequalto
thepredefinedconfiguredsize.Dothefollowingwhenimplementing
ProductImageValidator:
CreateavalidationclasscalledProductImageValidatorunderthe
com.packt.webstore.validatorpackagebyimplementingthe
org.springframework.validation.Validatorinterface
Addthecorrespondingerrormessageinthemessagesourcefile
YourProductImageValidatorclassshouldmaintainalongvariablecalled
allowedSizetocheckwhetherthegivenimagesizeislessthanorequalit
CreateabeanfortheProductImageValidatorclassintheservletcontextandaddit
underthespringValidatorssetoftheproductValidtorbean
RemembertosettheallowedSizepropertyintheProductImageValidatorbean
Afterapplyingyourcustomvalidationannotation@categoryonthecategoryfieldofthe
Productdomainclass,youraddproductpageshouldrejecttheproductsofother
categoriesthathavenotbeenconfiguredinCategoryValidator.
Summary
Inthischapter,welearnedtheconceptofvalidationandlearnedhowtoenablebean
validationinSpringMVCtoprocessforms.Wealsolearnedhowtosetupcustom
validationusingtheextensioncapabilityofthebeanvalidationframework.Afterthat,we
learnedhowtodocross-fieldvalidationusingSpringvalidation.Finally,welearnedhow
tointegratebeanvalidationandSpringvalidationtogether.
Inthenextchapter,wewilllearnhowtodevelopanapplicationusingRESTfulservices.
WewillcoverthebasicconceptsofHTTPverbsandtrytounderstandhowtheyare
relatedtostandardCRUDoperations.WewillalsocoverhowtofireanAjaxrequestand
howtohandleit.
Chapter8.GiveRESTtoYour
ApplicationwithAjax
RESTstandsforRepresentationalStateTransferandisastyleofwebapplication
architecture.EverythinginRESTisconsideredasaresource,andeveryresourceis
identifiedbyaURI.RESTfulwebserviceshavebeenembracedbylargeserviceproviders
acrosstheWebasanalternativetoSOAP-basedwebservicesduetoitssimplicity.
Afterfinishingthischapter,youwillhaveaclearideaaboutthefollowing:
RESTwebservices
Ajax
IntroducingREST
AsIalreadymentioned,inaREST-basedapplication,everything,includingstatic
resources,data,andoperations,areconsideredasresourcesandidentifiedbyaURI.For
example,considerapieceoffunctionalitythatcanhelpusaddanewproducttoourstore;
wecanrepresentthisoperationbyaURI,somethinglike
http://localhost:8080/webstore/products/add,andwecanpassthenewproduct
detailsinXMLorJSONrepresentationtothatURL.So,inREST,URIsareusedto
connectclientsandserverstoexchangeresourcesintheformofrepresentations(HTML,
XML,JSON,andsoon).Inordertoexchangedata,RESTreliesonbasicHTTPprotocol
methods:GET,POST,PUT,andDELETE.
SpringprovidesextensivesupporttodevelopREST-basedwebservices.Inourprevious
chapters,wesawthatwheneverawebrequestwasmade,wereturnedawebpagetoserve
thatrequest;usually,suchwebpageswillalwayscontainsomestates(dynamicdata).
However,inREST-basedapplications,weonlyreturnthestates,anditisuptotheclient
todecideonhowtorenderorpresentthedatatotheenduser.
Normally,REST-basedwebservicesreturndataintwoformats:XMLandJSON.Weare
goingtodevelopsomeREST-basedwebservicesthatwillreturndataintheJSONformat.
OncewegettheJSONdata,we’llthenrenderitasanHTMLpageinthebrowserusinga
JavaScriptlibrary.
Inourwebstoreapplication,wehavesuccessfullylistedsomeoftheproducts;however,
thestorecannotmakeprofitwithoutfacilitatingtheendusertopickupsomeproductsand
placetheminhis/hershoppingcart.Solet’saddashoppingcartfacilitytoourstore.
Timeforaction–implementingRESTful
webservices
Wearegoingtoaddashoppingcartfacilityintwophases.Firstly,wewillcreateaREST-
stylecontrollertohandleallshopping-cart-relatedwebrequests.Secondly,wewilladd
someJavaScriptcodetorendertheJSONdatareturnedbytheRESTwebservice
controller.First,let’simplementsomeRESTfulwebservicesusingSpringMVC
controllerssothatlaterwecanaddsomeJavaScriptcodetoconsumethosewebservices.
1. CreateadomainclassnamedCartItemunderthepackage
com.packt.webstore.domaininthesourcefoldersrc/main/java;then,addthe
followingcodetoit:
packagecom.packt.webstore.domain;
importjava.math.BigDecimal;
publicclassCartItem{
privateProductproduct;
privateintquantity;
privateBigDecimaltotalPrice;
publicCartItem(){
//TODOAuto-generatedconstructorstub
}
publicCartItem(Productproduct){
super();
this.product=product;
this.quantity=1;
this.totalPrice=product.getUnitPrice();
}
publicProductgetProduct(){
returnproduct;
}
publicvoidsetProduct(Productproduct){
this.product=product;
this.updateTotalPrice();
}
publicintgetQuantity(){
returnquantity;
}
publicvoidsetQuantity(intquantity){
this.quantity=quantity;
this.updateTotalPrice();
}
publicBigDecimalgetTotalPrice(){
returntotalPrice;
}
publicvoidupdateTotalPrice(){
totalPrice=
this.product.getUnitPrice().multiply(newBigDecimal(this.quantity));
}
@Override
publicinthashCode(){
finalintprime=311;
intresult=1;
result=prime*result+((product==null)?0
:product.hashCode());
returnresult;
}
@Override
publicbooleanequals(Objectobj){
if(this==obj)
returntrue;
if(obj==null)
returnfalse;
if(getClass()!=obj.getClass())
returnfalse;
CartItemother=(CartItem)obj;
if(product==null){
if(other.product!=null)
returnfalse;
}elseif(!product.equals(other.product))
returnfalse;
returntrue;
}
}
2. Similarly,addonemoredomainclassnamedCarttothesamepackage,andaddthe
followingcodetoit:
packagecom.packt.webstore.domain;
importjava.math.BigDecimal;
importjava.util.HashMap;
importjava.util.Map;
publicclassCart{
privateStringcartId;
privateMap<String,CartItem>cartItems;
privateBigDecimalgrandTotal;
publicCart(){
cartItems=newHashMap<String,CartItem>();
grandTotal=newBigDecimal(0);
}
publicCart(StringcartId){
this();
this.cartId=cartId;
}
publicStringgetCartId(){
returncartId;
}
publicvoidsetCartId(StringcartId){
this.cartId=cartId;
}
publicMap<String,CartItem>getCartItems(){
returncartItems;
}
publicvoidsetCartItems(Map<String,CartItem>cartItems){
this.cartItems=cartItems;
}
publicBigDecimalgetGrandTotal(){
returngrandTotal;
}
publicvoidaddCartItem(CartItemitem){
StringproductId=item.getProduct().getProductId();
if(cartItems.containsKey(productId)){
CartItemexistingCartItem=cartItems.get(productId);
existingCartItem.setQuantity(existingCartItem.getQuantity()+
item.getQuantity());
cartItems.put(productId,existingCartItem);
}else{
cartItems.put(productId,item);
}
updateGrandTotal();
}
publicvoidremoveCartItem(CartItemitem){
StringproductId=item.getProduct().getProductId();
cartItems.remove(productId);
updateGrandTotal();
}
publicvoidupdateGrandTotal(){
grandTotal=newBigDecimal(0);
for(CartItemitem:cartItems.values()){
grandTotal=grandTotal.add(item.getTotalPrice());
}
}
@Override
publicinthashCode(){
finalintprime=71;
intresult=1;
result=prime*result+((cartId==null)?0
:cartId.hashCode());
returnresult;
}
@Override
publicbooleanequals(Objectobj){
if(this==obj)
returntrue;
if(obj==null)
returnfalse;
if(getClass()!=obj.getClass())
returnfalse;
Cartother=(Cart)obj;
if(cartId==null){
if(other.cartId!=null)
returnfalse;
}elseif(!cartId.equals(other.cartId))
returnfalse;
returntrue;
}
}
3. CreateaninterfacenamedCartRepositoryunderthepackage
com.packt.webstore.domain.repositoryinthesourcefoldersrc/main/java;then,
addthefollowingmethoddeclarationstoit:
Cartcreate(Cartcart);
Cartread(StringcartId);
voidupdate(StringcartId,Cartcart);
voiddelete(StringcartId);
4. CreateanimplementationclassnamedInMemoryCartRepositoryforthepreceding
interfaceunderthepackagecom.packt.webstore.domain.repository.implinthe
sourcefoldersrc/main/java;then,addthefollowingcodetoit:
packagecom.packt.webstore.domain.repository.impl;
importjava.util.HashMap;
importjava.util.Map;
importorg.springframework.stereotype.Repository;
importcom.packt.webstore.domain.Cart;
importcom.packt.webstore.domain.repository.CartRepository;
@Repository
publicclassInMemoryCartRepositoryimplementsCartRepository{
privateMap<String,Cart>listOfCarts;
publicInMemoryCartRepository(){
listOfCarts=newHashMap<String,Cart>();
}
publicCartcreate(Cartcart){
if(listOfCarts.keySet().contains(cart.getCartId())){
thrownewIllegalArgumentException(String.format("Cannotcreate
acart.Acartwiththegiveid(%)aldradyexist",cart.getCartId()));
}
listOfCarts.put(cart.getCartId(),cart);
returncart;
}
publicCartread(StringcartId){
returnlistOfCarts.get(cartId);
}
publicvoidupdate(StringcartId,Cartcart){
if(!listOfCarts.keySet().contains(cartId)){
thrownewIllegalArgumentException(String.format("Cannotupdate
cart.Thecartwiththegiveid(%)doesnotdoesnotexist",cartId));
}
listOfCarts.put(cartId,cart);
}
publicvoiddelete(StringcartId){
if(!listOfCarts.keySet().contains(cartId)){
thrownewIllegalArgumentException(String.format("Cannotdelete
cart.Thecartwiththegiveid(%)doesnotdoesnotexist",cartId));
}
listOfCarts.remove(cartId);
}
}
5. CreateaninterfacenamedCartServiceunderthepackage
com.packt.webstore.serviceinthesourcefoldersrc/main/java;then,addthe
followingmethoddeclarationstoit:
packagecom.packt.webstore.service;
importcom.packt.webstore.domain.Cart;
publicinterfaceCartService{
Cartcreate(Cartcart);
Cartread(StringcartId);
voidupdate(StringcartId,Cartcart);
voiddelete(StringcartId);
}
6. CreateanimplementationclassnamedCartServiceImplfortheearlierinterface
underthepackagecom.packt.webstore.service.implinthesourcefolder
src/main/java;then,addthefollowingcodetoit:
packagecom.packt.webstore.service.impl;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Service;
importorg.springframework.transaction.annotation.Transactional;
importcom.packt.webstore.domain.Cart;
importcom.packt.webstore.domain.repository.CartRepository;
importcom.packt.webstore.service.CartService;
@Service
publicclassCartServiceImplimplementsCartService{
@Autowired
privateCartRepositorycartRepository;
publicCartcreate(Cartcart){
returncartRepository.create(cart);
}
publicCartread(StringcartId){
returncartRepository.read(cartId);
}
publicvoidupdate(StringcartId,Cartcart){
cartRepository.update(cartId,cart);
}
publicvoiddelete(StringcartId){
cartRepository.delete(cartId);
}
}
7. Now,createaclassnamedCartRestControllerunderthepackage
com.packt.webstore.controllerinthesourcefoldersrc/main/java;then,addthe
followingcodetoit:
packagecom.packt.webstore.controller;
importjavax.servlet.http.HttpServletRequest;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.http.HttpStatus;
importorg.springframework.stereotype.Controller;
importorg.springframework.web.bind.annotation.ExceptionHandler;
importorg.springframework.web.bind.annotation.PathVariable;
importorg.springframework.web.bind.annotation.RequestBody;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
importorg.springframework.web.bind.annotation.ResponseBody;
importorg.springframework.web.bind.annotation.ResponseStatus;
importcom.packt.webstore.domain.Cart;
importcom.packt.webstore.domain.CartItem;
importcom.packt.webstore.domain.Product;
importcom.packt.webstore.exception.ProductNotFoundException;
importcom.packt.webstore.service.CartService;
importcom.packt.webstore.service.ProductService;
@Controller
@RequestMapping(value="rest/cart")
publicclassCartRestController{
@Autowired
privateCartServicecartService;
@Autowired
privateProductServiceproductService;
@RequestMapping(method=RequestMethod.POST)
public@ResponseBodyCartcreate(@RequestBodyCartcart){
returncartService.create(cart);
}
@RequestMapping(value="/{cartId}",method=RequestMethod.GET)
public@ResponseBodyCartread(@PathVariable(value="cartId")String
cartId){
returncartService.read(cartId);
}
@RequestMapping(value="/{cartId}",method=RequestMethod.PUT)
@ResponseStatus(value=HttpStatus.NO_CONTENT)
publicvoidupdate(@PathVariable(value="cartId")StringcartId,
@RequestBodyCartcart){
cartService.update(cartId,cart);
}
@RequestMapping(value="/{cartId}",method=RequestMethod.DELETE)
@ResponseStatus(value=HttpStatus.NO_CONTENT)
publicvoiddelete(@PathVariable(value="cartId")StringcartId){
cartService.delete(cartId);
}
@RequestMapping(value="/add/{productId}",method=
RequestMethod.PUT)
@ResponseStatus(value=HttpStatus.NO_CONTENT)
publicvoidaddItem(@PathVariableStringproductId,
HttpServletRequestrequest){
StringsessionId=request.getSession(true).getId();
Cartcart=cartService.read(sessionId);
if(cart==null){
cart=cartService.create(newCart(sessionId));
}
Productproduct=productService.getProductById(productId);
if(product==null){
thrownewIllegalArgumentException(new
ProductNotFoundException(productId));
}
cart.addCartItem(newCartItem(product));
cartService.update(sessionId,cart);
}
@RequestMapping(value="/remove/{productId}",method=
RequestMethod.PUT)
@ResponseStatus(value=HttpStatus.NO_CONTENT)
publicvoidremoveItem(@PathVariableStringproductId,
HttpServletRequestrequest){
StringsessionId=request.getSession(true).getId();
Cartcart=cartService.read(sessionId);
if(cart==null){
cart=cartService.create(newCart(sessionId));
}
Productproduct=productService.getProductById(productId);
if(product==null){
thrownewIllegalArgumentException(new
ProductNotFoundException(productId));
}
cart.removeCartItem(newCartItem(product));
cartService.update(sessionId,cart);
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value=HttpStatus.BAD_REQUEST,reason="Illegal
request,pleaseverifyyourpayload")
publicvoidhandleClientErrors(Exceptionex){}
@ExceptionHandler(Exception.class)
@ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR,
reason="Internalservererror")
publicvoidhandleServerErrors(Exceptionex){}
}
8. Now,runourwebstoreprojectfromSTS.
Whatjusthappened?
Instep1and2,wecreatedtwodomainclassescalledCartItemandCarttoholdthe
informationabouttheshoppingcart.TheCartItemclassrepresentsasingleitemina
shoppingcart,anditholdsinformationsuchasproduct,quantity,andtotalPrice.
Similarly,Cartrepresentsthewholeshoppingcartitself,andCartcanhaveacollectionof
cartItemsandgrandTotal.
Insteps3and4,wecreatedarepositorylayertomanagetheCartobjects.Inthe
CartRepositoryinterface,wedefinedfourmethodstotakecareofCRUDoperations
(create,read,update,anddelete)ontheCartobjects.InMemoryCartRepositoryisjust
animplementationofCartRepository.
Similarly,insteps5and6,wecreatedtheservicelayerfortheCartobjects.The
CartServiceinterfacehasthesamemethodsasthatoftheCartRepositoryand
CartServiceImplclass;theonlydifferenceisthatitinternallyuses
InMemoryCartRepositorytocarryoutalltheCRUDoperations.
Step7isverycrucialinthewholesequencebecauseinthatstepwecreatedourREST-
styledcontrollertohandleallRESTwebservicesrelatedtoCart.The
CartRestControllerclassmainlyhasfourmethodstohandlewebrequestsforCRUD
operationsontheCartobjects,namelycreate,read,update,anddelete.Additionally,it
hastwomethods,addItemandremoveItem,tohandletheaddingandremovingof
CartItemfromtheCartobjects.WewillhaveadeeperlookatthefirstfourCRUD
methods.
Ifyousee,onthesurface,theCartRestControllerclassisjustlikeanyothernormal
SpringMVCcontrollerbecauseitjusthasthesame@Controllerand@RequestMapping
annotations.SowhatmakesitsospecialtobecomeaREST-styledcontrolleristhe
@ResponseBodyand@RequestBodyannotations;seethefollowingcontrollermethod:
@RequestMapping(method=RequestMethod.POST)
public@ResponseBodyCartcreate(@RequestBodyCartcart){
returncartService.create(cart);
}
Usually,everycontrollermethodisusedtoreturnaviewnamesothatthedispatcher
servletcanfindtheappropriateviewfileanddispatchthatviewfiletotheclient,buthere
wehavereturnedtheobject(theCartobject).Insteadofputtingtheobjectintothemodel,
wehavereturnedtheobjectbecausewewanttoreturnthestateoftheobjectinJSON
format.RememberthatREST-basedwebservicesshouldreturndataineitherJSONor
XMLformat,andtheclientcanusethisdataintheirdesiredway;theymayrenderittoan
HTMLpage,ortheymaysendittosomeexternalsystemasisintheformofraw
JSON/XMLdata.
Okay!Let’scometothepoint;thecreatecontrollermethodjustreturnedanewlycreated
Cartjavaobject.HowisitthatthisJavaobjectgotconvertedintoJSONorXMLformat?
Here,the@ResponseBodyannotationstepsintothepicture.The@ResponseBodyannotation
willconvertthegivenJavaobjectintoJSON/XMLformatandsenditasaresponseinthe
bodyofanHTTPresponse.Similarly,whenyousendanHTTPrequesttoacontroller
methodwithJSON/XMLdatainit,the@RequestBodyannotationwillconvertitintothe
correspondingJavaobject.Thisiswhythecreatemethodhasthecartparameter
annotatedwiththe@RequestBodyannotationandthereturnobjectannotatedwiththe
@ResponseBodyannotation.
Ifyoucloselyobservethe@RequestMappingannotationofallthosefourCRUDmethods,
youwillendupwiththefollowingtable:
URL HTTPmethod Description
http://localhost:8080/webstore/rest/cart POST Createsanewcart
http://localhost:8080/webstore/rest/cart/1234 GET RetrievescartwiththeID1234
http://localhost:8080/webstore/rest/cart/1234 PUT UpdatescartwiththeID1234
http://localhost:8080/webstore/rest/cart/1234 DELETE DeletescartwiththeID1234
ThoughtherequestmappingURLismoreorlessthesame,wecanperformdifferent
operationsbasedontheHTTPmethod;forexample,ifyousendaGETrequesttothe
URLhttp://localhost:8080/webstore/rest/cart/1234,thereadcontrollermethod
willgetexecutedandtheCartobjectwiththeID1234willgetreturnedintheJSON
format.Similarly,ifyousendaPUTorDELETErequesttothesameURL,theupdateor
deletecontrollermethodwouldgetcalledcorrespondingly.
InadditiontothosefourCRUDrequest-mappingmethods,wehavetwomorerequest-
mappingmethodsthattakecareofaddingandremovingCartItemfromtheCartobject.
Thesemethodsareconsideredasupdatemethods;thisiswhyboththeaddItemand
removeItemmethodshavePUTastherequestmethodtypeintheir@RequestMapping
annotation.Forinstance,ifyousendaPUTrequesttotheURL
http://localhost:8080/webstore/rest/cart/add/P1236,aproductwiththeproductID
P1236willbeaddedtotheCartobject.Similarly,ifyousendaPUTrequesttotheURL
http://localhost:8080/webstore/rest/cart/remove/P1236,aproductwithP1236will
beremovedfromtheCartobject.
Timeforaction–consumingRESTweb
services
Okay!WehavecreatedourREST-stylecontrollerthatcanservesomeREST-basedweb
requests,butwehavenotseenourCartRestControllerclassinaction.Usingastandard
browser,wecanonlysendGETorPOSTrequests;inordertosendaPUTorDELETErequest,
weneedaspecialtool.ThereareplentyofHTTPclienttoolsavailabletosendsuch
requests.Let’suseonesuchtoolcalledPostmantotestourCartRestController.Postman
isaGoogleChromebrowserextension,soyou’dbetterinstallGoogleChromeinyour
systembeforeyoudownloadthePostmanHTTPclient.Performthefollowingsteps:
1. GotothePostmandownloadpage,http://www.getpostman.com/,fromyourGoogle
ChromebrowserandclickontheDownloadPostmanlink.Itwilltakeyoutothe
Chromewebstorepage;clickonthe+FREEbuttontoinstallthePostmantoolin
yourbrowser.Considerthefollowingscreenshot:
Postman—HTTPclientappinstallation
2. Now,aGoogleloginpagewillappearaskingyoutologin.LoginusingyourGoogle
account.
3. Aconfirmdialogwillbeshownonyourscreenaskingyourpermissiontoaddthe
Postmanextensiontoyourbrowser;clickontheAddbuttonasshowninthe
followingscreenshot:
Postman—HTTPclientappinstallation
4. NowopenyourGoogleChromebrowserandentertheURL,chrome://apps/.Aweb
pagewillgetloadedwithalltheavailableappsforyourChromebrowser.Justclick
onthePostmanicontolaunchthePostmanapp.BeforeyoulaunchPostman,ensure
thatyourwebstoreprojectisrunning.
5. Now,inthePostmanapp,entertherequestURLas
http://localhost:8080/webstore/rest/cart,therequestmethodasPOST,then
selecttherawformat,andfinallythecontenttypeasJSON(application/json).
Considerthefollowingscreenshot:
Postman–postingawebrequest
6. Now,enterthefollowingJSONcontentinthecontentboxandclickontheSend
button.YouwillgetHTTPrespondstatus200OK.Considerthefollowingcode
snippet:
{
"cartId":"1234",
"cartItems":{
"P1234":{
"product":{
"productId":"P1234",
"name":"iPhone5s",
"unitPrice":500,
"description":"AppleiPhone5ssmartphonewith4.00-inch
640x1136displayand8-megapixelrearcamera",
"manufacturer":"Apple",
"category":"SmartPhone",
"unitsInStock":1000,
"unitsInOrder":0,
"discontinued":false,
"condition":"NEW"
},
"quantity":1,
"totalPrice":500
}
},
"grandTotal":500
}
7. Now,similarlyinthePostmanapp,enterthetargetURLas
http://localhost:8080/webstore/rest/cart/1234,therequestmethodasGET,
andclickontheSendbutton.YouwillgetthesameJSONcartdatathatyoujust
postedinstep7astheresponse.
8. Toupdatethecart,inthePostmanapp,enterthetargetURLas
http://localhost:8080/webstore/rest/cart/1234andjustchangetheJSONdata.
Forinstance,inthecontentbox,justchangequantityto3,totalPriceto1500and
grandTotalto1500,choosetherequestmethodasPUTandthecontenttypeas
JSON(application/json),andsendtherequesttothesameURLbyclickingonthe
Sendbutton.Toverifywhetheryourchangestookplace,justrepeatstep7.Consider
thefollowingcodesnippet:
{
"cartId":"1234",
"cartItems":{
"P1234":{
"product":{
"productId":"P1234",
"name":"iPhone5s",
"unitPrice":500,
"description":"AppleiPhone5ssmartphonewith4.00-inch
640x1136displayand8-megapixelrearcamera",
"manufacturer":"Apple",
"category":"SmartPhone",
"unitsInStock":1000,
"unitsInOrder":0,
"discontinued":false,
"condition":"NEW"
},
"quantity":3,
"totalPrice":1500
}
},
"grandTotal":1500
}
9. Similarly,todeletethecart,justenterthe
http://localhost:8080/webstore/rest/cart/1234URL.InthePostmanapp,
enterthetargetURLandrequestmethodasDELETEandclickontheSendbutton.
YouwillgetanHTTPstatus204NoContentastheresponse.Toverifywhetherthe
cartgotdeleted,justrepeatstep7;youwillgetanemptyresponse.
Whatjusthappened?
Atthestartofthechapter,wediscussedthatmostoftheREST-basedwebservicesare
designedtoexchangedatainJSONorXMLformat.ThisisbecauseJSONandXML
formatsareconsideredtobeuniversalformats,andanysystemcaneasilyunderstand,
parse,andinterpretthatdata.InordertotestREST-basedwebservicesthatwehave
created,weneedatoolthatcansenddifferent(GET,PUT,POST,orDELETE)typesofHTTP
requestswithJSONdatainitsrequestbody.Postmanisonesuchtoolthatisavailableasa
GoogleChromeextension.
Fromstep1to4,weinstalledthePostmanappinourGoogleChromebrowser.Instep6
wesentourfirstREST-basedwebrequesttothetargetURL
http://localhost:8080/webstore/rest/carttocreateanewcartinourwebstore
application.WedidthisbysendingaPOSTrequestwiththewholecartinformationas
JSONdatatothetargetURL.IfyounoticethefollowingJSONdata,itrepresentsacart
withthecartIDas1234,andithasjustoneproductcartitem(iPhone5s)init.Consider
thefollowingcodesnippet:
{
"cartId":"1234",
"cartItems":{
"P1234":{
"product":{
"productId":"P1234",
"name":"iPhone5s",
"unitPrice":500,
"description":"AppleiPhone5ssmartphonewith4.00-inch640x1136
displayand8-megapixelrearcamera",
"manufacturer":"Apple",
"category":"SmartPhone",
"unitsInStock":1000,
"unitsInOrder":0,
"discontinued":false,
"condition":"NEW"
},
"quantity":1,
"totalPrice":500
}
},
"grandTotal":500
}
Nowthatwehavepostedourfirstcart,toverifywhetherthatcartgotstoredinoursystem,
wehavesentanotherRESTwebrequestinstep7togetthewholecartinformationin
JSONformat.NoticethatthistimetherequesttypeisGETandthetargetURLis
http://localhost:8080/webstore/rest/cart/1234.Rememberwelearnedthatina
REST-basedapplication,everyresourceisidentifiablebyaURI.Here,theURL
http://localhost:8080/webstore/rest/cart/1234representsacartwhoseIDis1234.
IfyousendaGETrequesttotheURLmentionedearlier,youwillgetthecartinformation
intheformofJSONdata;similarly,youcanevenupdatethewholecartbysendingan
updatedJSONdataasaPUTrequesttothesameURL.Thisiswhatwedidinstep8.Ina
similarfashion,wesentaDELETErequesttothesameURLtodeletethecartwhoseIDis
1234.
Okay!WehavetestedorconsumedourREST-basedwebserviceswiththehelpofthe
PostmanHTTPclienttool,whichisworkingquitewell.However,inareal-world
application,mostofthetime,thesekindsofREST-basedwebservicesareconsumedfrom
thefrontendwiththehelpofaconceptcalledAjax.UsingaJavaScriptlibrary,wecan
sendanAjaxrequesttothebackend.Inthenextsection,wewillseewhatAjaxrequests
areandhowtoconsumeaREST-basedwebserviceusingJavaScript/Ajaxlibraries.
HandlingawebserviceinAjax
AsynchronousJavaScriptandXML(Ajax)isawebdevelopmenttechniqueusedonthe
clientsidetocreateasynchronouswebapplications.Inatypicalwebapplication,every
timeawebrequestisfiredasaresponse,wegetafullwebpageloadedasaresponse;
however,inanAjax-basedasynchronouswebapplication,webpagesareupdated
asynchronouslybypollingsmalldatawiththeserverbehindthescenes.Thismeansthat
usingAjax,itispossibletoupdatepartsofawebpagewithoutreloadingtheentireweb
page.WithAjax,webapplicationscansenddatatoandretrievedatafromaserver
asynchronously.TheasynchronousaspectofAjaxallowsustowritecodethatcansenda
requesttoaserverandhandleaserverresponsewithoutreloadingtheentirewebpage.
InanAjax-basedapplication,theXMLHttpRequestobjectisusedtoexchangedata
asynchronouslywiththeserver,whereXMLorJSONisoftenusedastheformatfor
transferringdata.The“X”inAJAXstandsforXML,butJSONisusedinsteadofXML
nowadaysbecauseofitssimplicity;also,itusesfewercharacterstorepresentthesame
datacomparedtoXML.So,itcanreducethebandwidthrequirementsoverthenetworkto
ensuredatatransferisfasterthannormal.
Okay!WehaveimplementedsomeREST-basedwebservicesthatcanmanagethe
shoppingcartinourapplication,butweneedafrontendthatcanfacilitatetheenduserto
managetheshoppingcartvisually.So,let’sconsumethosewebservicesviaAjaxinthe
frontendtomanagetheshoppingcart.
Timeforaction–consumingRESTweb
servicesviaAjax
InordertoconsumeRestwebservicesviaAjax,performthefollowingsteps:
1. AddaJavaScriptfilenamedcontrollers.jsunderthedirectory
/src/main/webapp/resources/js/;then,addthefollowingcodesnippetstoitand
saveit:
varcartApp=angular.module('cartApp',[]);
cartApp.controller('cartCtrl',function($scope,$http){
$scope.refreshCart=function(cartId){
$http.get('/webstore/rest/cart/'+$scope.cartId)
.success(function(data){
$scope.cart=data;
});
};
$scope.clearCart=function(){
$http.delete('/webstore/rest/cart/'+$scope.cartId)
.success($scope.refreshCart($scope.cartId));
};
$scope.initCartId=function(cartId){
$scope.cartId=cartId;
$scope.refreshCart($scope.cartId);
};
$scope.addToCart=function(productId){
$http.put('/webstore/rest/cart/add/'+productId)
.success(function(data){
$scope.refreshCart($http.get('/webstore/rest/cart/get/cartId'));
alert("ProductSuccessfullyaddedtotheCart!");
});
};
$scope.removeFromCart=function(productId){
$http.put('/webstore/rest/cart/remove/'+productId)
.success(function(data){
$scope.refreshCart($http.get('/webstore/rest/cart/get/cartId'));
});
};
});
2. Now,createaclassnamedCartControllerunderthepackage
com.packt.webstore.controllerinthesourcefoldersrc/main/java;then,addthe
followingcodetoit:
packagecom.packt.webstore.controller;
importjavax.servlet.http.HttpServletRequest;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.PathVariable;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping(value="/cart")
publicclassCartController{
@RequestMapping
publicStringget(HttpServletRequestrequest){
return"redirect:/cart/"+request.getSession(true).getId();
}
@RequestMapping(value="/{cartId}",method=RequestMethod.GET)
publicStringgetCart(@PathVariable(value="cartId")StringcartId,
Modelmodel){
model.addAttribute("cartId",cartId);
return"cart";
}
}
3. AddonemoreJSPviewfilenamedcart.jspunderthedirectory
src/main/webapp/WEB-INF/views/;then,addthefollowingcodesnippetstoitand
saveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=ISO-8859-
1">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.
js"></script>
<scriptsrc="/webstore/resource/js/controllers.js"></script>
<title>Cart</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Cart</h1>
<p>Alltheselectedproductsinyourcart</p>
</div>
</div>
</section>
<sectionclass="container"ng-app="cartApp">
<divng-controller="cartCtrl"ng-init="initCartId('${cartId}')">
<div>
<aclass="btnbtn-dangerpull-left"
ng-click="clearCart()"><span
class="glyphiconglyphicon-remove-sign"></span>ClearCart
</a><ahref="#"class="btnbtn-successpull-right"><span
class="glyphicon-shopping-cartglyphicon"></span>Checkout
</a>
</div>
<tableclass="tabletable-hover">
<tr>
<th>Product</th>
<th>Unitprice</th>
<th>Qauntity</th>
<th>Price</th>
<th>Action</th>
</tr>
<trng-repeat="itemincart.cartItems">
<td>{{item.product.productId}}-{{item.product.name}}</td>
<td>{{item.product.unitPrice}}</td>
<td>{{item.quantity}}</td>
<td>{{item.totalPrice}}</td>
<td><ahref="#"class="labellabel-danger"ng-
click="removeFromCart(item.product.productId)"><span
class="glyphiconglyphicon-remove"/></span>Remove
</a></td>
</tr>
<tr>
<th></th>
<th></th>
<th>GrandTotal</th>
<th>{{cart.grandTotal}}</th>
<th></th>
</tr>
</table>
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-
default">
<spanclass="glyphicon-hand-leftglyphicon"></span>
Continueshopping
</a>
</div>
</section>
</body>
</html>
4. Openproduct.jspfromsrc/main/webapp/WEB-INF/views/andaddthefollowing
AngularJS-relatedscriptlinksintheheadsectionasfollows:
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.
js">
</script>
5. Similarly,inthesameheadsectionaddonemorescriptlinkstoourcontroller.js
inthefollowingmanner:
<scriptsrc="/webstore/resource/js/controllers.js"></script>
6. Now,addtheng-clickAngularJsdirectivetotheOrderNow<a>tagasfollows:
<ahref="#"class="btnbtn-warningbtn-large"ng-
click="addToCart('${product.productId}')">
<spanclass="glyphicon-shopping-cartglyphicon"></span>OrderNow
</a>
7. Finally,addonemore<a>tagasfollowsbesidestheOrderNow<a>tagtoshowthe
ViewCartbutton,thensavetheproduct.jsppage:
<ahref="<spring:urlvalue="/cart"/>"class="btnbtn-default">
<spanclass="glyphicon-hand-rightglyphicon"></span>ViewCart
</a>
8. Nowaddtheng-appAngularJSdirectivetotheOrderNow<section>tagas
follows:
<sectionclass="container"ng-app="cartApp">
9. Then,addtheng-controllerAngularJSdirectivetothesurrounding<p>tagofthe
OrderNowlinkasfollows:
<png-controller="cartCtrl">
10. Now,runyourapplicationandentertheURL
http://localhost:8080/webstore/products/product?id=P1234.Youwillbeable
toseetheproductdetailspageofaproductwhoseproductIDisP1234.
11. NowclickontheOrderNowbutton;analertmessagewillbeshownthatsays
Productsuccessfullyaddedtothecart!!.
12. Now,clickontheViewCartbutton;youwillseeawebpagethatshowsashopping
cartpage,asshowninthefollowingscreenshot:
Theshoppingcartpage
Whatjusthappened?
ThereareplentyofJavaScriptframeworksavailabletosendanAjaxrequesttotheserver;
wedecidedtouseAngularJs(http://angularjs.org/)asourfrontendJavaScriptlibraryto
sendAjaxrequests.ItalsohastheconceptsofModel,View,Controller,andsoon,butthe
onlydifferenceisthatitisdesignedtoworkinthefrontendusingJavaScript.
Instep1,wecreatedourAngularJS-basedcontrollercalledcontrollers.jsin
/src/main/webapp/resources/js/.Rememberthatwepurposelyputthisfileunderthe
resourcesdirectorybecausefromtheclientside,wewanttoaccessthisfileasastatic
resource;wedon’twanttogothroughSpringMVCcontrollersinordertogetthisfile.If
youremembercorrectly,wehavealreadyconfiguredthelocationoftheSpringMVC’s
resourcesinDispatcherServlet-context.xmlinthepreviouschapters;itwasdoneinthe
followingmanner:
<mvc:resourceslocation="/resources/"mapping="/resource/**"/>
Okay!Let’sgettothepoint;whathavewewrittenincontrollers.js?Wehavewritten
fivefrontendcontrollermethods,namelyrefreshCart,clearCart,initCartId,
addToCart,andremoveFromCart.Thesemethodsareusedtocommunicatewiththeserver
usingAjaxcalls.Forexample,considerthefollowingcontrollermethod:
$scope.refreshCart=function(cartId){
$http.get('/webstore/rest/cart/'+$scope.cartId)
.success(function(data){
$scope.cart=data;
});
};
WithintherefreshCartmethod,usingAngularJS’$httpobject,wesentanHTTPGET
requesttotheURItemplate/webstore/rest/cart/'+$scope.cartId.Basedonthevalue
storedinthe$scope.cartIdvariable,theactualrequestissenttothetargetRESTURL.
Forinstance,if$scope.cartIdcontainsavalueof1234,thenaGETrequestwillbesentto
http://localhost:8080/webstore/rest/cart/1234togetacartobjectasJSONdata
whoseIDis1234.OncewegotthecartobjectasJSONdata,westoreditinthefrontend
Angularmodelusingthe$scopeobjectasfollows:
.success(function(data){
$scope.cart=data;
}
Similarly,allotherAngularJScontrollermethodsfiresomeAjaxwebrequeststothe
server,andretrieveorupdatecart.Forexample,theaddToCartandremoveFromCart
methodsjustaddedcartItemandremovedcartItemfromthecartobject.
WehavejustdefinedourAngularJScontrollermethods,butwehavetoinvokethis
methodinordertodosomethinguseful.Thisiswhatwedidinstep2;wejustdefinedour
regularSpringMVCcontrollernamedCartController,whichhastworequestmapping
methods,namelygetandgetCart.WheneverausualwebrequestcomestotheURL
http://localhost:8080/webstore/cart,thegetmethodwillbeinvoked,andinsidethe
getmethod,weretrievedthesessionIDanduseditasacartIDtoinvokethegetCart
method.HerewemaintainedthesessionIDasacartID.Considerthefollowingcode
snippet:
@RequestMapping
publicStringget(HttpServletRequestrequest){
return"redirect:/cart/"+request.getSession(true).getId();
}
WithinthegetCartmethod,wesimplystoredcartIdintheSpringMVCmodeland
returnedaviewnameascart.Wedidthiskindofsetupbecausewewantourapplication
toredirecttherequesttothecorrectcartbasedonthesessionIDwheneverarequest
comestohttp://localhost:8080/webstore/cart.Sincewehavereturnedaviewname
ascart,ourdispatcherservletdefinitelywouldlookforaviewfilecalledcart.jsp.This
iswhywecreatedthecart.jspfileinstep3.
Thecart.jspfilejustactsasatemplateforourshoppingcartpage.Thecart.jsppage
internallyusestheAngularJscontroller’smethodsthatwecreatedinstep1to
communicatewiththeserver.Theng-repeatdirectiveofAngularJSwouldrepeatthe
HTMLtablerowsdynamicallybasedoncartItemsthatareavailableincart:
<trng-repeat="itemincart.cartItems">
<td>{{item.product.productId}}-{{item.product.name}}</td>
<td>{{item.product.unitPrice}}</td>
<td>{{item.quantity}}</td>
<td>{{item.totalPrice}}</td>
<td><ahref="#"class="labellabel-danger"ng-
click="removeFromCart(item.product.productId)">
<spanclass="glyphiconglyphicon-remove"/></span>Remove
</a></td>
</tr>
Theng-clickdirectivefromtheremove<a>tagwillcalltheremoveFromCartcontroller
method.Similarly,toaddcartItemtothecart,inproduct.jsp,weaddedanotherng-
clickdirectiveinstep6toinvoketheaddToCartmethod;wedidthisinthefollowing
manner:
<ahref="#"class="btnbtn-warningbtn-large"ng-
click="addToCart('${product.productId}')"><span
class="glyphicon-shopping-cartglyphicon"></span>OrderNow
</a>
Sothat’sit!Wehavedoneeverythingtorolloutourshoppingcartinourapplication,
Afterrunningourapplication,wecanaccessourshoppingcartundertheURL
http://localhost:8080/webstore/cartandwecanevenaddproductstothecartfrom
eachproductdetailspageaswell.
Summary
Inthischapter,welearnedhowtodevelopREST-basedwebservicesusingSpringMVC,
andwealsolearnedhowtotestthosewebservicesusingthePostmanHTTPclienttool.
WealsocoveredthebasicconceptsofHTTPverbsandunderstoodhowitisrelatedto
standardCRUDoperations.Finally,welearnedhowtousetheAngularJsJavaScript
frameworktosendanAjaxrequesttoourserver.
Inthenextchapter,wewillseehowtointegratetilesandwebflowframeworks.
Chapter9.ApacheTilesandSpringWeb
FlowinAction
Whenitcomestowebapplicationdevelopment,reusabilityandmaintenancearetwo
importantfactorsthatneedtobeconsidered.SpringWebFlowisanindependent
frameworkthatfacilitatesyoutodevelophighlyconfigurableandmaintainableflow-
basedwebapplications.Ontheotherhand,ApacheTilesisanotherpopularopensource
frameworkthatencouragestheuseofreusabletemplate-basedwebapplication
development.
Inthischapter,wearegoingtolearnhowtoincorporatethesetwoframeworkswithina
SpringMVCapplicationsothatwecanobtainmaximumreusabilityoffrontendtemplates
withthehelpofApacheTilesandlessmaintenanceinourapplicationlogicwiththehelp
ofSpringWebFlow.Again,rememberthatthesetwoframeworksaretotallyindependent;
thereisnorequirementthatweshouldalwaysusetheseframeworkstogether.Apache
Tilesismostlyusedtoreduceredundantcodeinthefrontendbyleveragingfrontend
templates,whereasSpringWebFlowfacilitatesthedevelopmentofastatefulweb
applicationwithcontrollednavigationflow.Afterfinishingthischapter,youwillhavea
clearideaaboutthefollowingconcepts:
Developingflow-basedapplicationsusingSpringWebFlow
DecomposingpagesusingreusableApacheTilestemplates
WorkingwithSpringWebFlow
SpringWebFlowfacilitatesustodevelopaflow-basedwebapplicationeasily.Aflowina
webapplicationencapsulatesaseriesofstepsthatguidesauserthroughtheexecutionofa
businesstask,suchascheckingintoahotel,applyingforajob,andshoppingcart
checkout.Usually,aflowwillhaveaclearstartandendpoint.ItincludesmultipleHTTP
requests/responses,andtheusermustgothroughasetofscreensinaspecificorderto
completetheflow.
Inallourpreviouschapters—theresponsibilityofdefiningthepage(userinterface)flow
specificallyliesoncontrollers—weweavedthepageflowsintoindividualcontrollersand
views;forinstance,weusuallymappedawebrequesttoacontroller,andthecontroller
wastheonethatdecidedwhichlogicalviewneededtobereturnedasaresponse.Thisis
simpletounderstandandsufficientforstraightforwardpageflows,butwhenweb
applicationsgetmoreandmorecomplexintermsofuserinterfaceflows,maintaininga
largeandcomplexpageflowbecomesanightmare.
Ifyouaregoingtodevelopsuchacomplexflow-basedapplication,thenSpringWeb
Flow(SWF)canbeagoodcompanion.SpringWebFlowallowsyoutodefineand
executeuserinterface(UI)flowswithinyourwebapplication.Withoutfurtherado,let’s
divestraightintoSpringWebFlowbydefiningsomepageflowsinourproject.
Itisnicethatwehaveimplementedtheshoppingcartinourpreviouschapter;however,it
isofnouseifwedonotprovideacheckoutfacilitytocompletetheprocessingofthe
orderandthenshiptheproductstotherightcustomers.Let’sdothatintwophases.Firstly,
let’screatetherequiredbackendservices,domainobjects,andrepositoryimplementation
inordertocompletetheorderprocessing(here,strictlynothingrelatedtoSpringWeb
Flowisinvolved;itisjustasupportivebackendservicethatcanbeusedlaterbytheweb
flowdefinitioninordertocompletethecheckoutprocess).Secondly,weneedtodefine
theactualSpringWebFlowdefinitionthatcanuseourbackendservicesinorderto
executetheflowdefinition.Here,wewillsettheconfigurationanddefinitionoftheactual
webflow.
Timeforaction–implementingtheorder-
processingservice
Wewillstartwithimplementingourorderprocessingbackendservicefirst.Weproceedas
follows:
1. CreateaclassnamedAddressunderthecom.packt.webstore.domainpackageinthe
sourcefoldersrc/main/javaandaddthefollowingcodeintoit.NotethatIhave
skippedthegetters,setters,equals,andhashcodemethodsinthefollowingsnippet.
Pleasedoaddthosewhenyoucreatethisclass:
packagecom.packt.webstore.domain;
importjava.io.Serializable;
publicclassAddressimplementsSerializable{
privatestaticfinallongserialVersionUID=-530086768384258062L;
privateStringdoorNo;
privateStringstreetName;
privateStringareaName;
privateStringstate;
privateStringcountry;
privateStringzipCode;
//addgettersandsettersforallthefieldshere.
//OverrideequalsandhashCodebasedonallthefields.
//thecodeforthesameisavailableinthecodebundlewhichcan
bedownloadedfromwww.packtpub.com/support
}
2. CreateanotherclassnamedCustomerunderthesamepackageandaddthefollowing
codeintoit:
packagecom.packt.webstore.domain;
importjava.io.Serializable;
publicclassCustomerimplementsSerializable{
privatestaticfinallongserialVersionUID=2284040482222162898L;
privateStringcustomerId;
privateStringname;
privateAddressbillingAddress;
privateStringphoneNumber;
publicCustomer(){
super();
this.billingAddress=newAddress();
}
publicCustomer(StringcustomerId,Stringname){
this();
this.customerId=customerId;
this.name=name;
}
//addgettersandsettersforallthefieldshere.
//OverrideequalsandhashCodebasedoncustomerIdfield.
//thecodeforthesameisavailableinthecodebundlewhichcan
bedownloadedfromwww.packtpub.com/support
}
3. CreateonemoredomainclassnamedShippingDetailunderthesamepackageand
addthefollowingcodeintoit:
packagecom.packt.webstore.domain;
importjava.io.Serializable;
importjava.util.Date;
importorg.springframework.format.annotation.DateTimeFormat;
publicclassShippingDetailimplementsSerializable{
privatestaticfinallongserialVersionUID=6350930334140807514L;
privateStringname;
@DateTimeFormat(pattern="dd/MM/yyyy")
privateDateshippingDate;
privateAddressshippingAddress;
publicShippingDetail(){
this.shippingAddress=newAddress();
}
//addgettersandsettersforallthefieldshere.
}
4. Similarly,createourfinaldomainclassnamedOrderunderthesamepackageand
addthefollowingcodeintoit:
packagecom.packt.webstore.domain;
importjava.io.Serializable;
publicclassOrderimplementsSerializable{
privatestaticfinallongserialVersionUID=-3560539622417210365L;
privateLongorderId;
privateCartcart;
privateCustomercustomer;
privateShippingDetailshippingDetail;
publicOrder(){
this.customer=newCustomer();
this.shippingDetail=newShippingDetail();
}
//addgettersandsettersforallthefieldshere.
//OverrideequalsandhashCodebasedonorderIdfield.
//thecodeforthesameisavailableinthecodebundlewhichcan
bedownloadedfromwww.packtpub.com/support
}
5. NowmaketheProduct,CartItem,andCartdomainclassesserializableby
implementingtheSerializableinterface(java.io.Serializable)forallofthese
classes;wealsoneedtoaddaserialVersionUIDfieldtoeachoftheearlier
mentionedclassesaswell.
6. Next,createaninterfacenamedOrderRepositoryunderthe
com.packt.webstore.domain.repositorypackageinthesourcefolder
src/main/java,andaddasinglemethoddeclarationtoitasfollows:
LongsaveOrder(Orderorder);
7. CreateanimplementationclasscalledInMemoryOrderRepositoryImplunderthe
com.packt.webstore.domain.repository.implpackageinthesourcefolder
src/main/javaandaddthefollowingcodeintoit:
packagecom.packt.webstore.domain.repository.impl;
importjava.util.HashMap;
importjava.util.Map;
importorg.springframework.stereotype.Repository;
importcom.packt.webstore.domain.Order;
importcom.packt.webstore.domain.repository.OrderRepository;
@Repository
publicclassInMemoryOrderRepositoryImplimplementsOrderRepository{
privateMap<Long,Order>listOfOrders;
privatelongnextOrderId;
publicInMemoryOrderRepositoryImpl(){
listOfOrders=newHashMap<Long,Order>();
nextOrderId=1000;
}
publicLongsaveOrder(Orderorder){
order.setOrderId(getNextOrderId());
listOfOrders.put(order.getOrderId(),order);
returnorder.getOrderId();
}
privatesynchronizedlonggetNextOrderId(){
returnnextOrderId++;
}
}
8. Now,opentheOrderServiceinterfacefromthecom.packt.webstore.service
packageinthesourcefoldersrc/main/javaandaddsinglemethoddeclarationstoit
asfollows:
packagecom.packt.webstore.domain.repository;
importcom.packt.webstore.domain.Order;
publicinterfaceOrderRepository{
LongsaveOrder(Orderorder);
}
}
9. Next,opentheimplementationclassOrderServiceImplfromthe
com.packt.webstore.service.implpackageinthesourcefoldersrc/main/java
andaddthefollowingtwoautowiredreferencestoit:
@Autowired
privateOrderRepositoryorderRepository;
@Autowired
privateCartServicecartService;
10. NowaddamethodimplementationforthesaveOrdermethodasfollowsinthe
OrderServiceImplclass:
publicLongsaveOrder(Orderorder){
LongorderId=orderRepository.saveOrder(order);
cartService.delete(order.getCart().getCartId());
returnorderId;
}
11. Next,createanexceptionclasscalledInvalidCartExceptionunderthe
com.packt.webstore.exceptionpackageinthesourcefoldersrc/main/javaand
addthefollowingcodeintoit:
packagecom.packt.webstore.exception;
publicclassInvalidCartExceptionextendsRuntimeException{
privatestaticfinallongserialVersionUID=-5192041563033358491L;
privateStringcartId;
publicInvalidCartException(StringcartId){
this.cartId=cartId;
}
publicStringgetCartId(){
returncartId;
}
}
12. Now,opentheCartServiceinterfacefromthecom.packt.webstore.service
packageinthesourcefoldersrc/main/javaandaddonemoremethoddeclarationto
itasfollows:
Cartvalidate(StringcartId);
13. Next,opentheimplementationclassCartServiceImplfromthepackage
com.packt.webstore.service.implinthesourcefoldersrc/main/javaandadda
methodimplementationforthevalidatemethodasfollows:
publicCartvalidate(StringcartId){
Cartcart=cartRepository.read(cartId);
if(cart==null||cart.getCartItems().size()==0){
thrownewInvalidCartException(cartId);
}
returncart;
}
Whatjusthappened?
WhatwehavedonesofarisfamiliartoyouIguess;wecreatedsomedomainclasses
(Address,Customer,ShippingDetail,andOrder)aswellasanOrderRepository
interfaceanditsimplementationclassInMemoryOrderRepositoryImpltostorethe
processedOrderdomainobjects.Finally,wealsocreatedthecorrespondingOrderService
interfaceanditsimplementationclass,namelyOrderServiceImpl.
Onthesurface,itlooksasusual,buttherearesomeminutedetailsthatneedtobe
explained.Ifyounotice,allthedomainclasseswecreatedfromsteps1to4havejust
implementedtheSerializableinterface;notonlythat,weevenimplementedthe
Serializableinterfaceforotherexistingdomainclassesaswell,suchasProduct,
CartItem,andCartinstep5.Thisisbecauselaterwewillusethesedomainobjectsin
SpringWebFlow,andSpringWebFlowwillstorethesedomainobjectsinasessionto
managethestatebetweenpageflows.Sessiondatacouldbesavedintoadiskor
transferredtoanotherwebserverduringclustering.So,whilebringingbackthesession
objectfromthedisk,SpringWebFlowwilldeserializethedomainobject(form-backing
bean)tomaintainthestateofthepage.That’swhyitisamusttoserializethedomain
object/formbackingbean.SpringWebFlowusesatermcalledSnapshottomention
thesestateswithinasession.
Theremainingstepsfromstep6to13areself-explanatory.Wecreatedthe
OrderRepositoryandOrderServiceinterfacesandtheircorresponding
InMemoryOrderRepositoryImplandOrderServiceImplimplementations.Thepurposeof
theseclassesistosavetheOrderdomainobject.ThesaveOrdermethodfrom
OrderServiceImpljustdeletesthecorrespondingCartobjectfromCartRepositoryafter
successfullysavingtheorderdomainobject.Now,wehavesuccessfullycreatedallthe
requiredbackendservicesanddomainobjectsinordertokickofftheconfigurationand
definitionofourSpringWebFlow.
Timeforaction–implementingthe
checkoutflow
WewillnowaddSpringWebFlowsupporttoourprojectanddefinethecheckoutflowfor
ourshoppingcart.Considerthefollowingsteps:
1. Openpom.xml;youwillfindpom.xmlundertherootdirectoryoftheproject.
2. Youwillbeabletoseesometabsatthebottomofthepom.xmlfile.Selectthe
DependenciestabandclickontheAddbuttonintheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdas
org.springframework.webflow,ArtifactIdasspring-webflow,Versionas
2.3.3.RELEASE,selectScopeascompile,andclickontheOKbuttonandsave
pom.xml.
4. Createadirectorystructure,flows/checkout/,underthedirectory
src/main/webapp/WEB-INF/andcreateanXMLfilecalledcheckout-flow.xml.
Then,addthefollowingcontentintoitandsaveit:
<?xmlversion="1.0"encoding="UTF-8"?>
<flowxmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<varname="order"class="com.packt.webstore.domain.Order"/>
<action-stateid="addCartToOrder">
<evaluate
expression="cartServiceImpl.validate(requestParameters.cartId)"
result="order.cart"/>
<transitionto="InvalidCartWarning"
on-exception="com.packt.webstore.exception.InvalidCartException"
/>
<transitionto="collectCustomerInfo"/>
</action-state>
<view-stateid="collectCustomerInfo"view="collectCustomerInfo.jsp"
model="order">
<transitionon="customerInfoCollected"to="collectShippingDetail"
/>
</view-state>
<view-stateid="collectShippingDetail"model="order">
<transitionon="shippingDetailCollected"to="orderConfirmation"/>
<transitionon="backToCollectCustomerInfo"to="collectCustomerInfo"
/>
</view-state>
<view-stateid="orderConfirmation">
<transitionon="orderConfirmed"to="processOrder"/>
<transitionon="backToCollectShippingDetail"
to="collectShippingDetail"/>
</view-state>
<action-stateid="processOrder">
<evaluateexpression="orderServiceImpl.saveOrder(order)"
result="order.orderId"/>
<transitionto="thankCustomer"/>
</action-state>
<view-stateid="InvalidCartWarning">
<transitionto="endState"/>
</view-state>
<view-stateid="thankCustomer"model="order">
<transitionto="endState"/>
</view-state>
<end-stateid="endState"/>
<end-stateid="cancelCheckout"view="checkOutCancelled.jsp"/>
<global-transitions>
<transitionon="cancel"to="endState"/>
</global-transitions>
</flow>
5. NowopenthewebapplicationcontextconfigurationfileDispatcherServlet-
context.xmlandaddthefollowingwebflownamespaceattributetothe<beans>tag
atthetop:
xmlns:webflow-config="http://www.springframework.org/schema/webflow-
config"
6. Appendthefollowingschemalocationentrytotheexistingxsi:schemaLocation
attributeofthe<beans>tag:
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-
config-2.3.xsd
7. Nowaddthefollowingwebflowconfigurationtagstothewebapplicationcontext
configurationfile(DispatcherServlet-context.xml):
<webflow-config:flow-executorid="flowExecutor"flow-
registry="flowRegistry"/>
<webflow-config:flow-registryid="flowRegistry"base-path="/WEB-
INF/flows">
<webflow-config:flow-locationpath="/checkout/checkout-flow.xml"
id="checkout"/>
</webflow-config:flow-registry>
8. Finally,definethebeansforFlowHandlerMappingandFlowHandlerAdapterin
DispatcherServlet-context.xmlasfollowsandsavethefile:
<beanid="flowHandlerMapping"
class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<propertyname="flowRegistry"ref="flowRegistry"/>
</bean>
<beanid="flowHandlerAdapter"
class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<propertyname="flowExecutor"ref="flowExecutor"/>
</bean>
Whatjusthappened?
Fromsteps1to3,wejustaddedtheSpringWebFlowdependencytoourprojectthrough
aMavenconfiguration.Itwilldownloadandconfigurealltherequiredjarsrelatedtoweb
flowinourproject.Instep4,wecreatedourfirstflowdefinitionfilecalledcheckout-
flow.xmlunderthedirectory/src/main/webapp/WEB-INF/flows/checkout/.
SpringWebFlowusestheflowdefinitionfileasabasistoexecutetheflow.Inorderto
understandwhathasbeenwritteninthisfile,weneedtogetaclearideaaboutsomeofthe
basicconceptsofSpringWebFlow.Wewilllearnaboutthoseconceptsalittlebitandthen
wewillcomebacktocheckout-flow.xmltounderstandit.
Understandingtheflowdefinition
Aflowdefinitioniscomposedofasetofstates.EachstatewillhaveauniqueIDinthe
flowdefinition.TherearesixtypesofstatesavailableinSpringWebFlow:
start-state:Eachflowmusthaveasinglestartstatethathelpsinthecreationofthe
initialstateoftheflow.Notethatifthestartstateisnotspecified,theveryfirst
definedstatewithintheflowdefinitionfilebecomesthestartstate.
action-state:Aflowcanhavemanyactionstates,andanactionstateexecutesa
particularaction.Anactionnormallyinvolvesinteractionwithbackendservices,
suchasexecutingsomemethodsinaSpring-managedbean;SpringWebFlowuses
SpringExpressionLanguagetointeractwithbackendservicebeans.
view-state:Aviewstatedefinesalogicalviewandmodeltointeractwiththeend
user.Awebflowcanhavemultipleviewstates.Iftheviewattributeisnotspecified,
thentheIDoftheviewstateactsasthelogicalviewname.
decision-state:Thedecisionstateisusedtobranchtheflow;basedonthetest
condition,itroutesthetransitiontothenextpossiblestate.
subflow-state:Asubflowisanindependentflowthatcanbereusedfrominside
anotherflow.Whenanapplicationentersasubflow,themainflowispauseduntilthe
concernedsubflowiscompleted.
end-state:Anendstatedenotestheendofaflow’sexecution.Awebflowcanhave
multipleendstates,andthroughtheviewattributeofend-state,wecanspecifya
viewthatwillberenderedwhentheendstateisreached.
Wehavejustlearnedthataflowdefinitioniscomposedofasetofstates,butinorderto
makeamovefromonestatetoanother,weneedtodefinetransitionsinthesestates.Each
stateinawebflow(exceptforthestartandendstates)definesanumberoftransitionsto
movefromonestatetoanother.Atransitioncanbetriggeredbyaneventthatissignaled
fromthestate.
Understandingthecheckoutflow
Okay!WejustgottheminimumrequiredintroductionforSpringWebFlowconcepts.
ThereareplentyofadvancedconceptsouttheretomasterinSpringWebFlow;wearenot
goingtolookatallofthosebecausethatitselfdeservesaseparatebook.Asofnow,itis
enoughtounderstandthecheckout-flow.xmlflowdefinitionfile.However,beforewedo
this,wewillhaveaquickoverviewofourcheckoutflow.Thefollowingdiagramwillgive
youtheoverallideaofthecheckoutflowthatwehavejustimplemented:
Ourcheckoutflowdiagramhasastartstateandanendstate;eachroundedrectangleinthe
diagramdefinesanactionstate,andeachdouble-line-borderedroundedrectangledefines
aviewstate.Similarly,eacharrowedlinedefinesatransition,andthenameassociated
withitdefinestheeventthatcausesthatparticulartransition.Thecheckout-flow.xmlfile
justcontainstheearliermentionedflowinanXMLrepresentation.
Ifyouopenthecheckout-flow.xmlfile,thefirsttagyouwillencounterwithinthe<flow>
tagisthe<var>tag:
<varname="order"class="com.packt.webstore.domain.Order"/>
The<var>tagcreatesavariableinaflow.Thisvariablewillbeavailabletoallthestates
inaflow,whichmeanswecanreferenceandusethisvariableinsideanystatewithinthe
flow.Inthejustmentioned<var>tag,wecreatedanewinstanceoftheOrderclassand
storeditinavariablecalledorder.
Thenextthingwedefinedwithinthecheckout-flow.xmlfilewasthe<action-state>
definition;asalreadylearned,actionstatesarenormallyusedtoinvokebackendservices.
Inthefollowing<action-state>definition,wewillinvokethevalidatemethodofthe
cartServiceImplobjectandstoretheresultintheorder.cartobject:
<action-stateid="addCartToOrder">
<evaluateexpression
="cartServiceImpl.validate(requestParameters.cartId)"
result="order.cart"/>
<transitionto="InvalidCartWarning"on-exception
="com.packt.webstore.exception.InvalidCartException"/>
<transitionto="collectCustomerInfo"/>
</action-state>
Asalreadydefined,theordervariableatthestartoftheflowwillbeavailableinevery
stateofthisflow.So,wehaveusedthatvariable(order.cart)inthe<evaluate>tagto
storetheresultoftheevaluatedexpression,namely
cartServiceImpl.validate(requestParameters.cartId).
ThevalidatemethodofcartServiceImplwilltrytoreadacartobjectbasedonthe
givencartId.Ifitfindsavalidcartobject,thenitwillreturnit;otherwise,itwillthrow
InvalidCartException.Insuchacase,wewillroutethetransitiontoanotherstatewhose
IDisInvalidCartWarning:
<transitionto="InvalidCartWarning"on-exception=
"com.packt.webstore.exception.InvalidCartException"/>
Ifsuchanexceptionisnotthrownfromtheexpressionevaluation,wewillnaturallytransit
fromtheaddCartToOrderstatetothecollectCustomerInfostate,asfollows:
<transitionto="collectCustomerInfo"/>
IfyounoticethecollectCustomerInfostate,itisnothingbutaviewstateincheckout-
flow.xml.Wedefinedtheviewthatneedstoberenderedviatheviewattributeandthe
modelthatneedstobeattachedviathemodelattribute,asfollows:
<view-stateid="collectCustomerInfo"view="collectCustomerInfo.jsp"
model="order">
<transitionon="customerInfoCollected"to="collectShippingDetail"/>
</view-state>
Uponreachingthisviewstate,SpringWebFlowwillrenderthecollectCustomerInfo
viewandwaitfortheusertointeract.Oncetheuserentersthecustomerinfodetailsand
pressesthesubmitbutton,itwillresumeitstransitiontothecollectShippingDetail
viewstate.Asalreadylearned,atransitioncanbetriggeredviaanevent,soherethe
transitiontothecollectShippingDetailstatewouldgettriggeredwhenthe
customerInfoCollectedeventistriggered.Buthowdoyoufirethisevent
(customerInfoCollected)fromtheview?Wewillseethislaterinthechapter.Consider
thefollowingcodesnippet:
<transitionon="customerInfoCollected"to="collectShippingDetail"/>
ThenextstatedefinedwithinthecheckoutflowiscollectShippingDetail.Again,thisis
alsoaviewstate,andithastwotransitionsbackandforth;oneistogobacktothe
collectCustomerInfostateandthenextistogoforwardtotheorderConfirmationstate,
asshownasfollows:
<view-stateid="collectShippingDetail"model="order">
<transitionon="shippingDetailCollected"to="orderConfirmation"/>
<transitionon="backToCollectCustomerInfo"to="collectCustomerInfo"/>
</view-state>
Noteherethatwehaven’tmentionedtheviewattributeinthecollectShippingDetail
state;inthiscase,SpringWebFlowwouldconsidertheIDoftheviewstateasitsview
name.
TheorderConfirmationstatedefinitiondoesn’tneedmuchexplanation;itismorelike
thecollectShippingDetailviewstatewherewefurnishalltheorder-relateddetailsand
thenasktheusertoconfirm.Uponconfirmation,wemovetothenextstate,thatis,
processOrder:
<view-stateid="orderConfirmation">
<transitionon="orderConfirmed"to="processOrder"/>
<transitionon="backToCollectShippingDetail"to="collectShippingDetail"
/>
</view-state>
Next,theprocessOrderstateisanactionstatethatinteractswiththeorderServiceImpl
objecttosavetheorderobject.Uponsuccessfullysavingtheorderobject,itstoresthe
orderIDintheflowvariable(order.orderId)andtransitsittothenextstate,whichis
thankCustomer:
<action-stateid="processOrder">
<evaluateexpression="orderServiceImpl.saveOrder(order)"
result="order.orderId"/>
<transitionto="thankCustomer"/>
</action-state>
ThethankCustomerstateisaviewstatethatsimplyshowsathankyoumessagewiththe
confirmedorderIDtotheenduserandtransitsittotheendstate,asfollows:
<view-stateid="thankCustomer"model="order">
<transitionto="endState"/>
</view-state>
Inourcheckoutflow,wehavetwoendstates:oneisthenormalendstatewheretheflow
executionarrivesnaturallyaftertheflowends,andtheotheroneistheendstatethatis
reachedwhentheuserpressesthecancelbuttoninanyoneoftheviews.Considerthe
followingcodesnippet:
<end-stateid="endState"/>
<end-stateid="cancelCheckout"view="checkOutCancelled.jsp"/>
NotethatinthecancelCheckoutendstate,wespecifiedthenameofthelandingpagevia
theviewattribute,andthetransitiontothecancelCheckoutendstatehappenedthrough
theglobal-transitionsconfiguration:
<global-transitions>
<transitionon="cancel"to="cancelCheckout"/>
</global-transitions>
Aglobaltransitionisforsharingsomecommontransitionsbetweenstates.Insteadof
repeatingthetransitiondefinitioneverytimewithinthestatedefinition,wecandefine
themwithinoneglobaltransitionsothatthattransitionwillbeavailableimplicitlyfor
everystateintheflow.Inourcase,theendusermaycancelthecheckoutprocessinany
state;thisiswhywedefinedthetransitiontothecancelCheckoutstateinglobal-
transitions.
Wehavetotallyunderstoodthecheckoutflowdefinition(checkout-flow.xml).Nowour
SpringMVCshouldreadthisfileduringthebootupofourapplicationsothatitcanbe
readytodispatchanyflow-relatedrequeststotheSpringWebFlowframework.Wewill
beabletodothisviasomewebflowconfigurationtagsinthewebapplicationcontext
(DispatcherServlet-context.xml)asmentionedinsteps5to8.
Insteps5and6,weaddedtherequiredwebflow-confignamespaceandschemalocation
inDispatcherServlet-context.xml.Instep7,wecreatedflow-executorandflow-
registry.Asthenameimplies,flow-executorexecutesaflowbasedonthegivenflow
definition.Theflow-executorconfigurationtaggetsitsflowdefinitionfromflow-
registry.Wecanconfigureasmanyflowdefinitionsaswewantinflow-registry.A
flow-registryconfigurationtagisacollectionofflowdefinitions.Whenauserentersa
flow,theflowexecutorcreatesandlaunchesanexclusiveflowinstanceforthatuserbased
ontheflowdefinition:
<webflow-config:flow-executorid="flowExecutor"flow-
registry="flowRegistry"/>
<webflow-config:flow-registryid="flowRegistry"base-path="/WEB-
INF/flows">
<webflow-config:flow-locationpath="/checkout/checkout-flow.xml"
id="checkout"/>
</webflow-config:flow-registry>
Intheprecedingwebflowconfiguration,wecreatedflow-registrywhosebase-pathis
/WEB-INF/flows,soweneedtoputallourflowdefinitionsunderthe/WEB-INF/flows
directoryinordertobepickedupbyflow-registry.That’swhy,instep4,wecreatedour
checkout-flow.xmlfileunderthedirectorysrc/main/webapp/WEB-
INF/flows/checkout/.Asalreadymentioned,flow-registrycanhavemanyflow
definitions,andeachflowdefinitionisidentifiedbyitsIDwithinflow-registry.Inour
case,weaddedasingleflowdefinitionwhoseIDischeckoutandwhoserelativelocation
is/checkout/checkout-flow.xml.Rememberthatthepathattributeofa<webflow-
config:flow-location>tagisrelativetothebase-pathattributeofthe<webflow-
config:flow-registry>tag.
Oneimportantthingtounderstandbeforewewindupthewebflowconfigurationisthat
theIDofaflowdefinitionformstherelativeURLtoinvoketheflow.Bythis,whatImean
isthatinordertoinvoketheflowofourcheckoutviaawebrequest,weneedtofireaGET
requesttotheURLhttp://localhost:8080/webstore/checkoutbecauseourflowIDis
checkout.Moreover,inourflowdefinition(checkout-flow.xml),wehaven’tconfigured
anystartstate,sothefirststatedefinition(theaddCartToOrderactionstate)willbecome
thestartstate;also,theaddCartToOrderactionstateexpectingcartIdshouldbepresent
intherequestparameteroftheinvokingURL,whichisshownasfollows:
<action-stateid="addCartToOrder">
<evaluateexpression=
"cartServiceImpl.validate(requestParameters.cartId)"result="order.cart"/>
<transitionto="InvalidCartWarning"on-
exception="com.packt.webstore.exception.InvalidCartException"/>
<transitionto="collectCustomerInfo"/>
</action-state>
So,theactualURLthatcaninvokethisflowwouldbesomethingsimilarto
http://localhost:8080/webstore/checkout?cartId=55AD1472D4EC,wherethepart
afterthequestionmark(cartId=55AD1472D4EC)isconsideredasarequestparameter.
ItisgoodthatwehavedefinedourcheckoutflowandconfigureditwithSpringWeb
Flow;however,weneedtodefinetwomorebeansinourwebapplicationcontext
(DispatcherServlet-context.xml)todispatchallflow-invokingrequeststoflow-
executor.Wedidthisinstep8:
<beanid="flowHandlerMapping"class=
"org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<propertyname="flowRegistry"ref="flowRegistry"/>
</bean>
<beanid="flowHandlerAdapter"class=
"org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<propertyname="flowExecutor"ref="flowExecutor"/>
</bean>
TheflowHandlerMappingparametercreatesandconfiguresahandlermappingbasedon
theflowIDforeachdefinedflowfromflowRegistry.TheflowHandlerAdapteractsasa
bridgebetweenthedispatcherservletandSpringWebFlowinordertoexecutethe
instancesoftheflow.
Popquiz–webflow
Q1.Considerthefollowingwebflowregistryconfiguration;ithasasingleflowdefinition
file.HowwillyouformtheURLtoinvoketheflow?
<webflow-config:flow-registryid="flowRegistry"base-path="/WEB-
INF/flows">
<webflow-config:flow-locationpath="/customer/validate.xml"
id="validateCustomer"/>
</webflow-config:flow-registry>
1. http://localhost:8080/webstore/customer/validate
2. http://localhost:8080/webstore/validate
3. http://localhost:8080/webstore/validateCustomer
Q2.Considerthisflow-invokingURL:http://localhost:8080/webstore/validate?
customerId=C1234.Inaflowdefinitionfile,howwillyouretrievethecustomerIdHTTP
requestparameter?
1. <evaluateexpression="requestParameters.customerId"result=
"customerId"/>
2. <evaluateexpression="requestParameters(customerId)"result=
"customerId"/>
3. <evaluateexpression="requestParameters[customerId]"result=
"customerId"/>
Timeforaction–creatingviewsforevery
viewstate
Wehavedoneeverythingtorolloutourcheckoutflow,butonelastthingispending,that
is,creatingalltheviewsthatneedtobeusedintheviewstatesofourcheckoutflow.In
total,wehavesixviewstatesinourflowdefinition(collectCustomerInfo,
collectShippingDetail,orderConfirmation,InvalidCartWarning,thankCustomer,
andcancelCheckout),soweneedtocreatesixJSPfiles.Let’screateallofthem:
1. CreateaJSPviewfilecalledcollectCustomerInfo.jspunderthedirectory
src/main/webapp/WEB-INF/flows/checkout/,andaddthefollowingcodesnippet
intoitandsaveit.Inthefollowingcodesnippet,Ihaveskippedthe<input>tagsfor
someofthefieldsoftheCustomerdomainobject.Youcanfindthecompletecodefor
collectCustomerInfo.jspinthecodebundleofthisbook,whichcanbe
downloadedfromwww.packtpub.com/support.Considerthefollowingcodesnippet:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="form"
uri="http://www.springframework.org/tags/form"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset="utf-
8">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Customer</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Customer</h1>
<p>Customerdetails</p>
</div>
</div>
</section>
<sectionclass="container">
<form:formmodelAttribute="order.customer"class="form-
horizontal">
<fieldset>
<legend>CustomerDetails</legend>
<divclass="form-group">
<labelclass="control-labelcol-lg-2col-lg-2"
for="customerId"/>CustomerId</label>
<divclass="col-lg-10">
<form:inputid="customerId"path="customerId"type="text"
class="form:input-large"/>
</div>
</div>
<!--Similarly,addinputtagsfortheremainingfieldsofthe
customerdomainobject.Ihaveskippedthosetagshere-->
<inputtype="hidden"name="_flowExecutionKey"
value="${flowExecutionKey}"/>
<divclass="form-group">
<divclass="col-lg-offset-2col-lg-10">
<inputtype="submit"id="btnAdd"class="btnbtn-primary"
value="Add"name="_eventId_customerInfoCollected"/>
<buttonid="btnCancel"class="btnbtn-default"
name="_eventId_cancel">Cancel</button>
</div>
</div>
</fieldset>
</form:form>
</section>
</body>
</html>
2. Similarly,createonemoreJSPviewfilecalledcollectShippingDetail.jspunder
thesamedirectory,andaddthefollowingcodesnippetintoitandsaveit.Inthe
followingcodesnippet,Ihaveskippedthe<input>tagsforsomeofthefieldsofthe
Address(shippingAddress)domainobject.Youcanfindthecompletecodefor
collectShippingDetail.jspinthecodebundleofthisbook,whichcanbe
downloadedfromwww.packtpub.com/support.Considerthefollowingcodesnippet:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="form"
uri="http://www.springframework.org/tags/form"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset="utf-
8">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Customer</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Shipping</h1>
<p>Shippingdetails</p>
</div>
</div>
</section>
<sectionclass="container">
<form:formmodelAttribute="order.shippingDetail"class="form-
horizontal">
<fieldset>
<legend>ShippingDetails</legend>
<divclass="form-group">
<labelclass="control-labelcol-lg-2col-lg-2"for="name"
/>Name</label>
<divclass="col-lg-10">
<form:inputid="name"path="name"type="text"
class="form:input-large"/>
</div>
</div>
<divclass="form-group">
<labelclass="control-labelcol-lg-2col-lg-2"
for="shippingDate"/>shippingDate(dd/mm/yyyy)</label>
<divclass="col-lg-10">
<form:inputid="shippingDate"path="shippingDate"
type="text"class="form:input-large"/>
</div>
</div>
<divclass="form-group">
<labelclass="control-labelcol-lg-2"for="doorNo">Door
No</label>
<divclass="col-lg-10">
<form:inputid="doorNo"path="shippingAddress.doorNo"
type="text"
class="form:input-large"/>
</div>
</div>
<!--Similarly,addinputtagsfortheremainingfieldsofthe
shippingAddressdomainobject.Ihaveskippedthosetagshere-->
<inputtype="hidden"name="_flowExecutionKey"
value="${flowExecutionKey}"/>
<divclass="form-group">
<divclass="col-lg-offset-2col-lg-10">
<buttonid="back"class="btnbtn-default"
name="_eventId_backToCollectCustomerInfo">back</button>
<inputtype="submit"id="btnAdd"class="btnbtn-primary"
value="Add"name="_eventId_shippingDetailCollected"/>
<buttonid="btnCancel"class="btnbtn-default"
name="_eventId_cancel">Cancel</button>
</div>
</div>
</fieldset>
</form:form>
</section>
</body>
</html>
3. Also,createonemoreJSPviewfilecalledorderConfirmation.jsptoconfirmthe
orderfromtheuserunderthesamedirectory,andaddthefollowingcodesnippetinto
itandsaveit.Inthefollowingcodesnippet,Ihaveskippedthe<input>tagsforsome
ofthefieldsoftheOrderdomainobject.Youcanfindthecompletecodefor
orderConfirmation.jspinthecodebundleofthisbook,whichcanbedownloaded
fromwww.packtpub.com/support.Considerthefollowingcodesnippet:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="form"
uri="http://www.springframework.org/tags/form"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<%@taglibprefix="fmt"uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset="utf-
8">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>OrderConfirmation</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1>Order</h1>
<p>OrderConfirmation</p>
</div>
</div>
</section>
<divclass="container">
<divclass="row">
<form:formmodelAttribute="order"class="form-horizontal">
<inputtype="hidden"name="_flowExecutionKey"
value="${flowExecutionKey}"/>
<divclass="wellcol-xs-10col-sm-10col-md-6col-xs-offset-1
col-sm-offset-1col-md-offset-3">
<divclass="text-center">
<h1>Receipt</h1>
</div>
<divclass="row">
<divclass="col-xs-6col-sm-6col-md-6">
<address>
<strong>ShippingAddress</strong><br>
${order.shippingDetail.name}<br>
<!--Similarly,furnisheveryfieldoftheorderobjectwithinanhtml
tableusingexpressionnotation"${}".Ihaveskippedthosetagshere-
->
<buttonid="back"class="btnbtn-default"
name="_eventId_backToCollectShippingDetail">back</button>
<buttontype="submit"class="btnbtn-success"
name="_eventId_orderConfirmed">
Confirm<spanclass="glyphiconglyphicon-chevron-
right"></span>
</button>
<buttonid="btnCancel"class="btnbtn-default"
name="_eventId_cancel">Cancel</button>
</div>
</div>
</form:form>
</div>
</div>
</body>
</html>
4. Next,weneedtocreateanotherJSPviewfilecalledInvalidCartWarning.jspto
showanerrormessageinthecaseofanemptycartcheckout;addthefollowingcode
snippettoInvalidCartWarning.jspandsaveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset="utf-
8">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Invalidcart</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1class="alertalert-danger">InvalidCart</h1>
</div>
</div>
</section>
<section>
<divclass="container">
<p>
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-
primary">
<spanclass="glyphicon-hand-leftglyphicon"></span>
products
</a>
</p>
</div>
</section>
</body>
</html>
5. Tothankthecustomerafterasuccessfulcheckoutflow,weneedtocreateonemore
JSPviewfilecalledthankCustomer.jspasfollows:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<%@taglibprefix="fmt"uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset="utf-
8">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Invalidcart</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1class="alertalert-danger">Thankyou</h1>
<p>Thanksfortheorder.yourorderwillbedeliveredtoyou
on
<fmt:formatDatetype="date"
value="${order.shippingDetail.shippingDate}"/>!</p>
<p>YourOrderNumberis${order.orderId}</p>
</div>
</div>
</section>
<section>
<divclass="container">
<p>
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-
primary">
<spanclass="glyphicon-hand-leftglyphicon"></span>
products
</a>
</p>
</div>
</section>
</body>
</html>
6. Iftheusercancelsthecheckoutinanyoftheviews,weneedtoshowthecheckout
cancelledmessage;forthat,weneedtohaveaJSPfilecalled
checkOutCancelled.jspasfollows:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset="utf-
8">
<linkrel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Invalidcart</title>
</head>
<body>
<section>
<divclass="jumbotron">
<divclass="container">
<h1class="alertalert-danger">checkoutcancelled</h1>
<p>YourCheckoutprocesscancelled!youmaycontinue
shopping..</p>
</div>
</div>
</section>
<section>
<divclass="container">
<p>
<ahref="<spring:urlvalue="/products"/>"class="btnbtn-
primary">
<spanclass="glyphicon-hand-leftglyphicon"></span>
products
</a>
</p>
</div>
</section>
</body>
</html>
7. Asthelaststep,opencart.jspfromsrc\main\webapp\WEB-INF\views\andassign
the<spring:urlvalue="/checkout?cartId=${cartId}"/>valueforthehref
attributeofthecheckoutlinkasfollows:
<ahref="<spring:urlvalue="/checkout?cartId=${cartId}"/>"class="btn
btn-successpull-right"><span
class="glyphicon-shopping-cartglyphicon"></span>Checkout
</a>
8. Nowruntheapplicationandenterthehttp://localhost:8080/webstore/products
URL.Next,clickontheDetailsbuttonofanyoftheproductsandclickontheOrder
Nowbuttonontheproductdetailspagetoaddproductstotheshoppingcart.Nowgo
tothecartpagebyclickingontheViewCartbutton;youwillbeabletoseethe
Checkoutbuttononthatpage.ClickontheCheckoutbutton;youwillbeabletosee
awebpageasfollowsforcollectingcustomerinfo:
Customerdetailscollectionform
9. Afterfurnishingallthecustomerdetails,ifyouclickontheAddbutton,SpringWeb
Flowwilltakeyoutothenextviewstate,whichiscollectingtheshippingdetailsand
soonuptoconfirmingtheorder.Uponconfirmingtheorder,SpringWebFlowwill
showyouthethankyoumessageviewastheendstate.
Whatjusthappened?
Whatwehavedonefromstep1to6iskindofsimilar,thatis,creatingtheJSPviewfiles
foreachviewstate.Ifyouremember,wedefinedthemodelattributeforeachviewstatein
checkout-flow.xml.Considerthefollowingcodesnippet:
<view-stateid="collectCustomerInfo"view="collectCustomerInfo.jsp"
model="order">
<transitionon="customerInfoCollected"to="collectShippingDetail"/>
</view-state>
ThismodelobjectwillgetboundtotheviewviamodelAttributeofthe<form:form>tag
asfollows:
<form:formmodelAttribute="order.customer"class="form-horizontal">
<fieldset>
<legend>CustomerDetails</legend>
<divclass="form-group">
<labelclass="control-labelcol-lg-2col-lg-2"for="customerId"
/>CustomerId</label>
<divclass="col-lg-10">
<form:inputid="customerId"path="customerId"type="text"
class="form:input-large"/>
</div>
</div>
IntheprecedingsnippetofcollectCustomerInfo.jsp,youcannoticethatwebounded
the<form:input>tagwiththecustomerIdfieldofthecustomerobject,whichcomes
fromthemodelobject(order.customer).Similarly,weboundedtheshippingDetailand
orderobjectswithcollectShippingDetail.jspandorderConfirmation.jsp
respectively.
It’sgoodthatweboundedtheOrder,Customer,andShippingDetailobjectswiththe
views,butwhatwillhappenafterweclickonthesubmitbuttonineachview,orsaythe
thecancelorbackbuttons.Toknowtheanswer,weneedtoinvestigatethefollowingcode
snippetfromcollectCustomerInfo.jsp:
<inputtype="submit"id="btnAdd"class="btnbtn-primary"value="Add"
name="_eventId_customerInfoCollected"/>
Onthesurface,the<input>tagwejustmentionedactsasasubmitbutton,butthereal
differencecomesfromthenameattribute(name="_eventId_customerInfoCollected").
Wehaveassignedthevalue_eventId_customerInfoCollectedtothenameattributeof
the<input>tagwithapurpose.ThepurposeistoinstructSpringWebFlowtoraisean
eventoncethisformissubmitted.Whensubmittingthisform,SpringWebFlowwillraise
aneventbasedonthenameattribute.Sincewehaveassignedavaluewiththe_eventId_
prefix(_eventId_customerInfoCollected),SpringWebFlowcouldrecognizeitasan
eventnameandraiseaneventwiththenamecustomerInfoCollected.
Asalreadylearned,transitionfromonestatetoanotherhappenswiththehelpofevents.
So,uponsubmittingthecollectCustomerInfoform,SpringWebFlowwilltakeustothe
nextviewstate,thatis,collectShippingDetail:
<view-stateid="collectCustomerInfo"view="collectCustomerInfo.jsp"
model="order">
<transitionon="customerInfoCollected"to="collectShippingDetail"/>
</view-state>
Similarly,wecanraiseeventswhileclickingonthecancelorbackbuttons;seethe
followingcodesnippetfromcollectShippingDetail.jsp:
<buttonid="back"class="btnbtn-default"
name="_eventId_backToCollectCustomerInfo">back</button>
<buttonid="btnCancel"class="btnbtn-default"
name="_eventId_cancel">Cancel</button>
Okay!SoweunderstandhowtoraiseSpringWebFloweventsfromtheviewtodirectthe
transitionfromoneviewstatetoanother.However,weneedtounderstandonemore
importantconceptregardingtheSpringWebFlowexecution,thatis,eachflowexecution
isidentifiedbytheflowexecutionkeyatruntime.Duringtheflowexecution,whenaview
stateisentered,theflowexecutionpausesandwaitsfortheusertoperformanaction
(suchasenteringsomedataintheform).Whentheusersubmitstheformorchoosesto
cancelit,theflowexecutionkeyisalsosentalongwiththeformdatainordertoresume
theflowwhereitleftoff.Thisisdonewiththehelpofthehidden<input>tagasfollows:
<inputtype="hidden"name="_flowExecutionKey"value="${flowExecutionKey}"/>
Ifyoulookcarefully,wehavetheearliermentionedtagineveryflow-relatedviewfile
suchascollectCustomerInfo.jsp,collectShippingDetail.jsp,andsoon.SpringWeb
Flowwillstoreauniqueflowexecutionkeyunderthemodelattributename
flowExecutionKeyineveryflow-relatedview;weneedtostorethisvalueintheformofa
variablecalled_flowExecutionKeyinordertogetidentifiedbySpringWebFlow.
Sothat’sallaboutviewfilesthatareassociatedwiththedefinitionofourcheckoutflow.
However,weneedtoinvoketheflowuponclickingthecheckoutbuttonfromthecart
page.Aswehavealreadylearnedhowtoinvokeourcheckoutflow,weneedtofireaweb
requestwiththecartIDasarequestparameter.Instep7,wechangedthehrefattributeof
thecheckoutlinkasfollowstoformarequestURLthatissomethingsimilarto
http://localhost:8080/webstore/checkout?cartId=55AD1472D4EC.
So,nowifyouclickonthecheckoutbuttonafterselectingsomeproductsintothe
shoppingcart,youwillbeabletoinitiatethecheckoutflow.Thefollowingistheorder
confirmationpagethatwillappearastheoutcomeofreachingtheorderConfirmation
state:
Orderconfirmationpage
Haveagohero–addingadecisionstate
Althoughwehavefinishedwithourcheckoutflow,thereisstillsomeroomtoimprovethe
flow.Everytimethecheckoutflowstarts,itcollectsthecustomer’sdetails.Whatifa
returningcustomerplacesanorder?Probably,he/shemaynotliketoprovidecustomer
detailsrepeatedlyeverytime.However,youcanautofilldetails,therebypickingup
customerdetailsfromexistingrecords.Herearesomeoftheimprovementsyoucan
introducetoavoidcollectingcustomerdetailsinthecaseofregularcustomers:
Createacustomerrepositoryandservicelayertostore,retrieve,andfindcustomer
objects.Probably,youcanhavemethodssuchasthefollowinginyour
CustomerRepositoyandCustomerServiceinterfacesandintheircorresponding
implementationclasses:
publicvoidsaveCustomer(Customercustomer)
publicCustomergetCustomer(StringcustomerId)
publicBooleanisCustomerExist(StringcustomerId)
Defineaviewstateincheckout-flow.xmltocollectthecustomerID.Don’tforgetto
createthecorrespondingJSPviewfiletocollectthecustomerID.
Defineadecisionstateincheckout-flow.xmltocheckwhetheracustomerexistsin
CustomerRepositoythroughCustomerService.BasedontheretuningBoolean
value,directthetransitiontocollectthecustomerdetailsviewstateorprefillthe
order.customerobjectfromCustomerRepositoy.Thefollowingisasample
decisionstate:
<decision-stateid="checkCustomerExist">
<iftest="customerServiceImpl.isCustomerExist(order.customer.
customerId)"
then="collectShippingDetail"
else="collectCustomerInfo"/>
</decision-state>
Aftercollectingcustomerdetails,don’tforgettostoretheinformationin
CustomerRepositoythroughanactionstate.Similarly,filltheorder.customer
objectafterthedecisionstate.
EnhancingreusabilitythroughApache
Tiles
Inthepast,wedevelopedaseriesofwebpages(views),suchasapagetoshowproducts,
anotherpagetoaddproducts,andsoon,aspartofourwebstoreapplication;thoughevery
viewservesadifferentpurpose,allofthemshareacommonvisualpattern,forexample,
eachpagehasaheader,contentarea,andsoon.Wehavehardcodedandrepeatedthose
commonelementsineveryJSPviewpage.Thisisnotagoodideabecauseinfuture,ifwe
wanttochangethelookandfeelofanyofthesecommonelements,wewillhaveto
changeeverypageinordertomaintainaconsistentlookandfeelacrossallthewebpages.
Toaddressthisproblem,modernwebapplicationsusetemplatemechanisms;Apache
Tilesisonesuchtemplatecompositionframework.Tilesallowdeveloperstodefine
reusablepagefragments(tiles)thatcanbeassembledintoacompletewebpageatruntime.
Thesefragmentscanhaveparameterstoallowdynamiccontent.Thisincreasesthe
reusabilityoftemplatesandreducescodeduplication.
Timeforaction–creatingviewsforevery
viewstate
Enoughofintroduction;let’sdiveintoApacheTilesbydefiningacommonlayoutforour
webapplicationandletthepagesextendthelayout:
1. Openpom.xml;youwillfindpom.xmlundertheprojectrootdirectory.
2. Youwillbeabletoseesomebottomtabsunderpom.xml;selecttheDependenciestab
andclickontheAddbuttonoftheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdasorg.apache.tiles,
ArtifactIdastiles-extras,andVersionas3.0.3.Then,selectScopeascompile,
clickontheOKbutton,andsavepom.xml.
4. Similarly,addonemoredependencywithGroupIdasorg.slf4j,ArtifactIdas
slf4j-api,andVersionas1.7.5.Then,selectScopeascompile,clickontheOK
button,andsavepom.xml.
5. Nowcreateadirectorystructure,tiles/definitions/,underthedirectory
src/main/webapp/WEB-INF/;then,createanXMLfilecalledtile-definition.xml,
addthefollowingcontentintoit,andsaveit.Inthefollowingcodesnippet,Ihave
skippedthe<definition>tagsforsomeofthefieldsoflogicalviewnames.Youcan
findthecompletecodefordefinition.xmlinthecodebundleofthisbook,which
canbedownloadedfromwww.packtpub.com/support:
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEtiles-definitionsPUBLIC"-//ApacheSoftwareFoundation//DTD
TilesConfiguration3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definitionname="baseLayout"template="/WEB-
INF/tiles/template/baseLayout.jsp">
<put-attributename="title"value="SampleTitle"/>
<put-attributename="heading"value=""/>
<put-attributename="tagline"value=""/>
<put-attributename="navigation"value="/WEB-
INF/tiles/template/navigation.jsp"/>
<put-attributename="content"value=""/>
<put-attributename="footer"value="/WEB-
INF/tiles/template/footer.jsp"/>
</definition>
<definitionname="welcome"extends="baseLayout">
<put-attributename="title"value="Products"/>
<put-attributename="heading"value="Products"/>
<put-attributename="tagline"value="AvailableProducts"/>
<put-attributename="content"value="/WEB-INF/views/products.jsp"
/>
</definition>
<definitionname="products"extends="baseLayout">
<put-attributename="title"value="Products"/>
<put-attributename="heading"value="Products"/>
<put-attributename="tagline"value="AvailableProducts"/>
<put-attributename="content"value="/WEB-INF/views/products.jsp"
/>
</definition>
<definitionname="product"extends="baseLayout">
<put-attributename="title"value="Product"/>
<put-attributename="heading"value="Products"/>
<put-attributename="tagline"value="Product"/>
<put-attributename="content"value="/WEB-INF/views/product.jsp"/>
</definition>
<!—similarly,adddefinitiontagsforeverylogicalviewname.Ihave
skippedthishere,butyoucanfindthefulldefinitionfileinthe
codebundleofthisbook.-->
</tiles-definitions>
6. Nowcreateadirectorycalledtemplateunderthedirectorysrc/main/webapp/WEB-
INF/tiles/;then,createaJSPfilecalledbaseLayout.jsp,addthefollowingcontent
intoit,andsaveit:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<%@taglibprefix="tiles"uri="http://tiles.apache.org/tags-tiles"%>
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
<metaname="viewport"content="width=device-width,initial-
scale=1.0">
<title><tiles:insertAttributename="title"/></title>
<linkhref="http://getbootstrap.com/dist/css/bootstrap.css"
rel="stylesheet">
<link
href="http://getbootstrap.com/examples/jumbotron/jumbotron.css"
rel="stylesheet">
</head>
<body>
<divclass="container">
<divclass="header">
<ulclass="navnav-pillspull-right">
<tiles:insertAttributename="navigation"/>
</ul>
<h3class="text-muted">WebStore</h3>
</div>
<divclass="jumbotron">
<h1>
<tiles:insertAttributename="heading"/>
</h1>
<p>
<tiles:insertAttributename="tagline"/>
</p>
</div>
<divclass="row">
<tiles:insertAttributename="content"/>
</div>
<divclass="footer">
<tiles:insertAttributename="footer"/>
</div>
</div>
</body>
</html>
7. Underthesamedirectory(template),createanothertemplateJSPfilecalled
navigation.jspandaddthefollowingcontentintoit:
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<li><ahref="<spring:urlvalue="/products"/>">Home</a></li>
<li><ahref="<spring:URLvalue="/products/"/>">Products</a></li>
<li><ahref="<spring:urlvalue="/products/add"/>">AddProduct</a></li>
<li><ahref="<spring:urlvalue="/cart/"/>">Cart</a></li>
8. Similarly,createonelasttemplateJSPfilecalledfooter.jspandaddthefollowing
contenttoit:
<p>©Company2014</p>
9. Wehavecreatedthecommonbaselayouttemplateandthetiledefinitionforallour
pages.NowweneedtoremovethecommonpageelementsfromallourJSPview
files.Forexample,ifyouremovethejumbotronsectionfromproducts.jspand
keeponlythecontainersection,itwouldlooklikethefollowing.Notethatyou
shouldnotremovethetaglibreferencesandlinkreferencestoJavaScriptfiles:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<sectionclass="container">
<divclass="row">
<c:forEachvar="product"items="${products}">
<divclass="col-sm-6col-md-3"style="padding-bottom:15px">
<divclass="thumbnail">
<img
src="<c:url
value="/resource/images/${product.productId}.png"></c:url>"
alt="image"style="width:100%"/>
<divclass="caption">
<h3>${product.name}</h3>
<p>${product.description}</p>
<p>${product.unitPrice}USD</p>
<p>Available${product.unitsInStock}unitsinstock</p>
<p>
<a
href="<spring:urlvalue="/products/product?
id=${product.productId}"/>"
class="btnbtn-primary"><span
class="glyphicon-info-signglyphicon"/></span>Details
</a>
</p>
</div>
</div>
</div>
</c:forEach>
</div>
</section>
10. Similarly,removethejumbotronsectionfromeveryJSPviewfilethatisunderthe
/src/main/webapp/WEB-INF/viewsdirectory;donotremovethetaglibreferences
andlinkreferencestoJavaScriptfiles;forexample,incart.jsp,youshouldretain
thefollowinglines:
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglibprefix="spring"uri="http://www.springframework.org/tags"%>
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.
js"></script>
<scriptsrc="/webstore/resource/js/controllers.js"></script>
11. Finally,forTilesView,addaUrlBasedViewResolverbeantoourwebapplication
contextconfigurationfile(DispatcherServlet-context.xml),asfollows:
<beanid="tilesViewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<propertyname="viewClass"
value="org.springframework.web.servlet.view.tiles3.TilesView"/>
<propertyname="order"value="-2"/>
</bean>
12. Inordertolocatethetiledefinitionfile,weneedtoaddabeandefinitionfor
TilesConfigurerinDispatcherServlet-context.xml,asfollows:
<beanid="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<propertyname="definitions">
<list>
<value>/WEB-INF/tiles/definitions/tile-definition.xml</value>
</list>
</property>
</bean>
13. NowruntheapplicationandentertheURL
http://localhost:8080/webstore/products;youwillbeabletoseeourregular
productaddpagewithanextranavigationbaratthetopandfooteratthebottom.You
cannavigatetoaddtheproductspagebyclickingontheAddProductlink.
ProductspagewithApacheTilesview
Whatjusthappened?
ToworkwithApacheTiles,weneedjarsrelatedtoApacheTiles;fromsteps1to4,we
addedthosejarsviaMavendependencies.Step5isveryimportantbecausewecreatedour
tilesdefinitionfile(tile-definition.xml)inthisstep.Understandingthetiledefinition
fileiscrucialfordevelopingapplicationsbasedonApacheTiles,sowewilltryto
understandourtiledefinitionfile(tile-definition.xml).
Atile’sdefinitionfileisacollectionofdefinitions,andeachdefinitioncanbeassociated
withatemplateviathetemplateattributeforthelayout;refertothefollowingcodeto
knowhowtodothis:
<definitionname="baseLayout"template="/WEB-
INF/tiles/template/baseLayout.jsp">
<put-attributename="title"value="SampleTitle"/>
<put-attributename="heading"value="SampleHeader"/>
<put-attributename="tagline"value="SampleTagline"/>
<put-attributename="navigation"value="/WEB-
INF/tiles/template/navigation.jsp"/>
<put-attributename="content"value=""/>
<put-attributename="footer"value="/WEB-INF/tiles/template/footer.jsp"
/>
</definition>
Withineachdefinition,wecandefinemanyattributes.Theseattributescanbeasimple
textvalueorafull-blownmarkupfile.Theseattributeswouldbeavailableinthetemplate
fileviathe<tiles:insertAttribute>tag.Forexample,ifyouopenthebaselayout
template(baseLayout.jsp),youwillnoticethefollowingsnippetunderthejumbotron
<div>tag:
<h1>
<tiles:insertAttributename="heading"/>
</h1>
<p>
<tiles:insertAttributename="tagline"/>
</p>
So,atruntime,ApacheTileswillreplacethe<tiles:insertAttributename="heading"
/>tagwiththevalueSampleHeader;similarly,the<tiles:insertAttribute
name="tagline"/>tagwillalsobereplacedwiththeSampleTaglinevalue.
So,baseLayoutisassociatedwiththetemplate/WEB-
INF/tiles/template/baseLayout.jsp,andwecaninsertthedefinedattributessuchas
tile,heading,andtaglineinthetemplateusingthe<tiles:insertAttribute>tag.
ApacheTilesallowsustoextendadefinitionjustlikehowweextendaJavaclasssothat
thedefinedattributeswillbeavailableforthederiveddefinition,andwecanevenoverride
theseattributevaluesifwewant.Forexample,lookatthefollowingdefinitionfromtile-
definition.xml:
<definitionname="products"extends="baseLayout">
<put-attributename="title"value="Products"/>
<put-attributename="heading"value="Products"/>
<put-attributename="tagline"value="AvailableProducts"/>
<put-attributename="content"value="/WEB-INF/views/products.jsp"/>
</definition>
ThementioneddefinitionisanextensionofthebaseLayoutdefinition.Wehaveonly
overriddenthetitle,heading,tagline,andcontentattributes,andsincewehavenot
definedanytemplateforthisdefinition,itusesthesametemplatethatweconfiguredfor
thebaseLayoutdefinition.
Similarly,wehavedefinedthetiledefinitionforeverypossiblelogicalviewnamethatcan
bereturnedfromourcontrollers.Notethateachdefinitionname(exceptthebaseLayout
definition)isalogicalviewname.
Fromsteps6to8,wecreatedthetemplatesthatcanbeusedinthetiledefinition;firstwe
createdthebaselayouttemplate(baseLayout.jsp),thenthenavigationtemplate
(navigation.jsp),andfinallythefootertemplate(footer.jsp).
Steps9and10explainedhowtoremovetheexistingredundantcontent,suchasthe
jumbotron<div>tag,fromeveryJSPviewpage.Notethatyouhavetobecarefulwhile
doingthings;don’taccidentlyremovethetaglibreferencesandlinkreferencesto
JavaScriptfiles.
InStep11,wedefinedUrlBasedViewResolverforTilesView
(org.springframework.web.servlet.view.tiles3.TilesView)inordertoresolve
logicalviewnamesintothetile’sview.Finally,instep12,weconfigured
TilesConfigurer
(org.springframework.web.servlet.view.tiles3.TilesConfigurer)tolocatethetile’s
definitionfilesbytheApacheTilesframework.
That’sit!IfyouruntheapplicationandentertheURL
http://localhost:8080/webstore/products,youwillbeabletoseeourregular
productspagewithanextranavigationbaratthetopandfooteratthebottomas
mentionedinstep13.YoucannavigatetoaddtheproductspagebyclickingontheAdd
Productlink.Previously,everytimealogicalviewnamewasreturnedbythecontroller
method,theInternalResourceViewResolvercameintoactionandfoundthe
correspondingjspviewforthegivenlogicalviewname.Now,foreverylogicalview
name,UrlBasedViewResolverwillcomeintoactionandcomposethecorrespondingview
basedonthetemplate’sdefinition.
Popquiz–ApacheTiles
Q1.WhichofthefollowingstatementsaretrueaccordingtoApacheTiles?
1. Alogicalviewnamereturnedbythecontrollermustbeequaltothe<definition>
tagname
2. The<tiles:insertAttribute>tagactsasaplaceholderinthetemplate
3. A<definition>tagcanextendanother<definition>tag
Summary
SpringWebFlowandApacheTilesaretwoseparateframeworks.Weonlysawthe
minimumrequiredconceptstogetaquickoverviewoftheseframeworksinthischapter.
Inthebeginning,welearnedsomeofthebasicconceptsoftheSpringWebFlow
frameworkandthencreatedthecheckoutflowforourwebstoreapplication.Inthesecond
partofthischapter,wesawhowtouseandleveragetheApacheTilesframeworkinorder
tobringmaximumreusabilityinviewfilesandmaintainaconsistentlookandfeel
throughoutallthewebpagesofourapplication.
Inthenextchapter,wewillseehowtotestourwebapplicationusingvariousAPIs
providedbySpringMVC.
Chapter10.TestingYourApplication
Forawebapplicationdeveloper,testingthewebapplicationsisalwaysachallenging
task,becausegettingareal-timetestenvironmentforwebapplicationsrequiresalotof
effort.ThankstotheSpringMVCTestframework,testingSpringMVCapplicationsis
simplified.
Butwhydoweneedtoconsiderputtingineffortstotestourapplication?Writinggood
testcasesforourapplicationiskindoflikebuyinginsuranceforyourapplication.
Althoughitdoesnotaddanyfunctionalvaluestoyourapplication,itwilldefinitelysave
yourtimeandeffortbydetectingoffunctionalityfailuresearly.Considerthatyour
applicationisgrowingbiggerandbiggerintermsoffunctionality;youneedsome
mechanismtoensurethatexistingfunctionalitiesarenotdisturbedduetotheintroduction
ofnewfunctionalities.
Testingframeworksprovideyouwiththiskindofmechanismtoensurethatyour
applicationbehaviorisnotalteredduetorefactoringortheadditionofnewcode.Italso
ensuresthattheexistingfunctionalityworksasexpected.
Inthischapter,wearegoingtoseethefollowingtopics:
Testingthedomainobjectandvalidator
Testingcontrollers
TestingRESTfulwebservices
Unittesting
Insoftwaredevelopment,unittestingisasoftwaretestingmethodinwhichthesmallest
testablepartsofsourcecode,calledunits,areindividuallyandindependentlytestedto
determinewhethertheybehaveexactlyasweexpect.Tounittestoursourcecode,allwe
needisatestprogramthatcanrunabitofoursourcecode(unit),providesomeinputto
eachunit,andchecktheresultsfortheexpectedoutput.Mostunittestsarewrittenusing
somesortoftestframeworksetoflibrarycode,designedtomakewritingandrunning
testseasier.OnesuchframeworkiscalledJUnit.ItisaunittestingframeworkfortheJava
programminglanguage.
Timeforaction–unit-testingdomain
objects
Let’sseehowtotestoneofourdomainobjectusingtheJUnitframeworktoensureit
functionsasexpected.Inanearlierchapter,wecreatedadomainobjecttorepresentan
iteminashoppingcart,calledCartItem.TheCartItemclasshasamethodcalled
getTotalPricetoreturnthetotalpriceofthatparticularcartitembasedontheproduct
andnumberofitemsitrepresents.Let’stestwhetherthegetTotalPricemethodbehaves
properly.Followthesesteps:
1. Openpom.xmlandyouwillfindpom.xmlundertherootdirectoryoftheprojectitself.
2. Youwillseesometabsatthebottomofthepom.xmlfile.SelecttheDependencies
tabandclickontheAddbuttonoftheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdasjunit,ArtifactIdas
junit,andVersionas4.11;thenselecttestasScope,clickontheOKbutton,and
savepom.xml.
4. NowcreateaclasscalledCartItemTestunderthepackage
com.packt.webstore.domaininthesourcefolder,src/test/java.Addthe
followingcodeintoitandsavethefile:
packagecom.packt.webstore.domain;
importjava.math.BigDecimal;
importorg.junit.Assert;
importorg.junit.Before;
importorg.junit.Test;
publicclassCartItemTest{
privateCartItemcartItem;
@Before
publicvoidsetup(){
cartItem=newCartItem();
}
@Test
publicvoid
cartItem_total_price_should_be_eaual_to_product_unit_price_in_case_of_s
ingle_quantity(){
//Arrange
Productiphone=newProduct("P1234","iPhone5s",new
BigDecimal(500));
cartItem.setProduct(iphone);
//Act
BigDecimaltotalPrice=cartItem.getTotalPrice();
//Assert
Assert.assertEquals(iphone.getUnitPrice(),totalPrice);
}
}
5. Nowright-clickonCartItemTest.javaandgotoRunAs|JUnitTest.Youwillsee
afailingtestcaseintheJUnitwindow,asshowninthefollowingscreenshot:
JUnitfailingtestcaseinCartItemTest
6. Tomakethetestcasepass,assignthevalue1tothequantityfieldofCartItemin
thezeroargumentconstructoroftheCartItemclass(asfollows)andsavethefile:
publicCartItem(){
this.quantity=1;
}
7. Now,right-clickagainonCartItemTest.javaandgotoRunAs|JUnitTest.You
willseethatthetestcasehaspassedintheJUnitwindow,asshowninthefollowing
screenshot:
JUnitpassedtestcaseinCartItemTest
Whatjusthappened?
AsIalreadymentioned,thegetTotalPricemethodoftheCartItemclassisdesignedto
returnthecorrecttotalpricebasedontheproductandthenumberofproductsitrepresents.
Buttoensureitsproperbehavior,wehavewrittenatestprogramcalledCartItemTest
underthecom.packt.webstore.domainpackageinthesourcefolder,src/test/java,as
mentionedinstep4.
InCartItemTest,weusedsomeoftheJUnitframeworkAPIs,suchasthe@Testand
@Beforeannotations.So,beforewecanusetheseannotationsinourCartItemTestclass,
weneedtoaddaJUnitjarasdependencyinourproject.That’swhatwedidinsteps1
through3.
Now,let’sgettoknowtheCartItemTestclassthoroughly.Theimportantmethodinthe
CartItemTestclassistheonethatisannotatedwith@Test,called
cartItem_total_price_should_be_eaual_to_product_unit_price_in_case_of_single_quantity
The@Testannotation(org.junit.Test)marksaparticularmethodasatestmethod.This
issothattheJUnitframeworkcantreatthatmethodasatestmethodandexecuteitwhen
wegotoRunAs|JUnitTest.Considerthefollowingcodesnippet:
@Test
publicvoid
cartItem_total_price_should_be_eaual_to_product_unit_price_in_case_of_singl
e_quantity(){
//Arrange
Productiphone=newProduct("P1234","iPhone5s",newBigDecimal(500));
cartItem.setProduct(iphone);
//Act
BigDecimaltotalPrice=cartItem.getTotalPrice();
//Assert
Assert.assertEquals(iphone.getUnitPrice(),totalPrice);
}
Ifyounotice,theprecedingmethodhasbeendividedintothreelogicalpartscalled
Arrange,Act,andAssert:
Arrange:Thissectionarrangesallthenecessarypreconditionsandinputstoperform
atest
Act:Thissectionactsontheobjectormethodundertest
Assert:Thissectionassertsthattheexpectedresultshaveoccurred
IntheArrangepart,wejustinstantiatedaproductdomainobject(iphone)withaunit
pricevalueof500andaddedthatproductobjecttothecartItemobjectbycalling
cartItem.setProduct(iphone);.WethenaddedasingleproducttocartItem.We
haven’talteredthequantityaspectofthecartItemobject.So,ifwecallthe
getTotalPricemethodofcartItem.Wemustget500(inBigDecimal),becausetheunit
priceofthedomainobject(iphone)wehaveaddedincartItemis500.
IntheActpart,wejustcalledthemethodundertest,whichisthegetTotalPricemethod
ofthecartItemobject,andstoredtheresultinaBigDecimalvariablecalledtotalPrice.
Later,intheAssertpart,weusedtheJUnitAPI(Assert.assertEquals)toassertthe
equalitybetweentheunitPricevalueoftheproductdomainobjectandthecalculated
totalPriceofcartItem.Havealookatthefollowingcode:
Assert.assertEquals(iphone.getUnitPrice(),totalPrice);
ThetotalPriceparameterofcartItemmustbeequaltotheunitPricevalueofthe
productdomainobjectwehaveaddedtocartItem.Thisisbecauseweaddedthesingle-
productdomainobjectwhoseunitPriceandtotalPricevalueneedtobethesame.
WhenwerunCartItemTestasmentionedinstep5,theJUnitframeworktriestoexecute
allthemethodsannotatedwith@TestintheCartItemTestclass.So,basedonthe
assertions’results,atestcasemayfailorpass.Inourcase,ourtestcasefailed.Youcan
seethatthefailuretraceshowsanerrormessagethatsaysexpected<500>butwas:<0>
inthescreenshotimmediatelyafterstep5.Thisisbecauseitdidn’tupdatethequantity
fieldofthecartItemobjectwhenweaddedaproductdomainobjecttocartIteminthe
Arrangepart.Itisabug.Tofixthisbug,wedefaultthequantityfieldvalueto1
wheneverweinstantiatecartItem,usingthezeroargumentconstructorasmentionedin
thestep6.Now,ifyourunthetestcaseagain,itpassesasexpected.
Haveagohero–addingtestsforcart
ItisgoodthatwehavetestedandverifiedthegetTotalPricemethodoftheCartItem
class.YoucansimilarlywriteatestclassfortheCartdomainobjectclass.IntheCart
domainobjectclass,thereisamethodtogetthegrandtotal(getGrandTotal)andwrite
varioustestcasestocheckwhetherthegetGrandTotalmethodworksasexpected.
IntegrationtestingwiththeSpringTest
Contextframework
Whenindividualprogramunitsarecombinedandtestedasagroup,itisknownas
integrationtesting.TheSpringTestContextframeworkgivesfirstclasssupportforthe
integrationtestingofSpring-basedapplications.WehavedefinedlotsofSpring-managed
beansinourwebapplicationcontext(DispatcherServlet-context.xml),suchas
services,repositories,andviewresolvers,torunourapplication.Thesemanagedbeansare
instantiatedduringthestartupofanapplicationbytheSpringframework.While
performingintegrationtesting,ourtestenvironmentmustalsohavethosebeanstotestour
applicationsuccessfully.TheSpringTestContextframeworkgivesustheabilitytodefine
atestcontext,whichissimilartothewebapplicationcontext(DispatcherServlet-
context.xml).Let’sseehowtoincorporateSpringTestContexttotestour
ProductValidatorclass.
Timeforaction–testingtheproduct
validator
Let’sseehowwecanbootupourtestcontextusingtheSpringTestContextframeworkto
testourProductValidatorclass:
1. Openpom.xmlandyouwillfindpom.xmlundertherootdirectoryoftheprojectitself.
2. Youwillbeabletoseesometabsatthebottomofthepom.xmlfile;selectthe
DependenciestabandclickontheAddbuttonoftheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdas
org.springframework,ArtifactIdasspring-test,andVersionas4.0.3.RELEASE;
thenselectScopeastest,clickontheOKbutton,andsavepom.xml.
4. NowcreateanXMLfilecalledtest-DispatcherServlet-context.xmlunderthe
com.packt.webstore.validatorpackageinthesourcefolder,src/test/resources.
Addthefollowingcodeintoitandsaveit:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:webflow-config="http://www.springframework.org/schema/webflow-
config"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-
config-2.3.xsd">
<mvc:annotation-drivenvalidator="validator"/>
<context:component-scanbase-package="com.packt.webstore"/>
<beanid="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
>
<propertyname="basename"value="messages"/>
</bean>
<beanid="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFact
oryBean">
<propertyname="validationMessageSource"ref="messageSource"/>
</bean>
<beanid="productValidator"
class="com.packt.webstore.validator.ProductValidator">
<propertyname="springValidators">
<set>
<refbean="unitsInStockValidator"/>
</set>
</property>
</bean>
<beanid="unitsInStockValidator"
class="com.packt.webstore.validator.UnitsInStockValidator"/>
</beans>
5. Next,createaclasscalledProductValidatorTestunderthe
com.packt.webstore.validatorpackageinthesourcefolder,src/test/java.Add
thefollowingcodetoit:
packagecom.packt.webstore.domain;
packagecom.packt.webstore.validator;
importjava.math.BigDecimal;
importorg.junit.Assert;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
importorg.springframework.test.context.web.WebAppConfiguration;
importorg.springframework.validation.BindException;
importorg.springframework.validation.ValidationUtils;
importcom.packt.webstore.domain.Product;
importcom.packt.webstore.validator.ProductValidator;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-DispatcherServlet-context.xml")
@WebAppConfiguration
publicclassProductValidatorTest{
@Autowired
privateProductValidatorproductValidator;
@Test
publicvoidproduct_without_UnitPrice_should_be_invalid(){
//Arrange
Productproduct=newProduct();
BindExceptionbindException=newBindException(product,
"product");
//Act
ValidationUtils.invokeValidator(productValidator,product,
bindException);
//Assert
Assert.assertEquals(1,bindException.getErrorCount());
Assert.assertTrue(bindException.getLocalizedMessage().contains("Unit
priceisInvalid.Itcannotbeempty."));
}
@Test
publicvoidproduct_with_existing_productId_invalid(){
//Arrange
Productproduct=newProduct("P1234","iPhone5s",new
BigDecimal(500));
product.setCategory("Tablet");
BindExceptionbindException=newBindException(product,
"product");
//Act
ValidationUtils.invokeValidator(productValidator,product,
bindException);
//Assert
Assert.assertEquals(1,bindException.getErrorCount());
Assert.assertTrue(bindException.getLocalizedMessage().contains("A
productalreadyexistswiththisproductid."));
}
@Test
publicvoid
a_valid_product_should_not_get_any_error_during_validation(){
//Arrange
Productproduct=newProduct("P9876","iPhone5s",new
BigDecimal(500));
product.setCategory("Tablet");
BindExceptionbindException=newBindException(product,
"product");
//Act
ValidationUtils.invokeValidator(productValidator,product,
bindException);
//Assert
Assert.assertEquals(0,bindException.getErrorCount());
}
}
6. Nowright-clickonProductValidatorTestandgotoRunAs|JUnitTest.Youwill
beabletoseethetestcasesthatpass,asshowninthefollowingscreenshot:
Customerdetailscollectionform
Whatjusthappened?
AsIhavealreadymentioned,Springprovidesextensivesupportforintegrationtesting.In
ordertodevelopatestcaseusingtheSpringTestContextframework,weneedthe
spring-testjar.Instep3,wejustaddeddependencytothespring-testjar.TheSpring
TestContextframeworkcannotrunwithoutthesupportoftheJUnitjar.Instep4,wejust
createdatestwebapplicationcontextconfigurationfilecalledtest-DispatcherServlet-
context.xmlthatonlydefinesthebeansthatarerequiredforourtesttoexecute.Later,in
step5,whenwecreatedouractualtestcase,theSpringTestContextframeworkusedthis
fileasbasetocreatethetestcontext.
Step5isveryimportantbecauseitrepresentstheactualtestclass
(ProductValidatorTest)totestthevalidityofourProductdomainobject.Thegoalof
thetestclassistocheckwhetherallthevalidations(includingbeanvalidationandSpring
validation)thatarespecifiedintheProductdomainclassareworking.Ihopeyou
rememberthatwespecifiedsomeofthebeanvalidationannotation,suchas@NotNulland
@Pattern,ontheProductdomainclassinChapter7,ValidateYourProductswitha
Validator.
Onewaytotestwhetherthosevalidationsaretakingplaceisbymanuallyrunningour
applicationandtryingtoenterinvalidvalues.Thisapproachiscalledmanualtesting.This
isaverydifficultjob,whereasinautomatedtesting,wecanwritesometestclassestorun
testcasesinarepeatedfashiontotestthefunctionality.UsingJUnit,wecanwritesuch
kindsoftestclasses.
TheProductValidatorTestclasscontainsthreetestmethodsintotal;wecanidentifya
testmethodbythe@Testannotation(org.junit.Test)ofJUnit.Everytestmethodcanbe
logicallyseparatedintothreeparts,thatis,Arrange,Act,andAssert.IntheArrangepart,
weinstantiateandinstrumenttherequiredobjectsfortesting;intheActpart,weinvoke
theactualfunctionalitythatneedstobetested;andfinallyintheAssertpart,wecompare
theexpectedresultandtheactualresultthatistheoutputoftheinvokedfunctionality.
Havealookatthefollowingcode:
@Test
publicvoidproduct_without_UnitPrice_should_be_invalid(){
//Arrange
Productproduct=newProduct();
BindExceptionbindException=newBindException(product,"product");
//Act
ValidationUtils.invokeValidator(productValidator,product,
bindException);
//Assert
Assert.assertEquals(1,bindException.getErrorCount());
Assert.assertTrue(bindException.getLocalizedMessage().contains("Unit
priceisInvalid.Itcannotbeempty."));
}
IntheArrangepartofthistestmethod,wejustinstantiatedabareminimumProduct
domainobject.WehavenotsetanyvaluesfortheproductId,unitPrice,andcategory
fields.WepurposelysetupsuchabareminimumdomainobjectintheArrangepartto
checkwhetherourProductValidatorclassworksproperlyintheActpart.Accordingto
theProductValidatorclasslogic,thepresentstateoftheproductdomainobjectis
invalid.IntheActpart,weinvoketheproductValidatorobjectusingthe
ValidationUtilsclasstocheckwhetherthevalidationworksornot.Duringvalidation,
productValidatorwillstoretheerrorsinaBindExceptionobject.IntheAssertpart,we
simplycheckedwhetherthebindExceptionobjectcontainedoneerror,usingtheJUnit
AssertAPIs,andcheckedwhethertheerrormessagewasasexpected.
AnotherimportantthingweneedtounderstandinourProductValidatorTestclassisthat
weusedSpring’sstandard@Autowiredannotationtogettheinstanceof
ProductValidator;thequestionhereiswhoinstantiatedtheproductValidatorobject?
Theanswerisinthe@ContextConfigurationannotation.Notethelocationsattribute
specifiedinthe@ContextConfigurationannotation–ithasthesamenameasourtest
contextfile(test-DispatcherServlet-context.xml).
Asyoumightremember,wehavelearnedthatduringthebootingupofourapplication,
SpringMVCwillcreateawebapplicationcontext(Springcontainer)withthenecessary
beans,asdefinedinthewebapplicationcontextconfigurationfile.Weneedasimilarkind
ofcontextevenbeforerunningourtestclassessothatwecanusethosedefinedbeans
(objects)inourtestclasstotestitproperly.TheSpringTestframeworkmakesitpossible
viathe@ContextConfigurationannotation.
Nowweneedasimilarkindofrunningapplicationenvironmentwithalltheresourcefiles.
Toachievethis,weusedthe@WebAppConfigurationannotationfromtheSpringTest
framework.The@WebAppConfigurationannotationinstructstheSpringTestframeworkto
loadtheapplicationcontextasWebApplicationContext.
NowwehaveseenalmostalltheimportantthingsrelatedtoexecutingaSpringintegration
test,butonefinalconfigurationweneedtounderstandishowtointegrateJUnitandthe
SpringTestContextframeworkinourtestclass.The
@RunWith(SpringJUnit4ClassRunner.class)annotationdoesthatjob.
So,finally,whenwerunourtestcases,wewillseeagreenbarintheJUnitwindow,
indicatingthatthetestsweresuccessful.
Timeforaction–testingtheproduct
controller
Let’sseenowhowtotestourcontrollers:
1. CreateanXMLfilecalledtest-DispatcherServlet-context.xmlunderthe
com.packt.webstore.controllerpackageinthesourcefolder,
src/test/resources.Addthefollowingcodeintoitandsaveit:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:webflow-config="http://www.springframework.org/schema/webflow-
config"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-
config-2.3.xsd">
<mvc:annotation-drivenvalidator="validator"/>
<context:component-scanbase-package="com.packt.webstore"/>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolve
r">
<propertyname="prefix"value="/WEB-INF/views/"/>
<propertyname="suffix"value=".jsp"/>
</bean>
<beanid="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
>
<propertyname="basename"value="messages"/>
</bean>
<beanid="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFact
oryBean">
<propertyname="validationMessageSource"ref="messageSource"/>
</bean>
<beanid="productValidator"
class="com.packt.webstore.validator.ProductValidator">
<propertyname="springValidators">
<set>
<refbean="unitsInStockValidator"/>
</set>
</property>
</bean>
<beanid="unitsInStockValidator"
class="com.packt.webstore.validator.UnitsInStockValidator"/>
</beans>
2. CreateaclasscalledProductControllerTestunderthe
com.packt.webstore.controllerpackageinthesourcefolder,src/test/java,and
addthefollowingcodeintoit:
packagecom.packt.webstore.controller;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.model
;
importjava.math.BigDecimal;
importorg.junit.Before;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
importorg.springframework.test.context.web.WebAppConfiguration;
importorg.springframework.test.web.servlet.MockMvc;
importorg.springframework.test.web.servlet.setup.MockMvcBuilders;
importorg.springframework.web.context.WebApplicationContext;
importcom.packt.webstore.domain.Product;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-DispatcherServlet-context.xml")
@WebAppConfiguration
publicclassProductControllerTest{
@Autowired
privateWebApplicationContextwac;
privateMockMvcmockMvc;
@Before
publicvoidsetup(){
this.mockMvc=
MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
publicvoidtestGetProducts()throwsException{
this.mockMvc.perform(get("/products"))
.andExpect(model().attributeExists("products"));
}
@Test
publicvoidtestGetProductById()throwsException{
//Arrange
Productproduct=newProduct("P1234","iPhone5s",new
BigDecimal(500));
//Act&Assert
this.mockMvc.perform(get("/products/product")
.param("id","P1234"))
.andExpect(model().attributeExists("product"))
.andExpect(model().attribute("product",product));
}
}
3. Nowright-clickontheProductControllerTestclassandgotoRunAs|JUnitTest.
Youwillbeabletoseethatthetestcasesarebeingexecuted.Youwillbeabletosee
thetestresultsintheJUnitwindow.
Whatjusthappened?
JustlikewiththeProductValidatorTestclass,weneedtobootupthetestcontextand
runourProductControllerTestclassastheSpringintegrationtest.Soweusedsimilar
annotationsontopofProductControllerTest,asfollows:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-DispatcherServlet-context.xml")
@WebAppConfiguration
publicclassProductControllerTest{
ApartfromthetwotestmethodsthatareavailableunderProductControllerTest,a
singlesetupmethodisavailableasfollows:
@Before
publicvoidsetup(){
this.mockMvc=MockMvcBuilders.webAppContextSetup(this.wac).build();
}
The@Beforeannotationthatispresentontopoftheprecedingmethodindicatesthatthis
methodshouldbeexecutedbeforeeverytestmethod.Andwithinthatmethod,wesimply
buildourMockMvcobjectinordertouseitinthefollowingtestmethods.TheMockMvc
classisaspecialclassprovidedbytheSpringTestContextframeworktosimulatebrowser
actionswithinatestcase,suchasfiringHTTPrequests.Havealookatthefollowing
code:
@Test
publicvoidtestGetProducts()throwsException{
this.mockMvc.perform(get("/products"))
.andExpect(model().attributeExists("products"));
}
TheprecedingtestmethodsimplyfiresaGETHTTPrequesttoourapplicationusingthe
mockMvcobject,andasaresult,weensurethatthereturnedmodelcontainsanattribute
namedproducts.RememberthatthelistmethodintheProductControllerclassisthe
onethathandlestheprecedingwebrequest,soitwillfillthemodelwiththeavailable
productsundertheattributenamedproducts.
Afterrunningyourtestcase,youwillbeabletoseethegreenbarintheJUnitwindowthat
indicatesthetestshavepassed.
Timeforaction–testingRESTcontrollers
Similarly,wecantesttheREST-basedcontrollersaswell.Justperformthefollowing
steps:
1. Openpom.xmlandyouwillfindpom.xmlundertherootdirectoryoftheprojectitself.
2. Youwillbeabletoseesometabsatthebottomofthepom.xmlfile;selectthe
DependenciestabandclickontheAddbuttonoftheDependenciessection.
3. ASelectDependencywindowwillappear;enterGroupIdas
com.jayway.jsonpath,ArtifactIdasjson-path-assert,andVersionas0.8.1.
SelectScopeastest,clickontheOKbutton,andsavepom.xml.
4. NowcreateaclasscalledCartRestControllerTestunderthe
com.packt.webstore.controllerpackageinthesourcefolder,src/test/java.
Nowaddthefollowingcodetoit:
packagecom.packt.webstore.controller;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonP
ath;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.statu
s;
importorg.junit.Before;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.mock.web.MockHttpSession;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
importorg.springframework.test.context.web.WebAppConfiguration;
importorg.springframework.test.web.servlet.MockMvc;
importorg.springframework.test.web.servlet.setup.MockMvcBuilders;
importorg.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-DispatcherServlet-context.xml")
@WebAppConfiguration
publicclassCartRestControllerTest{
@Autowired
privateWebApplicationContextwac;
@Autowired
MockHttpSessionsession;
privateMockMvcmockMvc;
@Before
publicvoidsetup(){
this.mockMvc=
MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
publicvoidread_method_should_return_correct_cart_Json_object()
throwsException{
//Arrange
this.mockMvc.perform(put("/rest/cart/add/P1234").session(session))
.andExpect(status().is(204));
//Act
this.mockMvc.perform(get("/rest/cart/"+
session.getId()).session(session))
.andExpect(status().isOk())
.andExpect(jsonPath("$.cartItems.P1234.product.productId").value("P1234
"));
}
}
5. Nowright-clickonCartRestControllerTestandgotoRunAs|JUnitTest.You
willbeabletoseethattestcasesarebeingexecuted,andthetestresultswillbeseen
intheJUnitwindow.
Whatjusthappened?
WhiletestingRESTcontrollers,weneedtoensurethatthewebresponseforthegiven
webrequestcontainstheexpectedJSONobject.Toverifythat,weneedsomespecialized
APIstochecktheformatoftheJSONobject.Thejson-path-assertjarprovidesthese
APIs.WeaddedtheMavendependencytothejson-path-assertjarfromstep1through
3.
Instep4,wecreatedCartRestControllerTesttoverifywhetherour
CartRestControllerclassworksproperly.TheCartRestControllerTestclassisvery
similartoProductControllerTest;theonlydifferenceisthewayweasserttheresultofa
webrequest.InCartRestControllerTest,wehaveonetestmethodtotesttheread
methodoftheCartRestControllerclass.
ThereadmethodofCartRestControllerisdesignedtoreturnacartobjectasaJSON
objectforthegivencartID.InCartRestControllerTest,wehavetestedthisbehavior
throughtheread_method_should_return_correct_cart_Json_objecttestmethod.Have
alookatthefollowingcode:
@Test
publicvoidread_method_should_return_correct_cart_Json_object()throws
Exception{
//Arrange
this.mockMvc.perform(put("/rest/cart/add/P1234").session(session))
.andExpect(status().is(204));
//Act
this.mockMvc.perform(get("/rest/cart/"+
session.getId()).session(session))
.andExpect(status().isOk())
.andExpect(jsonPath("$.cartItems.P1234.product.productId").value("P1234"));
}
InordertogetacartobjectforthegivencartID,wefirstneedtostorethecartobjectin
ourcartrepositorythroughawebrequest.ThatiswhatwedidintheArrangepartofthe
precedingtestmethod.Thefirstwebrequestwefiredinthearrangepartwilladda
productdomainobjectinthecartwhoseIDisthesameasthesessionID.
IntheActpartofthetestcase,wesimplyfiredanotherREST-basedwebrequesttogetthe
cartobjectasaJSONobject.RememberthatweusedthesessionIDasourcartIDto
storeourcartobject,sowhileretrieving,weneedtogivethesamesessionIDinthe
requestURL.Forthis,wecanusethemocksessionobjectgivenbytheSpringTest
framework.Youcanseeinthefollowingcodethatwehaveautowiredthesessionobjectin
ourCartRestControllerTestclass:
this.mockMvc.perform(get("/rest/cart/"+session.getId()).session(session))
.andExpect(status().isOk())
.andExpect(
jsonPath("$.cartItems.P1234.product.productId").value("P1234"));
OncewegetthecartdomainobjectasaJSONobject,wehavetoverifythatitcontains
thecorrectproduct.WewillbeabletodothatwiththehelpofthejsonPathmethodof
MockMvcResultMatchers,asspecifiedintheprecedingcodesnippets.Aftersendingthe
RESTwebrequestgogetthecartobject,weverifiedthattheresponsestatusisas
expectedandalsocheckedwhethertheJSONobjectcontainsaproductwiththeIDP1234.
Finally,whenwerunthistestcase,youwillbeabletoseethatthetestcasesarebeing
executedandyouwillseethetestresultsintheJUnitwindow.
Haveagohero–addingtestsfortheremaining
RESTmethods
ItisgoodthatwehavetestedandverifiedthereadmethodofCartRestController,but
wehavenottestedtheothermethodsofCartRestController.Youcanaddtestsforother
methodsofCartRestControllerintheCartRestControllerTestclasstogetfamiliar
withtheSpringTestframework.
Summary
Inthisfinalchapter,youlearnedtheimportanceoftestingawebapplicationandsawhow
tounittestthedomainobject.Nextwelearnedhowtoperformintegrationteston
validatorusingtheSpringTestframework.Youalsosawhowtotestanormalcontroller
usingtheSpringTestContextframework.Asthelastexercise,yousawhowtotestREST-
basedcontrollersandhowtousethemocksessionobjectfromtheSpringTestframework.
Inthatexercise,youalsolearnedhowtoverifytheJSONpath.
AppendixA.UsingtheGradleBuildTool
Throughoutthisbook,wehaveusedApacheMavenasourbuildtool,butthereareother
popularbuildtoolsalsousedwidelyintheJavacommunity.OnesuchbuildtoolisGradle.
InsteadofXML,GradleusesaGroovy-basedDomainSpecificLanguage(DSL)asthe
baseforthebuildscript,whichprovidesmoreflexibilitywhendefiningcomplexbuild
scripts.ComparedtoMaven,Gradletakeslesstimeforincrementalbuilds.So,Gradle
buildsareveryfastandeffectiveforlargeprojects.
Inthisappendix,wewillseehowtoinstallanduseGradleasthebuildtoolinourproject.
InstallingGradle
PerformthefollowingstepstoinstallGradle:
1. GototheGradledownloadpagebyenteringtheURL
http://www.gradle.org/downloadsinyourbrowser.
2. ClickonthelatestGradlestablereleasedownloadlink;atthetimeofwritingthis,the
stablereleaseisgradle-1.11-all.zip.
3. Oncethedownloadisfinished,gotothedownloadeddirectoryandextracttheZIP
fileintoaconvenientdirectoryofyourchoice.
4. CreateanenvironmentvariablecalledGRADLE_HOME.EntertheextractedGradleZIP
directorypathasthevaluefortheGRADLE_HOMEenvironmentvariable.
5. Finally,appendtheGRADLE_HOMEvariabletoPATHbysimplyappendingthetext
;%GRADLE_HOME%\bintothePATHvariable.
NowthatyouhaveinstalledGradleonyourWindows-basedcomputer,toverifywhether
theinstallationwascompletedcorrectly,gotothecommandprompt,typegradle-v,and
pressEnter.TheoutputshowstheGradleversionandalsothelocalenvironment
configuration.
TheGradlebuildscriptforyourproject
ToconfiguretheGradlebuildscriptforyourproject,performthefollowingsteps:
1. Gototherootdirectoryofyourprojectfromthefilesystem,createafilecalled
build.gradle,andaddthefollowingcontentintothefileandsaveit:
applyplugin:'war'
applyplugin:'eclipse-wtp'
repositories{
mavenCentral()//addcentralmavenrepotoyourbuildfile
}
dependencies{
compile'org.springframework:spring-webmvc:4.0.3.RELEASE',
'javax.servlet:jstl:1.2',
'org.springframework.security:spring-security-web:3.1.4.RELEASE',
'commons-fileupload:commons-fileupload:1.2.2',
'org.apache.commons:commons-io:1.3.2',
'org.springframework:spring-oxm:4.0.3.RELEASE',
'org.codehaus.jackson:jackson-mapper-asl:1.9.10',
'log4j:log4j:1.2.12',
'org.hibernate:hibernate-validator:4.3.1.Final',
'org.springframework.webflow:spring-webflow:2.3.3.RELEASE',
'org.apache.tiles:tiles-extras:3.0.3',
'org.slf4j:slf4j-api:1.7.5'
compile('org.springframework.security:spring-security-
config:3.1.4.RELEASE'){
//excludingaparticulartransitivedependency:
excludegroup:'org.springframework',module:'spring-asm'
}
providedCompile'javax.servlet:javax.servlet-api:3.1.0'
testCompile'junit:junit:4.11',
'org.springframework:spring-test:4.0.3.RELEASE',
'com.jayway.jsonpath:json-path-assert:0.8.1'
}
2. Nowgototherootdirectoryofyourprojectfromthecommandpromptandissuethe
followingcommand:
>gradleeclipse
3. Next,openanewworkspaceinyourSTS,gotoFile|Import,selecttheExisting
ProjectsintoWorkspaceoptionfromthetreelist(youcanfindthisoptionunderthe
Generalnode),andthenclickontheNextbutton.
4. ClickontheBrowsebuttontoselecttherootdirectoryandlocateyourproject
directory.ClickonOKandthenonFinish.
Now,youwillbeabletoseeyourprojectconfiguredwiththerightdependenciesinyour
STS.
UnderstandingtheGradlescript
AtaskinGradleissimilartoagoalinMaven.TheGradlescriptsupportsmanyin-built
pluginstoexecutebuild-relatedtasks.Onesuchpluginisthewarplugin,whichprovides
manyconvenienttaskstohelpyoubuildawebproject.Wecanincorporatethesetasksin
ourbuildscripteasilybyapplyingaplugininourGradlescriptasfollows:
applyplugin:'war'
Similartothewarplugin,thereisanotherplugincalledeclipse-wtptoincorporatetasks
relatedtoconvertingaprojectintoaneclipseproject.Theeclipsecommandweusedin
step2isactuallyprovidedbytheeclipse-wtpplugin.
Insidetherepositoriessection,wecandefineourremotebinaryrepositorylocation.
WhenwebuildourGradleproject,weusethisremotebinaryrepositorytodownloadthe
requiredJARs.Inourcase,wedefinedourremoterepositoryastheMavencentral
repository,asfollows:
repositories{
mavenCentral()
}
Alloftheprojectdependenciesneedtobedefinedinsideofthedependenciessection
groupedunderthescopedeclaration,suchascompile,providedCompile,and
testCompile.Considerthefollowingcodesnippet:
dependencies{
compile
'org.springframework:spring-webmvc:4.0.3.RELEASE',
'javax.servlet:jstl:1.2'.
}
Ifyoulookcloselyatthefollowingdependencydeclarationline,thecompilescope
declaration,youseethateachdependencydeclarationlineisdelimitedwitha:(colon)
symbol,asfollows:
'org.springframework:spring-webmvc:4.0.3.RELEASE'
ThefirstpartofthepreviouslineisthegroupID,thesecondpartistheartifactID,andthe
finalpartistheversioninformationasprovidedinMaven.
So,itismorelikeaMavenbuildscriptbutdefinedusingaGradlescript,whichisbased
ontheGroovylanguage.
AppendixB.PopQuizAnswers
Chapter2,SpringMVCArchitecture–
ArchitectingYourWebStore
Popquiz–requestmapping
Q1 2
Popquiz–thewebapplicationcontext
Q1 1
Q2 3
Popquiz–webapplicationcontextconfiguration
Q1 4
Chapter3,ControlYourStorewith
Controllers
Popquiz–class-levelrequestmapping
Q1 1
Q2 2
Popquiz–requestpathvariable
Q1 4
Q2 1and4
Popquiz–therequestparameter
Q1 2
Chapter5,WorkingwithViewResolver
Popquiz–redirectview
Q1 3
Popquiz–staticview
Q1 2
Chapter6,InterceptYourStorewith
Interceptor
Popquiz–interceptor
Q1 2
Q2 2
Chapter9,ApacheTilesandSpringWeb
FlowinAction
Popquiz–webflow
Q1 3
Q2 1
Popquiz–ApacheTiles
Q1 Allthree(1,2,and3)
Index
A
actionstate,flowdefinition/Understandingtheflowdefinition
addToCartmethod,controllers.js/Whatjusthappened?
afterCompletionmethod
about/Workingwithinterceptors
Ajax
about/HandlingawebserviceinAjax
webservice,handling/HandlingawebserviceinAjax
AngularJs
using/Timeforaction–consumingRESTwebservicesviaAjax,Whatjust
happened?
URL/Whatjusthappened?
ng-repeatdirective/Whatjusthappened?
ng-clickdirective/Whatjusthappened?
ApacheTiles
reusability,enhancingthrough/EnhancingreusabilitythroughApacheTiles
views,creatingforviewstate/Timeforaction–creatingviewsforeveryview
state
templateattribute/Whatjusthappened?
<tiles*insertAttribute>tag/Whatjusthappened?
ApacheTomcat
about/Timeforaction–installingtheTomcatwebserver
URL/Timeforaction–installingtheTomcatwebserver
auditlogging
about/Auditlogging
performing,interceptorused/Auditlogging
C
<C*forEach>tag/Whatjusthappened?
<c*if>tag/Whatjusthappened?
@Categoryannotation/Haveagohero–addingcustomvalidationtoacategory
@Componentannotation/Whatjusthappened?
@ContextConfigurationannotation/Whatjusthappened?
@Controllerannotation/Definingacontroller
about/Whatjusthappened?
cart.jspfile/Whatjusthappened?
Cartclass
creating/Timeforaction–implementingRESTfulwebservices
about/Whatjusthappened?
CartControllerclass
creating/Timeforaction–consumingRESTwebservicesviaAjax
about/Whatjusthappened?
getmethod/Whatjusthappened?
getCartmethod/Whatjusthappened?
CartItemclass
creating/Timeforaction–implementingRESTfulwebservices
about/Whatjusthappened?,Timeforaction–unit-testingdomainobjects
CartRepositoryinterface
creating/Timeforaction–implementingRESTfulwebservices
about/Whatjusthappened?
CartRestControllerclass
creating/Timeforaction–implementingRESTfulwebservices
about/Whatjusthappened?
addItemmethod/Whatjusthappened?
removeItemmethod/Whatjusthappened?
CartRestControllerTestclass/Whatjusthappened?
CartServiceImplclass
creating/Timeforaction–implementingRESTfulwebservices
CartServiceinterface
creating/Timeforaction–implementingRESTfulwebservices
about/Whatjusthappened?
categoryview
adding,toproductspage/Timeforaction–showingproductsbasedon
category,Whatjusthappened?
checkoutflowdefinition
about/Understandingthecheckoutflow
diagrammaticrepresentation/Understandingthecheckoutflow
<flow>tag/Understandingthecheckoutflow
<var>tag/Understandingthecheckoutflow
Orderclass/Understandingthecheckoutflow
ordervariable/Understandingthecheckoutflow
<action-state>definition/Understandingthecheckoutflow
validatemethod,invoking/Understandingthecheckoutflow
<evaluate>tag/Understandingthecheckoutflow
InvalidCartWarningstate/Understandingthecheckoutflow
addCartToOrderstate/Understandingthecheckoutflow
collectCustomerInfostate/Understandingthecheckoutflow
collectShippingDetailstate/Understandingthecheckoutflow
orderConfirmationstate/Understandingthecheckoutflow
processOrderstate/Understandingthecheckoutflow
thankCustomerstate/Understandingthecheckoutflow
cancelCheckoutendstate/Understandingthecheckoutflow
views,creatingforviewstates/Timeforaction–creatingviewsforeveryview
state
collectCustomerInfo.jsp,creating/Timeforaction–creatingviewsforevery
viewstate
collectShippingDetail.jsp,creating/Timeforaction–creatingviewsforevery
viewstate
orderConfirmation.jsp,creating/Timeforaction–creatingviewsforevery
viewstate
InvalidCartWarning.jsp,creating/Timeforaction–creatingviewsforevery
viewstate
thankCustomer.jsp,creating/Timeforaction–creatingviewsforeveryview
state
checkOutCancelled.jsp,creating/Timeforaction–creatingviewsforevery
viewstate
decisionstate,adding/Haveagohero–addingadecisionstate
class-levelrequestmapping
adding/Timeforaction–addingclass-levelrequestmapping,Whatjust
happened?
classesToBeBoundproperty,xmlViewbean/Whatjusthappened?
conditionalredirecting
about/Conditionalredirecting
performing,interceptorused/Conditionalredirecting
offerpagerequests,intercepting/Timeforaction–interceptingofferpage
requests
ContentNegotiatingViewResolver
about/UsingContentNegotiatingViewResolver
configuring/Timeforaction–configuringContentNegotiatingViewResolver,
Whatjusthappened?
contentnegotiation
about/UsingContentNegotiatingViewResolver
controller
defining/Definingacontroller
class-levelrequestmapping,adding/Timeforaction–addingclass-level
requestmapping,Whatjusthappened?
controllers.js
creating/Whatjusthappened?
refreshCartmethod/Whatjusthappened?
initCartIdmethod/Whatjusthappened?
clearCartmethod/Whatjusthappened?
addToCartmethod/Whatjusthappened?
removeFromCartmethod/Whatjusthappened?
CRUDoperations
about/Whatjusthappened?
customvalidationannotation
creating/Haveagohero–addingcustomvalidationtoacategory
considerations/Haveagohero–addingcustomvalidationtoacategory
customvalidationsupport
adding/Timeforaction–addingcustomvalidationsupport
D
dataauditinterceptor
adding/Timeforaction–addingthedataauditinterceptor,Whatjust
happened?
databinding,Springtaglibraries
customizing/Customizingdatabinding
formbindingwhitelisting,adding/Timeforaction–whitelistingformfields,
Whatjusthappened?
DataTransferObjects(DTOs)/Servingandprocessingforms
decisionstate,flowdefinition/Understandingtheflowdefinition
dependencyinjection(DI)
about/Thewebapplicationcontext
developmentenvironment
configuring/Configuringadevelopmentenvironment
dispatcherservlet
about/Thedispatcherservlet
Dispatcherservlet
about/Thedispatcherservlet
configuring/Timeforaction–configuringthedispatcherservlet,Whatjust
happened?
domainlayer,webapplicationarchitecture
about/Thedomainlayer
domainobject,creating/Timeforaction–creatingadomainobject,Whatjust
happened?
domainobjects
unittesting/Timeforaction–unit-testingdomainobjects,Whatjusthappened?
E
eclipse-wtpplugin/UnderstandingtheGradlescript
eclipsecommand/UnderstandingtheGradlescript
endstate,flowdefinition/Understandingtheflowdefinition
environmentvariables,JDK
settingup/Timeforaction–settingupenvironmentvariables
errorRedirectproperty,PromoCodeInterceptorclass
about/Whatjusthappened?
exceptionhandler
adding/Timeforaction–addinganexceptionhandler
F
<form*form>tag/Whatjusthappened?
<form*input>tag/Whatjusthappened?
about/Whatjusthappened?
typeattribute/Whatjusthappened?
flow-executorconfiguration/Understandingthecheckoutflow
flow-registryconfigurationtag/Understandingthecheckoutflow
flowdefinition
about/Understandingtheflowdefinition
startstate/Understandingtheflowdefinition
actionstate/Understandingtheflowdefinition
viewstate/Understandingtheflowdefinition
decisionstate/Understandingtheflowdefinition
subflowstate/Understandingtheflowdefinition
endstate/Understandingtheflowdefinition
flowHandlerAdapter/Understandingthecheckoutflow
flowHandlerMappingparameter/Understandingthecheckoutflow
foreachloop/Whatjusthappened?
forloop
about/Whatjusthappened?
form-backingbean/Whatjusthappened?
forms,Springtaglibraries
serving/Servingandprocessingforms,Timeforaction–servingand
processingforms,Whatjusthappened?
processing/Servingandprocessingforms,Timeforaction–servingand
processingforms,Whatjusthappened?
G
getCartmethod,CartControllerclass/Whatjusthappened?
getProductByIdmethod/Whatjusthappened?
getProductsByFiltermethod/Whatjusthappened?
getTotalPricemethod
about/Timeforaction–unit-testingdomainobjects
Gradle
installing/InstallingGradle
buildscript,configuring/TheGradlebuildscriptforyourproject
Gradlescript
about/UnderstandingtheGradlescript
GRADLE_HOMEvariable/InstallingGradle
H
handlerexceptionresolver
implementing/Workingwiththehandlerexceptionresolver
responsestatusexception,adding/Timeforaction–addingtheresponsestatus
exception,Whatjusthappened?
exceptionhandler,adding/Timeforaction–addinganexceptionhandler,What
justhappened?
HandlerExceptionResolverimplementations
ResponseStatusExceptionResolver/Workingwiththehandlerexception
resolver
ExceptionHandlerExceptionResolver/Workingwiththehandlerexception
resolver
HandlerInterceptorinterface
preHandlemethod/Workingwithinterceptors
postHandlemethod/Workingwithinterceptors
afterCompletionmethod/Workingwithinterceptors
HandlerMappingimplementations
about/Handlermapping
HandlerMappinginterface
about/Handlermapping
HTTPrequests
GET/Timeforaction–consumingRESTwebservices
POST/Timeforaction–consumingRESTwebservices
PUT/Timeforaction–consumingRESTwebservices
DELETE/Timeforaction–consumingRESTwebservices
I
IDE
about/Configuringadevelopmentenvironment
InitBinderannotation/Whatjusthappened?
InMemoryCartRepositoryclass
creating/Timeforaction–implementingRESTfulwebservices
about/Whatjusthappened?
InternalResourceView
about/Resolvingviews
InternalResourceViewResolver
about/Resolvingviews
internationalization
about/Internationalization(i18n)
adding/Timeforaction–addinginternationalization,Whatjusthappened?
J
Javabeanvalidation
about/Beanvalidation
adding,toproductspage/Timeforaction–addingbeanvalidationsupport,
Whatjusthappened?,Haveagohero–addingmorevalidationintheadd
productspage
customvalidationsupport,adding/CustomvalidationwithJSR-303/bean
validation,Timeforaction–addingcustomvalidationsupport,Whatjust
happened?
customvalidation,addingtocategory/Haveagohero–addingcustom
validationtoacategory
JavaRuntimeEnvironment(JRE)/Timeforaction–installingJDK
JavaServerPagesStandardTagLibrary(JSTL)/Timeforaction–addingSpringjars
totheproject,Servingandprocessingforms
JDK
about/SettingupJava
settingup/SettingupJava
installing/Timeforaction–installingJDK
environmentvariables,settingup/Timeforaction–settingupenvironment
variables
JUnit
about/Timeforaction–unit-testingdomainobjects
M
@MatrixVariableannotation/Usingmatrixvariables
@ModelAttributeannotation/Whatjusthappened?
masterdetailview
implementing/Timeforaction–implementingamasterdetailview
matrixvariables
using/Usingmatrixvariables
productlist,filtering/Timeforaction–showingtheproductsbasedonfilter,
Whatjusthappened?
enabling/Whatjusthappened?
Mavenbuildtool
installing/Timeforaction–installingtheMavenbuildtool
configuring,onSTS/Timeforaction–configuringMavenonSTS
messageattribute/Whatjusthappened?
messages_nl.propertiesfile/Whatjusthappened?
multipartrequest
about/Themultipartrequestinaction
imageuploadfacility,addingtoproductpage/Timeforaction–addingimages
totheproductpage
productusermanuals,uploadingtoserver/Haveagohero–uploadingproduct
usermanualstotheserver
multiplefilters
adding,tolistproducts/Haveagohero–addingmultiplefilterstolistproducts
P
@PathVariableannotation/Whatjusthappened?
pathattribute/Whatjusthappened?
persistencelayer,webapplicationarchitecture
about/Thepersistencelayer
repositoryobject,creating/Timeforaction–creatingarepositoryobject,What
justhappened?
pom.xmlfile
configuring/Timeforaction–addingJavaversionpropertiesinpom.xml
postHandlemethod
about/Workingwithinterceptors
Postman
about/Timeforaction–consumingRESTwebservices
downloadpage/Timeforaction–consumingRESTwebservices
installing/Timeforaction–consumingRESTwebservices
preHandlemethod
about/Workingwithinterceptors
processAddNewProductFormmethod/Whatjusthappened?
productcontroller
testing/Timeforaction–testingtheproductcontroller
ProductControllerTestclass
creating/Timeforaction–testingtheproductcontroller
/Whatjusthappened?
productdetailspage
creating/Timeforaction–addingtheproductdetailspage
ProductImageValidatorclass
about/Haveagohero–addingSpringvalidationtotheproductimage
productToBeAddedobject/Whatjusthappened?
productvalidator
testing/Timeforaction–testingtheproductvalidator
ProductValidatorclass/Timeforaction–testingtheproductvalidator
ProductValidatorTestclass/Whatjusthappened?
PromoCodeInterceptorclass
promoCodeproperty/Whatjusthappened?
errorRedirectproperty/Whatjusthappened?
offerRedirectproperty/Whatjusthappened?
promoCodeproperty,PromoCodeInterceptorclass
about/Whatjusthappened?
R
@Repositoryannotation/Whatjusthappened?
@RequestMappingannotation/Timeforaction–examiningrequestmapping,What
justhappened?,Definingacontroller,Whatjusthappened?
about/Whatjusthappened?
RedirectAfterPost/Whatjusthappened?
redirectview
about/Theredirectview
RedirectView
examining/Timeforaction–examiningRedirectView,Whatjusthappened?
refreshCartmethod,controllers.js/Whatjusthappened?
removeFromCartmethod,controllers.js/Whatjusthappened?
requestmapping
examining/Timeforaction–examiningrequestmapping,Whatjusthappened?
requestparameters
about/Understandingrequestparameters
productdetailspage,adding/Timeforaction–addingtheproductdetailspage
masterdetailview,implementing/Timeforaction–implementingamaster
detailview,Whatjusthappened?
multiplefilters,addingtolistproducts/Haveagohero–addingmultiplefilters
tolistproducts
responsestatusexception
adding/Timeforaction–addingtheresponsestatusexception
REST
about/IntroducingREST
RESTcontrollers
testing/Timeforaction–testingRESTcontrollers,Whatjusthappened?
RESTfulwebservices
implementing/Timeforaction–implementingRESTfulwebservices
RESTwebservices
consuming/Timeforaction–consumingRESTwebservices
consuming,viaAjax/Timeforaction–consumingRESTwebservicesvia
Ajax
S
<security*authentication-manager>tag/Whatjusthappened?
<security*http>tag/Whatjusthappened?
<spring*message>tag/Whatjusthappened?,Whatjusthappened?
servicelayer,webapplicationarchitecture
about/Theservicelayer
serviceobject,creating/Timeforaction–creatingaserviceobject,Whatjust
happened?
productdomainobject,accessing/Haveagohero–accessingtheproduct
domainobjectviaaservice
servlet-api/Timeforaction–addingSpringjarstotheproject
SessionLocaleResolverbean/Whatjusthappened?
spring-oxmnotation/Whatjusthappened?
SpringExpressionLanguage/Understandingtheflowdefinition
SpringMVC
controller,defining/Definingacontroller
controller/TheroleofacontrollerinSpringMVC
URItemplatepatterns,using/UsingURItemplatepatterns
SpringMVCarchitecture
dispatcherservlet/Thedispatcherservlet
requestmapping/Timeforaction–examiningrequestmapping
webapplicationcontext/Thewebapplicationcontext
webapplicationcontextconfigurationfile/Thewebapplicationcontext
configuration
viewresolver/Viewresolvers
webapplicationarchitecture/Anoverviewofthewebapplicationarchitecture
SpringMVCinterceptor
about/Workingwithinterceptors
HandlerInterceptorinterface/Workingwithinterceptors
configuring/Timeforaction–configuringaninterceptor,Whatjusthappened?
internationalization/Internationalization(i18n)
SpringMVCproject
creating/CreatingourfirstSpringMVCproject,Timeforaction–creatinga
SpringMVCprojectinSTS,Whatjusthappened?
dependencies,managing/SpringMVCdependencies
Springjars,adding/Timeforaction–addingSpringjarstotheproject,What
justhappened?
Javaversionproperties,addinginpom.xmlfile/Timeforaction–addingJava
versionpropertiesinpom.xml
welcomepage,creating/Ajump-starttoMVC,Timeforaction–addinga
welcomepage
welcomepage,adding/Timeforaction–addingawelcomepage,Whatjust
happened?
deploying/Deployingourproject
running/Timeforaction–runningtheproject
SpringSecurity
using/UsingSpringSecuritytags
SpringSecuritytags
using/UsingSpringSecuritytags
loginpage,adding/Timeforaction–addingaloginpage,Whatjusthappened?
Springtaglibraries
forms,serving/Servingandprocessingforms,Timeforaction–servingand
processingforms,Whatjusthappened?
forms,processing/Servingandprocessingforms,Timeforaction–servingand
processingforms,Whatjusthappened?
databinding,customizing/Customizingdatabinding
textmessages,externalizing/Externalizingtextmessages
SpringSecuritytags/UsingSpringSecuritytags
SpringTestContext
incorporating,totest/IntegrationtestingwiththeSpringTestContext
framework
SpringToolSuite(STS)/Configuringadevelopmentenvironment
installing/Timeforaction–installingSpringToolSuite
URL/Timeforaction–installingSpringToolSuite
Tomcat,configuring/Timeforaction–configuringTomcatonSTS,Whatjust
happened?
Maven,configuring/Timeforaction–configuringMavenonSTS
SpringMVCproject,creating/CreatingourfirstSpringMVCproject,What
justhappened?
Springvalidation
about/Springvalidation
using/Springvalidation
adding/Timeforaction–addingSpringvalidation,Whatjusthappened?
combining,withbeanvalidation/Timeforaction–combiningSpringandbean
validations,Whatjusthappened?
adding,toproductimage/Haveagohero–addingSpringvalidationtothe
productimage
SpringValidatorinterface/Whatjusthappened?
SpringWebFlow
about/WorkingwithSpringWebFlow
orderprocessingservice,implementing/Timeforaction–implementingthe
order-processingservice
checkoutflow,implementing/Timeforaction–implementingthecheckout
flow
flowdefinition/Understandingtheflowdefinition
checkoutflowdefinition/Understandingthecheckoutflow
application,running/Timeforaction–creatingviewsforeveryviewstate,
Whatjusthappened?
startstate,flowdefinition/Understandingtheflowdefinition
staticresources
serving/Servingstaticresources,Timeforaction–servingstaticresources,
Whatjusthappened?
images,addingtoproductdetailpage/Timeforaction–addingimagestothe
productdetailpage
subflowstate,flowdefinition/Understandingtheflowdefinition
T
testing
integrating,withSpringTestContext/IntegrationtestingwiththeSpringTest
Contextframework
tests
adding,forcart/Haveagohero–addingtestsforcart
adding,forremainingRESTmethods/Haveagohero–addingtestsforthe
remainingRESTmethods
textmessages,Springtaglibraries
externalizing/Externalizingtextmessages,Timeforaction–externalizing
messages,Whatjusthappened?
Tomcatwebserver
installing/Timeforaction–installingtheTomcatwebserver
configuring,onSTS/Timeforaction–configuringTomcatonSTS,Whatjust
happened?
transitivedependencies/Whatjusthappened?
U
UnitsInStockValidatorclass/Whatjusthappened?
unittesting
about/Unittesting
domainobjects/Timeforaction–unit-testingdomainobjects,Whatjust
happened?
URItemplatepatterns
using/UsingURItemplatepatterns
categoryview,addingtoproductspage/Timeforaction–showingproducts
basedoncategory,Whatjusthappened?
userinterface(UI)/WorkingwithSpringWebFlow
V
validationMessageSourceproperty,LocalValidatorFactoryBean/Whatjust
happened?
viewresolver
about/Viewresolvers
viewresolvers
about/Resolvingviews
views
resolving/Resolvingviews
InternalResourceView/Resolvingviews
VelocityView/Resolvingviews
FreeMarkerView/Resolvingviews
TilesView/Resolvingviews
RedirectView/Resolvingviews
redirectview/Theredirectview
viewstate,flowdefinition/Understandingtheflowdefinition
W
<web-app>tag/Timeforaction–addingaloginpage
@WebAppConfigurationannotation/Whatjusthappened?
warplugin/UnderstandingtheGradlescript
webapplicationarchitecture
domainlayer/Thedomainlayer
persistencelayer/Thepersistencelayer
servicelayer/Theservicelayer
overview/Anoverviewofthewebapplicationarchitecture
customers,listing/Haveagohero–listingallourcustomers
webapplicationcontext
about/Thewebapplicationcontext
nameandlocation,tweaking/Timeforaction–understandingtheweb
applicationcontext,Whatjusthappened?
configurationfile/Thewebapplicationcontextconfiguration
webserver
installing/Installingawebserver
webservice
handling,inAjax/HandlingawebserviceinAjax