4.3. Session Management¶
Table of Contents
- Overview
- How to use
- How to extend
- Appendix
4.3.1. Overview¶
This chapter explains Session Management in a Web application.
Sr. No. Description (1) Web browser (Client) accesses the Web application (Server) when session is not established. (2) Web application createsHttpSession
object for storing the session with Web browser. Session ID is issued at the time ofHttpSession
object creation. (3) Web application stores the data sent by the Web browser inHttpSession
object. (4) Web application sends a response to the Web browser. By setting “JSESSIONID = Issued session ID” in the “Set-Cookie” header of response, session ID is linked to the Web Browser.Linked session ID is stored in Cookie. (5) Web browser links the session ID to Web application by setting “JSESSIONID = Issued session ID” in the “Cookie” header of request.Application server on which the Web application is deployed, fetchesHttpSession
object corresponding to the session ID linked from the Web browser and maps it with the request. (6) Web application fetches the data stored in the request mentioned in (1), from theHttpSession
object associated with the request.Same data can be accessed across requests. (7) Web application returns the response to Web browser.Note
About the parameter name to link session ID
In JavaEE Serlvet specifications, the default parameter name to link a session ID is JSESSIONID.
4.3.1.1. Session lifecycle¶
Note
"Session"
mentioned in the following explanation refers tojavax.servlet.http.HttpSession
object provided by Servlet API.HttpSession
object is the Java object representing the logical session described above.
4.3.1.1.1. Generating a session¶
When Web application is created by the method recommended in this guideline, session can be generated by any one of the following processes.
Sr. No. Description
Authentication/authorization process provided by Spring Security.Timing and status of session generation can be specified by Spring Security settings.For details on session management in Spring Security, refer to How to use.
CSRF token check process provided by Spring SecurityWhen a session is already established, new session is not generated.For details on CSRF token check, refer to CSRF Countermeasures.
Transaction token check process provided by common library.When a session is already established, new session is not generated.For details on Transaction token check, refer to Double Submit Protection.
Process for passing a model (form object, domain object etc.) to redirect destination request, by using addFlashAttribute method ofRedirectAttributes
interface.When a session is already established, new session is not generated.For details onRedirectAttributes
and Flash scope, refer to Passing data while redirecting request.
Process for storing a model (form object, domain object etc.) in a session, using@SessionAttributes
annotation.The specified model (form object, domain object etc.) is stored in session. When a session is already established, new session is not generated.For details on how to use@SessionAttributes
annotation, refer to Using @SessionAttributes annotation.
Process that uses session-scoped bean in Spring Framework.When a session is already established, new session is not generated.For details on how to use session-scoped bean, refer to Using session-scoped bean of Spring Framework.Note
In Sr. Nos. 4, 5, 6 mentioned above, whether the session is used or not is specified by Controller implementation whereas, session generation timing is controlled by the framework. In other words,
HttpSession
API need not be used directly as Controller process.
4.3.1.1.2. Storing attributes in a session¶
When Web application is created by the methods recommended in this guideline, attributes (objects) are stored in session by any one of the following processes.
Sr. No. Description
Authentication process provided by Spring Security.Authenticated user information is stored in the session.For details on Spring Security authentication process, refer to Authentication.
CSRF token check process provided by Spring Security.Issued token value is stored in session.For details on CSRF token check, refer to CSRF Countermeasures.
Transaction token check process provided by common library.Issued token value is stored in session.For details on Transaction token check, refer to Double Submit Protection.
Process for passing a model (form object, domain object etc.) to redirect destination request, by using addFlashAttribute method ofRedirectAttributes
interface.Object specified in the addFlashAttribute method ofRedirectAttributes
interface, is stored in an area called Flash scope in the session.For details onRedirectAttributes
and Flash scope, refer to Passing data while redirecting request.
Process for storing a model (form object, domain object etc.) in session, using@SessionAttributes
annotation.The specified model (form object, domain object etc.) is stored in session.For the details on how to use@SessionAttributes
annotation, refer to, Using @SessionAttributes annotation.
Process that uses session-scoped bean in Spring Framework.Session-scoped bean is stored in session.For details on how to use session-scoped bean, refer to Using session-scoped bean of Spring Framework.Note
Timing to store the object in session is controlled by the framework. Hence, setAttribute method of
HttpSession
object is not called as Controller process.
4.3.1.1.3. Deleting attributes from a session¶
When Web application is created by the methods recommended in this guideline, attributes (objects) are deleted from a session by any one of the following processes.
Sr. No. Description
Logout process provided by Spring Security.Authenticated user information is deleted from the session.For details on Spring Security logout process, refer to Authentication.
Transaction token check process provided by common library.When the value of issued token exceeds the upper limit allocated to NameSpace, token value that is not in use, is deleted from the session.For details on Transaction token check, refer to Double Submit Protection.
Redirect process after the object is stored in Flash scope.Object specified in the addFlashAttribute method ofRedirectAttributes
interface, is deleted from the Flash scope area of session.
Framework process, after the setComplete method ofSessionStatus
object is called as Controller process.Object specified by@SessionAttributes
annotation is deleted from the session.Note
Timing to delete the object is controlled by the framework. Hence, removeAttribute method of
HttpSession
object is not called as Controller process.
4.3.1.1.4. Discarding a session¶
When Web application is created by the methods recommended in this guideline, session is discarded using any one of the following processes.
Sr. No. Description
Logout process provided by Spring Security.For details on Spring Security logout process, refer to Authentication.
Process for detecting session timeout of application server.
The scenario in which a session is discarded explicitly is illustrated below.
Sr. No. Description (1) Access the process that discards session from Web browser.When using Spring Security, the logout process provided by it is used to discard the session.For details on logout process of Spring Security, refer to Authentication. (2) Web application discardsHttpSession
object corresponding to the session ID linked from Web browser.At this point,HttpSession
object with the ID,"SESSION01"
, disappears from server side. (3) When the discarded session is accessed from Web browser using the respective session ID, a new session is generated, asHttpSession
object corresponding to the requested session ID does not exist.In the above example, a session with ID"SESSION02"
is being generated.
The scenario in which a session is automatically discarded due to timeout is illustrated below.
Sr. No. Description (1) When an established session is not accessed for a particular period, application server detects session timeout. (2) Application server discards the session for which session timeout is detected. (3) When the session is accessed from Web browser after session timeout occurs, session timeout error is returned to Web browser sinceHttpSession
object corresponding to the session ID requested from the Web browser, does not exist.Note
Designing session timeout
When data needs to be stored in the session, the design should include ‘session timeout’. It is recommended to set the timeout as short as possible, especially when the data to be stored is large.
Note
Default session timeout period
Default session timeout period differs depending on application server.
- Tomcat: 1800 seconds (30 minutes)
- WebLogic: 3600 seconds (60 minutes)
- WebSphere: 1800 seconds (30 minutes)
- JBoss: 1800 seconds (30 minutes)
4.3.1.1.5. Detecting a request after session timeout¶
When Web application is created by the method recommended in this guideline, a request subsequent to session timeout is detected by any one of the following processes.
Sr. No. Description
Session timeout check process provided by Spring Security.Session timeout check is performed as per default settings in Spring Security.Therefore, to store data in session, settings to validate the timeout check process of a Spring Security session, are required.For details on timeout check process in Spring Security, refer to How to use.
When not using Spring Security, timeout check process needs to be implemented in Servlet Filter orHandlerInterceptor
of Spring MVC.
The scenario in which session timeout is detected using session check process provided by Spring Security, is illustrated below.
Sr. No. Description (1) When an established session is not accessed for a particular period, application server detects session timeout and discards the session. (2) Web browser accesses the session after the session timeout occurs. (3) Spring Security throws session timeout error as, theHttpSession
object corresponding to the session ID linked from client, does not exist.In default Spring Security implementation, response is sent to the request for redirecting to URL to display the error screen.Note
Necessity of session timeout check
For the processes having precondition, “Data should be stored in session”, session timeout check should always be performed. If this check is not performed, unexpected system errors and operations may occur, as data required by the process cannot be fetched.
4.3.1.2. About using a session¶
Note
Rather than simply storing the data in session, this guideline initially recommends to consider a policy wherein session is not used. Further, if session is used, it recommends storing only the absolutely required data in it.
Note
Storing the data applicable to following conditions has proved better.
Data that does not provide linkage between use cases, but for such data, the status when it is moved and returned from a different use case needs to be stored.For example, search condition of a List screen corresponds to this pattern.When the search condition of a List screen returns from another use case (example: “Change searched data” use case), many a times its status before moving to other use case needs to be stored as a functional requirement.Search conditions can also be shared in hidden state but this causes excessive dependency between use cases and implementing the application is expected to be complex. Data for which linkage between use cases is necessary.For example, data stored in the shopping site cart corresponds to this pattern.Data stored in the shopping site cart involves use cases of ” Adding the product to the cart”, ” Displaying the cart”, “Changing the status of the cart” and ” Purchasing the products in the cart”. It requires the data linkage of all these use cases.However, when scalability needs to be considered, sometimes it is better to store the data in database instead of session.
4.3.1.3. Advantages and disadvantages of using session¶
The advantages and disadvantages of using session are as follows:
Advantages
- Data can be shared across multiple screens (multiple requests) easily when a single process is composed in multiple screens such as a Wizard Screens.
- By storing the fetched data in session, number of executions of data acquisition can be reduced.
Disadvantages
- When the screen with same process is opened in multiple browsers or various tabs, data consistency cannot be maintained, as mutual operations interfere with the data stored in session.In order to maintain data consistency, control is required so that screen with the same process cannot be opened in multiple browsers or tabs.This control can be implemented by using Transaction token check provided by common library, however it results in reduced usability.
- Session data is usually stored in application server memory; hence memory usage also increases with increase in data stored in the session.If unnecessary data that is no longer used in a process is left as it is, it gets excluded from garbage collection process, leading to memory exhaustion. Such data needs to be deleted from session at a stage when it is rendered unnecessary.The timing to delete unnecessary data from session needs to be designed separately.
- Storing process data in session may lower scalability of application server.
Note
Any one of the following methods is required to scale out the application server.
- Performing session replication and sharing the session information with all application servers.When performing session replication, the load on replication process increases in proportion to the data stored in session and number of application servers for replication.Therefore, issues owing to scale out such as, possible degradation in response time etc. need to be considered.
- Distributing all requests of the same session to the same application server, using load balancer.When requests are distributed to the same application server and if the server is down, the process cannot be continued by another application server.Therefore, it needs to be noted that, this method may not be feasible in applications that demand high availability (service level).
Scale out method should be determined upon considering each of these points.
4.3.1.3.1. Advantages and disadvantages of not using session¶
Advantages
- As data is not stored at server side, even if multiple browsers and tabs are used, their operations do not interfere with each other. Therefore, multiple screens of the same process can be started without impairing the usability.
- As data is not stored at server side, continuous utilization of memory can be controlled.
- Number of factors that lower the scalability of application server are reduced.
Disadvantages
- Data required for server processing needs to be sent as request parameter. As a result, even the items that are not displayed on the screen need to be specified as hidden items.Thus, JSP implementation code increases.This can be minimized by creating JSP tag library.
- Amount of data flowing to the network increases, as all the data required for server processing needs to be sent through requests.
- Data required for screen display needs to be fetched each time. Hence, number of executions of data acquisition increases.
4.3.1.4. About the data to be stored in session¶
Following points need to be considered for the data to be stored in session.
- It should be serializable object (object implementing
java.io.Serializable
). - It should not be a large object that can cause memory exhaustion.
4.3.1.4.1. Serializable objects¶
Cases where data is input/output to disk are as follows:
- When application server is stopped while a session is active, the session and the data stored in it are saved to the disk.The saved session and data stored in it, are restored with the start of application server.Support status for this data restoring operation differs depending on application server.
- If session storage area is about to overflow or if the session is not accessed for a particular period of time since the last access, there is a possibility of session swap-out.The swapped-out session is swapped-in when the session is accessed again.Conditions etc. for swap-out differ depending on application server.
Cases where data is input/output to network are as follows:
- To perform replication of session in another application server, data stored in the session is sent to that application server via network.
4.3.1.4.2. Amount of data to be stored in session¶
It is recommended that the data to be stored in session should be as compact as possible.
When large amount of data is stored in session, it fatally degrades the performance. Hence, it is recommended to design such that, large amount of data will not be stored in session.
The main causes of performance degradation are as follows:
- When storing large amount of data in session, application server settings need to be enabled such that session is easily swapped-out so as to prevent memory exhaustion.Swap-out is a “Heavy” process. Hence, it occurring very frequently may affect the overall application performance.Support status for swap-out operation or setting method, differs depending on application server.
- When carrying out session replication, serialization and deserialization of an object are performed.Serialization and deserialization of an object with large capacity being “heavy” processes, may affect performance, such as response time.
To make the session data compact, storing the data applicable to the following conditions in request scope instead of session scope, should be considered.
- Read-only data that cannot be edited by screen operations.If latest data is fetched at a time when it is required, and if the fetched data is displayed in View (JSP) by storing it in request scope, it need not be stored in session.
- Data that can be edited by screen operations. It can be shared only in the screen operations of a use case.If data can be shared in all screen transitions as hidden HTML fields, it need not be stored in session.
4.3.1.5. Points to be considered in case of application server clustering¶
- For systems that require high availability (service level), if one AP server is down, it should be possible to continue the processing on another AP server.To be able to continue processing on another AP server when one AP server is down, session information needs to be shared amongst all AP servers. Hence, session replication needs to be carried out by application servers that are configured as a cluster.As an alternate method, session information can be shared by setting the session storage location to cache server such as Oracle Coherence or database.It would be better to consider setting the session storage location to a cache server like Oracle Coherence or database if, number of AP servers, amount of data stored in session and number of sessions that can be pasted simultaneously are in large quantity.
- For systems that do not require high availability (service level), processing need not be continued on another server if the AP server is down.Therefore, session information need not be shared amongst all AP servers. Thus, it is alright if all the requests in a same session are distributed to the same AP server, using load balancer functionality.
Warning
When the Web application is created by methods recommended in this guideline, either of the above mechanisms needs to be applied to store the following data in session.
- User information authenticated by the Spring Security authentication process.
- Token values issued by Spring Security CSRF token check.
- Token values issued by the transaction token check provided by common library.
4.3.1.6. About storage location of session¶
4.3.2. How to use¶
This guideline recommends using any one of the following methods to store data in session.
Warning
HttpSession
API can be called directly by specifying the HttpSession
object as an argument of Controller handler method. However,
as a rule, this guideline strongly recommends not to use the HttpSession API directly.
HttpSession
API may be used directly only for those processes that cannot be implemented otherwise. However,
in most of the business processes, it is not necessary to use the HttpSession API directly. Therefore, set such that HttpSession
object is not specified as an argument of the Controller handler method.
4.3.2.1. Using @SessionAttributes
annotation¶
@SessionAttributes
annotation is used for sharing data during screen transitions in Controller.
@SessionAttributes
annotation, should be considered.4.3.2.1.1. Specifying the object to be stored in session¶
Specify the object to be stored in session by specifying @SessionAttributes
annotation in “class”.
@Controller @RequestMapping("wizard") @SessionAttributes(types = { WizardForm.class, Entity.class }) // (1) public class WizardController { // ... }
Sr. No. Description (1) In “types” attribute of@SessionAttributes
annotation, specify the object type to be stored in session.Amongst the objects added toModel
object using@ModelAttribute
annotation or addAttribute method ofModel
, the objects matching with the type specified in “types” attribute, are stored in session.In the above example, objects ofWizardForm
class andEntity
class are stored in session.Note
Management unit for life cycle
Life cycles of the objects stored in session using
@SessionAttributes
annotation, are managed at Controller level.When setComplete method of
SessionStatus
object is called, all the objects specified with `` @SessionAttributes `` annotation are deleted from session. Therefore, to store the objects having different life cycles in session, it is necessary to divide the Controller.Warning
Points to be considered when using @SessionAttributes annotation
As described above, life cycle is managed at Controller level. However, when the object with the same attribute name in multiple Controllers, is stored in session using
@SessionAttributes
annotation, its life cycle is managed across Controllers.In case of a process wherein, screen can be operated simultaneously by opening another window or tab, it can cause an error as the same object is being accessed. Therefore, to use the class of the same form object in multiple Controllers, first different values (attribute names) should be specified in “value” attribute of
@ModelAttribute
annotation. Then the same values should be specified in “value” attribute of@SessionAttributes
annotation.
@Controller @RequestMapping("wizard") @SessionAttributes(value = { "wizardCreateForm" }) // (2) public class WizardController { // ... @ModelAttribute(value = "wizardCreateForm") public WizardForm setUpWizardForm() { return new WizardForm(); } // ... }
Sr. No. Description (2) Specify the attribute name of the object to be stored in session, in “value” attribute of@SessionAttributes
annotation.Among the objects added toModel
object using@ModelAttribute
annotation or addAttribute method ofModel
, the objects matching with the attribute name specified invalue
attribute, are stored in session.In the above example, objects with attribute name"wizardCreateForm"
are stored in session.
4.3.2.1.2. Adding object to session¶
Object can be added to session using the following two methods.
- Method with
@ModelAttribute
annotation is used to return the object to be added to the session. - addAttribute method of
Model
object is used to add the object stored in session.
Object added to Model
object is stored in session as per the type of @SessionAttributes
annotation and value of “value” attribute.
Hence, there is no need to consider the session when implementation is carried out using handler method of Controller.
@ModelAttribute
annotation, is explained below.@ModelAttribute(value = "wizardForm") // (1) public WizardForm setUpWizardForm() { return new WizardForm(); }
Sr. No. Description (1) In “value” attribute, specify the attribute name to be stored inModel
object.In the above example, the object returned is stored in session, with attribute name"wizardForm"
.When “value” attribute is specified, method with@ModelAttribute
annotation is no longer called by the subsequent requests after the object is stored in session. Thus, it has an advantage wherein, unnecessary objects are not generated.Warning
Operation in case “value” attribute of @ModelAttribute annotation is omitted
When “value” attribute is omitted, method with
@ModelAttribute
annotation is called by all requests in order to generate default attribute name. Therefore, it has a disadvantage of unnecessary objects being generated. Hence, This method should not be used when storing objects in session.@ModelAttribute // (1) public WizardForm setUpWizardForm() { return new WizardForm(); }
Sr. No. Description (1) Use the method with@ModelAttribute
annotation to generate and return the object to be added in session.In the above example, object returned with attribute name"wizardForm"
annotation, is stored in session.
Model
object is explained below.@RequestMapping(value = "update/{id}", params = "form1") public String updateForm1(@PathVariable("id") Integer id, WizardForm form, Model model) { Entity loadedEntity = entityService.getEntity(id); model.addAttribute(loadedEntity); // (3) beanMapper.map(loadedEntity, form); return "wizard/form1"; }
Sr. No. Description (3) Add the object to be stored in session using addAttribute method ofModel
object.In the above example, the object fetched from domain layer with the attribute name"entity"
is stored in session.
4.3.2.1.3. Fetching the object stored in session¶
Model
object as per the attribute value of @SessionAttributes
annotation.@RequestMapping(value = "save", method = RequestMethod.POST) public String save(@Validated({ Wizard1.class, Wizard2.class, Wizard3.class }) WizardForm form, // (1) BindingResult result, Entity entity, // (2) RedirectAttributes redirectAttributes) { // ... return "redirect:/wizard/save?complete"; }
Sr. No. Description (1) Fetch the object stored inModel
object.In the above example, object stored in session scope with attribute name"wizardForm"
is passed to argument form.For details onWizard1.class
,Wizard2.class
,Wizard3.class
specified by@Validated
annotation, refer to Example of screen transition implementation using @SessionAttributes in wizard format of Appendix. (2) In the above example, object stored in session scope with attribute name"entity"
, is passed to argument “entity”.Note
How to prevent binding of request parameters when receiving object stored in session scope
When an object stored in the session scope is received as an argument of the handler method, there is a possibility that the request parameter is bound to the argument.
In order to prevent the request parameter from being bound, it can be realized by getting the object stored in the session scope from the
Model
object in the handler method without receiving it from the argument of the handler method, but it is not type-safe because it is necessary to specify the attribute name of the object to be acquired as a character string.On the other hand, in Spring Framework 4.3, the
binding
attribute is added to theModelAttribute
annotation, so that it becomes possible to specify whether or not to bind request parameters as arguments. By attaching the@ ModelAttribute
annotation as an argument and specifyingfalse
as thebinding
attribute, it is possible to prevent binding of request parameters and objects stored in the scope can be acquired.In the example below, an object stored in the session scope with an attribute name
entity
is acquired by preventing binding of request parameters.@RequestMapping(value = "save", method = RequestMethod.POST) public String save(@Validated({ Wizard1.class, Wizard2.class, Wizard3.class }) WizardForm form, BindingResult result, @ModelAttribute(binding = false) Entity entity, RedirectAttributes redirectAttributes) { // ... return "redirect:/wizard/save?complete"; }
When the object to be passed to the argument of Controller handler method does not exist in Model
object, the operation changes depending on whether @ModelAttribute
annotation is specified or not.
- When
@ModelAttribute
annotation is not specified, a new object is created and passed as argument. The created object is stored inModel
object and subsequently in session as well.
Note
Redirect operations
Created object is not stored in session if it is redirected to a transition destination. Therefore, when referring to the created object in redirect process, it is necessary to store the object in Flash scope using addFlashAttribute method of
RedirectAttributes
.
- When
@ModelAttribute
annotation is specified,org.springframework.web.HttpSessionRequiredException
occurs.
@RequestMapping(value = "save", method = RequestMethod.POST) public String save(@Validated({ Wizard1.class, Wizard2.class, Wizard3.class }) WizardForm form, // (3) BindingResult result, @ModelAttribute Entity entity, // (4) RedirectAttributes redirectAttributes) { // ... return "redirect:/wizard/save?complete"; }
Sr. No. Description (3) Input validation is performed by setting specific verification groups (Wizard1.class
,Wizard2.class
,Wizard3.class
) using@Validated
annotation.For details on input validation, refer to Input Validation. (4) When@ModelAttribute
annotation is specified in argument and when the target object that does not exist in session is called,HttpSessionRequiredException
occurs.HttpSessionRequiredException
occurs due to client operations such as browser back, directly accessing specified URL etc. Therefore, the exception needs to be handled as client error.
Following are the settings to handle HttpSessionRequiredException
as client error.
- spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <property name="exceptionCodeResolver" ref="exceptionCodeResolver" /> <!-- ... --> <property name="exceptionMappings"> <map> <!-- ... --> <entry key="HttpSessionRequiredException " value="common/error/operationError" /> <!-- (5) --> </map> </property> <property name="statusCodes"> <map> <!-- ... --> <entry key="common/error/operationError" value="400" /> <!-- (6) --> </map> </property> <!-- ... --> </bean>
Sr. No. Description (5) Add the exception handling definition ofHttpSessionRequiredException
toexceptionMappings
ofSystemExceptionResolver
provided by common library.In the above example,/WEB-INF/views/common/error/operationError.jsp
is specified as the transition destination at the time of exception. (6) InstatusCodes
ofSystemExceptionResolver
, specify the HTTP response code that is generated whenHttpSessionRequiredException
occurs.In the above example, Bad Request (400
) is specified as the HTTP response code at the time of exception.
- applicationContext.xml
<bean id="exceptionCodeResolver" class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver"> <!-- Setting and Customization by project. --> <property name="exceptionMappings"> <map> <!-- ... --> <entry key="HttpSessionRequiredException" value="w.xx.0003" /> <!-- (7) --> </map> </property> <property name="defaultExceptionCode" value="e.xx.0001" /> <!-- (8) --> </bean>
Sr. No. Description (7) Add the exception handling definition ofHttpSessionRequiredException
toexceptionMappings
ofSimpleMappingExceptionCodeResolver
provided by common library.In the above example,"w.xx.0003"
is specified as the exception code at the time of exception.When this setting is not added, default exception code is output to log. (8) Default exception code at the time of exception.
4.3.2.1.4. Deleting the object stored in session¶
@SessionAttributes
, call setComplete method of org.springframework.web.bind.support.SessionStatus
from the handler method of Controller.SessionStatus
object, the object specified in attribute value of @SessionAttributes
annotation, is deleted from session.Note
Time when the object is deleted from session
By calling setComplete method of
SessionStatus
object, the object specified in attribute value of@SessionAttributes
annotation is deleted from session. However, the actual time when the object is deleted is different than when setComplete method is called.setComplete method of
SessionStatus
object changes only the internal flag, whereas, the actual deletion of object is carried out by the framework, after the handler method of Controller is completed.Note
Referring object from View (JSP)
Object is deleted from session by calling the setComplete method of
SessionStatus
object. However, it can be referred from View (JSP) as the same object remains inModel
object.
The objects stored in session need to be deleted from the following 3 locations.
- Request to display completion screen. (Mandatory)Delete unnecessary objects, since the objects stored in session are not accessed once completion screen is displayed.
Warning
Reason for deletion
The objects stored in session are outside the scope of garbage collection process. As a result, if unnecessary objects are not deleted, it leads to memory exhaustion. Moreover, storing unnecessary objects in session results in session swap-out which is a heavy process and may affect the overall application performance.
- Request to stop the consecutive screen operations. (Mandatory)Events such as “Go Back to Menu” or “Cancel” etc. to stop the consecutive screen operations, do not access the objects stored in session. Therefore, unnecessary objects should be deleted from session.
- Request for initial display of input screen. (Optional)
Warning
Reason for deletion
When the browser or tab is closed during screen operations, information entered during input, remains in the form object stored in session. As a result, this information is displayed on screen if it is not deleted at the time of initial display. However, in cases where it is alright to display this information on screen, it is not mandatory to delete the information using request for initial display.
Example given below illustrates implementation of object deletion by using a request to display completion screen.
// (1) @RequestMapping(value = "save", method = RequestMethod.POST) public String save(@ModelAttribute @Validated({ Wizard1.class, Wizard2.class, Wizard3.class }) WizardForm form, BindingResult result, Entity entity, RedirectAttributes redirectAttributes) { // ... return "redirect:/wizard/save?complete"; // (2) } // (3) @RequestMapping(value = "save", params = "complete", method = RequestMethod.GET) public String saveComplete(SessionStatus sessionStatus) { sessionStatus.setComplete(); // (4) return "wizard/complete"; }
Sr. No. Description (1) Handler method to perform update process. (2) Redirect to request (3), in order to display completion screen. (3) Handler method to display completion screen. (4) Call setComplete method ofSessionStatus
object and delete the object from session.Display process for View (JSP) is not affected directly as the same object remains inModel
object.
Example given below illustrates implementation of object deletion by using a request to stop consecutive operations.
// (1) @RequestMapping(value = "save", params = "cancel", method = RequestMethod.POST) public String saveCancel(SessionStatus sessionStatus) { sessionStatus.setComplete(); // (2) return "redirect:/wizard/menu"; // (3) }
Sr. No. Description (1) Handler method to stop the consecutive screen operations. (2) Call setComplete method ofSessionStatus
object and delete the object from session. (3) In the above example, user is redirected to Menu Screen.
Example given below illustrates implementation of object deletion by using a request for initial display of input screen.
// (1) @RequestMapping(value = "create", method = RequestMethod.GET) public String initializeCreateWizardForm(SessionStatus sessionStatus) { sessionStatus.setComplete(); // (2) return "redirect:/wizard/create?form1"; // (3) } // (4) @RequestMapping(value = "create", params = "form1") public String createForm1() { return "wizard/form1"; }
Sr. No. Description (1) Handler method for initial display of input screen. (2) Call setComplete method ofSessionStatus
object. (3) Redirect to request (4), that displays input screen.Object is deleted from session by calling the setComplete method ofSessionStatus
object.However, as the same object remains inModel
object,if View (JSP) is called directly, the information being entered is displayed.Therefore, It should be redirected to the request for displaying input screen after deleting from session. (4) Handler method to display input screen.
4.3.2.1.5. Process implementation using @SessionAttributes¶
For specific implementation, refer to Example of screen transition implementation using @SessionAttributes in wizard format of Appendix.
4.3.2.2. Using session-scoped bean of Spring Framework¶
4.3.2.2.1. Bean definition of session scope¶
Define session-scoped bean of Spring Framework.
There are 2 methods to define the session-scoped bean.
- Define bean using component-scan.
- Define bean in Bean definition file (XML).
How to use component-scan is shown below.
- Class
@Component @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) // (1) public class SessionCart implements Serializable { private static final long serialVersionUID = 1L; private Cart cart; public Cart getCart() { if (cart == null) { cart = new Cart(); } return cart; } public void setCart(Cart cart) { this.cart = cart; } public void clearCart() { // (2) cart.clearCart(); } }
Sr. No. Description (1) Set Bean scope to"session"
. Also, it be enabled the scoped-proxy by specifying aScopedProxyMode.TARGET_CLASS
inproxyMode
attribute. (2) Create a method to clear the cart content (delete products from the cart) while order has been finished.Note
To define Entity class to be handled using JPA as session scope ban, it is recommended to provide a wrapper class instead of defining it directly as session-scoped bean.
If Entity class to be handled using JPA is defined as session-scoped bean, it cannot be handled directly using the API of JPA (throws error if handled directly). Therefore, a process to convert it to Entity object that can be handled in JPA is required.
In the above example, JPA Entity class called
Cart
is wrapped in a wrapper class calledSessionCart
and set as session-scoped bean. With this, the process to convert it to Entity object that can be handled in JPA is no longer required; hence the process to be performed in Controller becomes simple.Note
Reason for enabling scoped-proxy
scoped-proxy needs to be enabled to inject session-scoped bean in singleton scope Controller.
- spring-mvc.xml
<context:component-scan base-package="xxx.yyy.zzz.app" /> // (2)
Sr. No. Description (2) Scan components using the<context:component-scan>
element. Specify a base package to scan components in thebase-package
attribute.
How to define it in Bean definition file (XML), is shown below.
- JavaBean
<beans:bean id="sessionCart" class="xxx.yyy.zzz.app.SessionCart" scope="session"> <!-- (3) --> <aop:scoped-proxy /> <!-- (4) --> </beans:bean>
Sr. No. Description (3) Set Bean scope to"session"
. (4) Specify<aop:scoped-proxy />
element and enable scoped-proxy.
4.3.2.2.2. Using session-scoped bean¶
@Inject SessionCart sessionCart; // (1) @RequestMapping(value = "add") public String addCart(@Validated ItemForm form, BindingResult result) { if (result.hasErrors()) { return "item/item"; } CartItem cartItem = beanMapper.map(form, CartItem.class); Cart addedCart = cartService.addCartItem(sessionCart.getCart(), // (2) cartItem); sessionCart.setCart(addedCart); // (3) return "redirect:/cart"; }
Sr. No. Description (1) Inject session-scoped bean to Controller. (2) On calling the method of session-scoped bean, object stored in session is returned.When there is no object stored in session, newly created object is returned as well as stored in session.In the above example, Service method is called to check inventory etc. before adding to cart. (3) In the above example,Cart
object passed as an argument of addCartItem method ofCartService
andtheCart
object returned with value, may form a separate instance.Therefore, the returnedCart
object is set to session-scoped bean.Note
How to refer session-scoped Bean from View(JSP)
A session-scoped Bean can be referred from JSP even when Bean is not added to
Model
object in Controller by using SpEL(Spring Expression Language) formula.<spring:eval var="cart" expression="@sessionCart.cart" /> <%-- (1) --%> <%-- omitted --%> <c:forEach var="item" items="${cart.cartItems}"> <%-- (2) --%> <tr> <td>${f:h(item.id)}</td> <td>${f:h(item.itemCode)}</td> <td>${f:h(item.quantity)}</td> </tr> </c:forEach>
Sr. No. Description (1) Refer session-scoped Bean. (2) Display session-scoped Bean.
4.3.2.2.3. Deleting objects stored in session¶
Note
The IoC Container destroys session-scoped beans when a session expires.
Since the IoC Container manages the lifecycle of session-scoped beans, avoid deleting them explicitly.
@Controller @RequestMapping("order") public class OrderController { @Inject SessionCart sessionCart; // (1) // ... @RequestMapping(method = RequestMethod.POST) public String order() { // ... return "redirect:/order?complete"; } @RequestMapping(params = "complete", method = RequestMethod.GET) public String complete(Model model, SessionStatus sessionStatus) { sessionCart.clearCart(); // (2) return "order/complete"; } }
Sr. No. Description (1) An injectable session-scoped bean. (2) delete all items which have already been ordered by initializing the state of the session-scoped bean.
4.3.2.2.4. Process implementation using session-scoped bean¶
For a more specific implementation, refer to Example of screen transition across multiple Controllers using session-scoped bean. of Appendix.
4.3.2.3. Debug log output of session operations¶
For details on common library, refer to :ref:logging_appendix_httpsessioneventlogginglistener.
4.3.2.4. Using the JSP implicit object sessionScope
¶
To use JSP implicit object sessionScope
, the session attribute value of page directive needs to be set to true
.
It is set as false
in the include.jsp
, provided by blank project.
include.jsp
is stored in the src/main/webapp/WEB-INF/views/common
directory.
include.jsp
<%@ page session="true"%> <%-- (1) --%> <%-- omitted --%>
Sr. No. Description (1) Set the session attribute value of page directive totrue
.
4.3.3. How to extend¶
4.3.3.1. Synchronizing requests in same session¶
@SessionAttributes
annotation or session-scoped bean.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
to ‘true’ and synchronize the requests in the same session.It can be implemented by creating BeanPostProcessor as follows and performing bean definition.
- Component
package com.example.app.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; public class EnableSynchronizeOnSessionPostProcessor implements BeanPostProcessor { private static final Logger logger = LoggerFactory .getLogger(EnableSynchronizeOnSessionPostProcessor.class); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // NO-OP return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instance of RequestMappingHandlerAdapter) { RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean; logger.info("enable synchronizeOnSession => {}", adapter); adapter.setSynchronizeOnSession(true); // (1) } return bean; } }
Sr. No. Description (1) Requests in the same session can be synchronized by specifyingtrue
as an argument of setSynchronizeOnSession method oforg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
.
- spring-mvc.xml
<bean class="com.example.app.config.EnableSynchronizeOnSessionPostProcessor" /> <!-- (2) -->
Sr. No. Description (2) Define bean forBeanPostProcessor
created in (1).
4.3.4. Appendix¶
4.3.4.1. Example of screen transition implementation using @SessionAttributes
in wizard format¶
Screen transition implementation using @SessionAttributes
annotation in wizard format, is explained below.
Process specifications are as follows:
- Provide screen for registering and updating entity.
- Input screen consists of 3 sub-screens wherein one field is entered on each sub-screen.
- Before saving (registering/updating), the entered values can be verified on confirmation screen.
- Input validation is performed at the time of screen transition. User is returned to input screen in case of error.
- Before saving (registering/updating), perform input validation again for all the input values and display the error screen notifying invalid operations.
- If all the input values are appropriately validated, save the input data to the database.
Basic screen transition is as follows:
Example of implementation is as follows:
- Form object
public class WizardForm implements Serializable { private static final long serialVersionUID = 1L; // (1) @NotEmpty(groups = { Wizard1.class }) private String field1; // (2) @NotEmpty(groups = { Wizard2.class }) private String field2; // (3) @NotEmpty(groups = { Wizard3.class }) private String field3; // ... // (4) public static interface Wizard1 { } // (5) public static interface Wizard2 { } // (6) public static interface Wizard3 { } }
Sr. No. Description (1) Field to be entered on the first page of input screen. (2) Field to be entered on the second page of input screen. (3) Field to be entered on the third page of input screen. (4) Verification group interface to indicate that it is the field entered on the first page of input screen. (5) Verification group interface to indicate that it is the field entered on the second page of input screen. (6) Verification group interface to indicate that it is the field entered on the third page of input screen.Note
Verification group
When performing input validation for screen transition, only the fields on corresponding page need to be validated. In Bean Validation, verification rules can be grouped by setting the interface or class representing the verification group. In this implementation, input validation for each screen is performed by providing verification group for each screen.
- Controller
@Controller @RequestMapping("wizard") @SessionAttributes(types = { WizardForm.class, Entity.class }) // (7) public class WizardController { @Inject WizardService wizardService; @Inject Mapper beanMapper;
Sr. No. Description (7) In the above example, form object (WizardForm.class
) and entity (Entity.class
) object are stored in session.@ModelAttribute("wizardForm") // (8) public WizardForm setUpWizardForm() { return new WizardForm(); }
Sr. No. Description (8) In the above example, form object (WizardForm
) to be stored in session is generated. To prevent creation of unnecessary objects, “value” attribute of@ModelAttribute
annotation is specified.// (9) @RequestMapping(value = "create", method = RequestMethod.GET) public String initializeCreateWizardForm(SessionStatus sessionStatus) { sessionStatus.setComplete(); return "redirect:/wizard/create?form1"; } // (10) @RequestMapping(value = "create", params = "form1") public String createForm1() { return "wizard/form1"; }
Sr. No. Description (9) Handler method for initial display of input screen for registration.Objects for which operation is in process, may be stored in session; hence such objects are deleted by this handler method. (10) Handler method to display the first page of input screen for registration.// (11) @RequestMapping(value = "{id}/update", method = RequestMethod.GET) public String initializeUpdateWizardForm(@PathVariable("id") Integer id, RedirectAttributes redirectAttributes, SessionStatus sessionStatus) { sessionStatus.setComplete(); return "redirect:/wizard/{id}/update?form1"; } // (12) @RequestMapping(value = "{id}/update", params = "form1") public String updateForm1(@PathVariable("id") Integer id, WizardForm form, Model model) { Entity loadedEntity = wizardService.getEntity(id); beanMapper.map(loadedEntity, form); // (13) model.addAttribute(loadedEntity); // (14) return "wizard/form1"; }
Sr. No. Description (11) Handler method for initial display of input screen for update. (12) Handler method to display input screen for update (first page). (13) Set the fetched entity status in form object. In the above example, Bean mapper library called Dozer, is used. (14) Add the fetched entity toModel
object and store in session.In the above example, it is stored in session with attribute name"entity"
.// (15) @RequestMapping(value = "save", params = "form2", method = RequestMethod.POST) public String saveForm2(@Validated(Wizard1.class) WizardForm form, // (16) BindingResult result) { if (result.hasErrors()) { return saveRedoForm1(); } return "wizard/form2"; } // (17) @RequestMapping(value = "save", params = "form3", method = RequestMethod.POST) public String saveForm3(@Validated(Wizard2.class) WizardForm form, // (18) BindingResult result) { if (result.hasErrors()) { return saveRedoForm2(); } return "wizard/form3"; } // (19) @RequestMapping(value = "save", params = "confirm", method = RequestMethod.POST) public String saveConfirm(@Validated(Wizard3.class) WizardForm form, // (20) BindingResult result) { if (result.hasErrors()) { return saveRedoForm3(); } return "wizard/confirm"; }
Sr. No. Description (15) Handler method to display second page of input screen. (16) Specify the verification group (Wizard1.class
) on the first page of input screen, in “value” attribute of@Validated
annotation, so as to perform input validation of only the value entered in first page of input screen. (17) Handler method to display the third page of input screen. (18) Specify the verification group (Wizard2.class
) on the second page of input screen, in “value” attribute of@Validated
annotation, so as to perform input validation of only the value entered in second page of input screen. (19) Handler method to display confirmation screen. (20) Specify the verification group (Wizard3.class
) on the third page of input screen, in “value” attribute of@Validated
annotation, so as to perform input validation of only the value entered in third page of input screen.// (21) @RequestMapping(value = "save", method = RequestMethod.POST) public String save(@ModelAttribute @Validated({ Wizard1.class, Wizard2.class, Wizard3.class }) WizardForm form, // (22) BindingResult result, Entity entity, // (23) RedirectAttributes redirectAttributes) { if (result.hasErrors()) { throw new InvalidRequestException(result); // (24) } beanMapper.map(form, entity); entity = wizardService.saveEntity(entity); // (25) redirectAttributes.addFlashAttribute(entity); // (26) return "redirect:/wizard/save?complete"; } // (27) @RequestMapping(value = "save", params = "complete", method = RequestMethod.GET) public String saveComplete(SessionStatus sessionStatus) { sessionStatus.setComplete(); return "wizard/complete"; }
Sr. No. Description (21) Handler method to execute save process. (22) Specify the verification group interface (Wizard1.class
,Wizard2.class
,Wizard3.class
) of each input screen, in the “value” attribute of@Validated
annotation, to check all the values entered on input screen. (23) Fetch theEntity.class
object to be saved.For registration process, newly created object is fetched and for update process, the object stored in session by process (14), is fetched. (24) As error does not occur when screen transition is performed using the buttons provided by the application,InvalidRequestException
is thrown when an invalid operation is performed.Further, exception classInvalidRequestException
needs to be created separately, as it is not provided by common library. (25) Save theEntity.class
object reflecting the input value. (26)Entity.class
object saved by handler method of redirect destination is stored in Flash scope so that it can be referred. (27) Handler method to display completion screen.// (28) @RequestMapping(value = "save", params = "redoForm1") public String saveRedoForm1() { return "wizard/form1"; } // (29) @RequestMapping(value = "save", params = "redoForm2") public String saveRedoForm2() { return "wizard/form2"; } // (30) @RequestMapping(value = "save", params = "redoForm3") public String saveRedoForm3() { return "wizard/form3"; } }
Sr. No. Description (28) Handler method to re-display first page of input screen. (29) Handler method to re-display second page of input screen. (30) Handler method to re-display third page of input screen.
- Complete Controller source code
@Controller @RequestMapping("wizard") @SessionAttributes(types = { WizardForm.class, Entity.class }) // (7) public class WizardController { @Inject EntityService wizardService; @Inject Mapper beanMapper; @ModelAttribute("wizardForm") // (8) public WizardForm setUpWizardForm() { return new WizardForm(); } // (9) @RequestMapping(value = "create", method = RequestMethod.GET) public String initializeCreateWizardForm(SessionStatus sessionStatus) { sessionStatus.setComplete(); return "redirect:/wizard/create?form1"; } // (10) @RequestMapping(value = "create", params = "form1") public String createForm1() { return "wizard/form1"; } // (11) @RequestMapping(value = "{id}/update", method = RequestMethod.GET) public String initializeUpdateWizardForm(@PathVariable("id") Integer id, RedirectAttributes redirectAttributes, SessionStatus sessionStatus) { sessionStatus.setComplete(); return "redirect:/wizard/{id}/update?form1"; } // (12) @RequestMapping(value = "{id}/update", params = "form1") public String updateForm1(@PathVariable("id") Integer id, WizardForm form, Model model) { Entity loadedEntity = wizardService.getEntity(id); beanMapper.map(loadedEntity, form); // (13) model.addAttribute(loadedEntity); // (14) return "wizard/form1"; } // (15) @RequestMapping(value = "save", params = "form2", method = RequestMethod.POST) public String saveForm2(@Validated(Wizard1.class) WizardForm form, // (16) BindingResult result) { if (result.hasErrors()) { return saveRedoForm1(); } return "wizard/form2"; } // (17) @RequestMapping(value = "save", params = "form3", method = RequestMethod.POST) public String saveForm3(@Validated(Wizard2.class) WizardForm form, // (18) BindingResult result) { if (result.hasErrors()) { return saveRedoForm2(); } return "wizard/form3"; } // (19) @RequestMapping(value = "save", params = "confirm", method = RequestMethod.POST) public String saveConfirm(@Validated(Wizard3.class) WizardForm form, // (20) BindingResult result) { if (result.hasErrors()) { return saveRedoForm3(); } return "wizard/confirm"; } // (21) @RequestMapping(value = "save", method = RequestMethod.POST) public String save(@ModelAttribute @Validated({ Wizard1.class, Wizard2.class, Wizard3.class }) WizardForm form, // (22) BindingResult result, Entity entity, // (23) RedirectAttributes redirectAttributes) { if (result.hasErrors()) { throw new InvalidRequestException(result); // (24) } beanMapper.map(form, entity); entity = wizardService.saveEntity(entity); // (25) redirectAttributes.addFlashAttribute(entity); // (26) return "redirect:/wizard/save?complete"; } // (27) @RequestMapping(value = "save", params = "complete", method = RequestMethod.GET) public String saveComplete(SessionStatus sessionStatus) { sessionStatus.setComplete(); return "wizard/complete"; } // (28) @RequestMapping(value = "save", params = "redoForm1") public String saveRedoForm1() { return "wizard/form1"; } // (29) @RequestMapping(value = "save", params = "redoForm2") public String saveRedoForm2() { return "wizard/form2"; } // (30) @RequestMapping(value = "save", params = "redoForm3") public String saveRedoForm3() { return "wizard/form3"; } }
- First page of input screen (JSP)
<html> <head> <title>Wizard Form(1/3)</title> </head> <body> <h1>Wizard Form(1/3)</h1> <form:form action="${pageContext.request.contextPath}/wizard/save" modelAttribute="wizardForm"> <form:label path="field1">Field1</form:label> : <form:input path="field1" /> <form:errors path="field1" /> <div> <form:button name="form2">Next</form:button> </div> </form:form> </body> </html>
- Second page of input screen (JSP)
<html> <head> <title>Wizard Form(2/3)</title> </head> <body> <h1>Wizard Form(2/3)</h1> <%-- (31) --%> <form:form action="${pageContext.request.contextPath}/wizard/save" modelAttribute="wizardForm"> <form:label path="field2">Field2</form:label> : <form:input path="field2" /> <form:errors path="field2" /> <div> <form:button name="redoForm1">Back</form:button> <form:button name="form3">Next</form:button> </div> </form:form> </body> </html>
Sr. No. Description (31) There is no need to hide the input screen fields of first page since the form object is stored in session.
- Third page of input screen (JSP)
<html> <head> <title>Wizard Form(3/3)</title> </head> <body> <h1>Wizard Form(3/3)</h1> <%-- (32) --%> <form:form action="${pageContext.request.contextPath}/wizard/save" modelAttribute="wizardForm"> <form:label path="field3">Field3</form:label> : <form:input path="field3" /> <form:errors path="field3" /> <div> <form:button name="redoForm2">Back</form:button> <form:button name="confirm">Confirm</form:button> </div> </form:form> </body> </html>
Sr. No. Description (32) There is no need to hide the input screen fields of first and second page since form object is stored in session.
- Confirmation screen (JSP)
<html> <head> <title>Confirm</title> </head> <body> <h1>Confirm</h1> <%-- (33) --%> <form:form action="${pageContext.request.contextPath}/wizard/save" modelAttribute="wizardForm"> <div> Field1 : <c:out value="${wizardForm.field1}" /> </div> <div> Field2 : <c:out value="${wizardForm.field2}" /> </div> <div> Field3 : <c:out value="${wizardForm.field3}" /> </div> <div> <form:button name="redoForm3">Back</form:button> <form:button>OK</form:button> </div> </form:form> </body> </html>
Sr. No. Description (33) There is no need to hide input screen fields since form object is stored in session.
- Completion screen (JSP)
<html> <head> <title>Complete</title> </head> <body> <h1>Complete</h1> <div> <div> ID : ${f:h(entity.id)} </div> <div> Field1 : ${f:h(entity.field1)} </div> <div> Field2 : ${f:h(entity.field2)} </div> <div> Field3 : ${f:h(entity.field3)} </div> </div> <div> <a href="${pageContext.request.contextPath}/wizard/create"> Continue operation of Create </a> </div> <div> <a href="${pageContext.request.contextPath}/wizard/${entity.id}/update"> Continue operation of Update </a> </div> </body> </html>
- spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver"> <property name="exceptionCodeResolver" ref="exceptionCodeResolver" /> <!-- ... --> <property name="exceptionMappings"> <map> <!-- ... --> <entry key="InvalidRequestException" value="common/error/operationError" /> <!-- (34) --> </map> </property> <property name="statusCodes"> <map> <!-- ... --> <entry key="common/error/operationError" value="400" /> <!-- (35) --> </map> </property> <!-- ... --> </bean>
Sr. No. Description (34) Add exception handling definition ofInvalidRequestException
that notifies detection of invalid request when executing save process, inexceptionMappings
ofSystemExceptionResolver
, provided by common library.In the above example,/WEB-INF/views/common/error/operationError.jsp
is specified as the transition destination when exception occurs. (35) Specify the HTTP response code whenHttpSessionRequiredException
occurs instatusCodes
ofSystemExceptionResolver
.In the above example, Bad Request (400
) is specified as HTTP response code when exception occurs.
- applicationContext.xml
<bean id="exceptionCodeResolver" class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver"> <!-- Setting and Customization by project. --> <property name="exceptionMappings"> <map> <!-- ... --> <entry key="InvalidRequestException" value="w.xx.0004" /> <!-- (36) --> </map> </property> <property name="defaultExceptionCode" value="e.xx.0001" /> <!-- (37) --> </bean>
Sr. No. Description (36) Add exception handling definition ofInvalidRequestException
toexceptionMappings
ofSimpleMappingExceptionCodeResolver
provided by common library.In the above example,"w.xx.0004"
is specified as the exception code when an exception occurs.When this setting is not added, default exception code is output to log. (37) Default exception code in case of an exception.
4.3.4.2. Example of screen transition across multiple Controllers using session-scoped bean.¶
Implementation using session-scoped bean is explained with example of process of performing screen transitions across multiple Controllers.
Process specifications are as follows:
- Provide a process to add products to cart.
- Provide a process to change the quantity of products added to the cart.
- Provide a process to order the products stored in cart.
- Above 3 processes are provided as independent functions and should be set in separate Controllers (ItemController, CartController, OrderController).
- Store the cart in session as it is shared by the above 3 processes.
- Display the cart screen when products are added to cart.
Screen transition should be as follows:
Implementation is as follows:
- JavaBean to be defined as session-scoped bean.
@Component @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public class SessionCart implements Serializable { private static final long serialVersionUID = 1L; private Cart cart; // (1) public Cart getCart() { if (cart == null) { cart = new Cart(); } return cart; } public void setCart(Cart cart) { this.cart = cart; } public void clearCart() { // (2) cart.clearCart(); } }
Sr. No. Description (1) Wrap the Entity (Domain object) called,Cart
. (2) Make cart empty by removing all product objects fromcart
which are added in the cart.
- ItemController
@Controller @RequestMapping("item") public class ItemController { @Inject SessionCart sessionCart; @Inject CartService cartService; @Inject Mapper beanMapper; @ModelAttribute public ItemForm setUpItemForm() { return new ItemForm(); } // (3) @RequestMapping public String view(Model model) { return "item/item"; } // (4) @RequestMapping(value = "add") public String addCart(@Validated ItemForm form, BindingResult result) { if (result.hasErrors()) { return "item/item"; } CartItem cartItem = beanMapper.map(form, CartItem.class); Cart cart = cartService.addCartItem(sessionCart.getCart(), // (5) cartItem); sessionCart.setCart(cart); // (6) return "redirect:/cart"; // (7) } }
Sr. No. Description (3) Handler method to display product screen. (4) Handler method to add specified products to cart. (5) PassCart
object stored in the session in Service method. (6) ReflectCart
object returned by Service method to session-scoped bean.By reflecting in session-scoped Bean,Cart
object is stored in the session. (7) Redirect to the request to display cart screen after adding products to cart.When transiting to a separate Controller screen, instead of calling View (JSP) directly, it is recommended to redirect to the request for displaying the screen.
- CartController
@Controller @RequestMapping("cart") public class CartController { @Inject SessionCart sessionCart; @Inject CartService cartService; @Inject Mapper beanMapper; @ModelAttribute public CartForm setUpCartForm() { return new CartForm(); } // (8) @RequestMapping public String cart(CartForm form) { beanMapper.map(sessionCart.getCart(), form); return "cart/cart"; } // (9) @RequestMapping(params = "edit", method = RequestMethod.POST) public String edit(@Validated CartForm form, BindingResult result, Model model) { if (result.hasErrors()) { return "cart/cart"; } Cart cart = sessionCart.getCart(); Iterator<CartItemForm> itemForm = form.getCartItems().iterator(); for (CartItem item : cart.getCartItems()) { beanMapper.map(itemForm.next(), item); } cart = cartService.saveCart(cart); sessionCart.setCart(cart); // (10) return "redirect:/cart"; // (11) } }
Sr. No. Description (8) Handler method to display cart screen (quantity change screen). (9) Handler method to change quantity. (10) ReflectCart
object returned by Service method, to session-scoped bean.By reflecting in session-scoped bean, it is reflected in the session. (11) Redirect to the request to display cart screen (quantity change screen) after changing quantity.In case of Update process, instead of calling View (JSP) directly, it is recommended to redirect to the request for displaying the screen.
- OrderController
@Controller @RequestMapping("order") @SessionAttributes("scopedTarget.sessionCart") public class OrderController { @Inject SessionCart sessionCart; @ModelAttribute public OrderForm setUpOrderForm() { return new OrderForm(); } // (12) @RequestMapping public String view() { return "order/order"; } // (13) @RequestMapping(method = RequestMethod.POST) public String order() { // ... return "redirect:/order?complete"; } // (14) @RequestMapping(params = "complete", method = RequestMethod.GET) public String complete(Model model, SessionStatus sessionStatus) { sessionCart.clearCart(); return "order/complete"; } }
Sr. No. Description (12) Handler method to display Order screen. (13) Handler method to place an Order. (14) Handler method to display Order Completion screen.
- Product screen (JSP)
<html> <head> <title>Item</title> </head> <body> <h1>Item</h1> <form:form action="${pageContext.request.contextPath}/item/add" modelAttribute="itemForm"> <form:label path="itemCode">Item Code</form:label> : <form:input path="itemCode" /> <form:errors path="quantity" /> <br> <form:label path="quantity">Quantity</form:label> : <form:input path="quantity" /> <form:errors path="quantity" /> <div> <%-- (15) --%> <form:button>Add</form:button> </div> </form:form> <div> <a href="${pageContext.request.contextPath}/cart">Go to Cart</a> </div> </body> </html>
Sr. No. Description (15) Button to add a product.
- Cart screen (JSP)
<html> <head> <title>Cart</title> </head> <body> <%-- (16) --%> <spring:eval var="cart" experssion="@sessionCart.cart" /> <h1>Cart</h1> <c:choose> <c:when test="${ empty cart.cartItems }"> <div>Cart is empty.</div> </c:when> <c:otherwise> CART ID : ${f:h(cart.id)} <form:form modelAttribute="cartForm"> <table border="1"> <thead> <tr> <th>ID</th> <th>ITEM CODE</th> <th>QUANTITY</th> </tr> </thead> <tbody> <c:forEach var="item" items="${cart.cartItems}" varStatus="rowStatus"> <tr> <td>${f:h(item.id)}</td> <td>${f:h(item.itemCode)}</td> <td> <form:input path="cartItems[${rowStatus.index}].quantity" /> <form:errors path="cartItems[${rowStatus.index}].quantity" /> </td> </tr> </c:forEach> </tbody> </table> <%-- (17) --%> <form:button name="edit">Save</form:button> </form:form> </c:otherwise> </c:choose> <c:if test="${ not empty cart.cartItems }"> <div> <%-- (18) --%> <a href="${pageContext.request.contextPath}/order">Go to Order</a> </div> </c:if> <div> <a href="${pageContext.request.contextPath}/item">Back to Item</a> </div> </body> </html>
Sr. No. Description (16) Refer session-scoped Bean using SpEL formula. (17) Button to update quantity. (18) Link to display Order screen.
- Order screen (JSP)
<html> <head> <title>Order</title> </head> <body> <spring:eval var="cart" experssion="@sessionCart.cart" /> <h1>Order</h1> <table border="1"> <thead> <tr> <th>ID</th> <th>ITEM CODE</th> <th>QUANTITY</th> </tr> </thead> <tbody> <c:forEach var="item" items="${cart.cartItems}" varStatus="rowStatus"> <tr> <td>${f:h(item.id)}</td> <td>${f:h(item.itemCode)}</td> <td>${f:h(item.quantity)}</td> </tr> </c:forEach> </tbody> </table> <form:form modelAttribute="orderForm"> <%-- (19) --%> <form:button>Order</form:button> </form:form> <div> <a href="${pageContext.request.contextPath}/cart">Back to Cart</a> </div> <div> <a href="${pageContext.request.contextPath}/item">Back to Item</a> </div> </body> </html>
Sr. No. Description (19) Button to place an order.
- Order Completion screen (JSP)
<html> <head> <title>Order Complete</title> </head> <body> <h1>Order Complete</h1> ORDER ID : ${f:h(order.id)} <table border="1"> <thead> <tr> <th>ID</th> <th>ITEM CODE</th> <th>QUANTITY</th> </tr> </thead> <tbody> <c:forEach var="item" items="${order.orderItems}" varStatus="rowStatus"> <tr> <td>${f:h(item.id)}</td> <td>${f:h(item.itemCode)}</td> <td>${f:h(item.quantity)}</td> </tr> </c:forEach> </tbody> </table> <br> <div> <a href="${pageContext.request.contextPath}/item">Back to Item</a> </div> </body> </html>