Session Scoped Beans
Session Scoped Beans - Initial Design
Problem
uPortal 3 has a set of objects whose instances are scoped to the HttpSessions of particular portal users. The desire is to have these objects scoped transparently to the session so they may be used like any other spring bean in the code. That is, they will appear to be singleton beans from the perspective of code dependent upon the bean, but the object instance handling any particular call will be the appropriate instance for the portal user being served.
Solution
The solution to this problem has two parts, a servlet Filter to put the HttpSession for the request in a ThreadLocal and an implementation of the Spring TargetSource interface to access the HttpSession from the ThreadLocal and store beans in.
SessionLocalBindingFilter
The SessionLocalBindingFilter places the HttpSession for the current request in a ThreadLocal so all code running in that Thread may access it. The filter provide static accessor methods to the HttpSession to hide the actual ThreadLocal from client code. It removes the HttpSession from the ThreadLocal in a finally block, ensuring the HttpSession won't accidentally remain associated with the thread if it is being used in a thread pool.
SessionLocalBindingFilter.java
SessionLocalTargetSource
The SessionLocalTargetSource manages creation of prototype beans and scoping them to the current session. To scope your beans to the HttpSession you need to return a Proxy for each. This TargetSource implementation is provided to the
from Spring. For each method call made on the Proxy it consults the TargetSource implementation. When using the SessionLocalTargetSource it will first look in the HttpSession to see if the bean already exists, if so it is simply returned. If the bean does not yet exist it is created from the spring context. If there is no HttpSession for some reason (the method call was not triggered as some part of a request to the server) the bean is created from the context and stored in a local variable which is used whenever no HttpSession is available.
SessionBoundBeanDestructionListener
The SessionBoundBeanDestructionListener provides cleanup to HttpSession scoped beans. This listener implements the servlet HttpSessionListener class and is registered in the web.xml of the application. When the HttpSession is destroyed the listener calls destroy on every bean in the session that implements DisposableBean.
SessionBoundBeanDestructionListener.java
Spring Configuration
The following is a snippet from one of the spring configuration files. This exposes the transient portlet application entity registry as a session scoped bean. Note that the list of proxied interfaces must be provided. There is likely an auto-proxy way to do this but I'm not sure for the purpose of this example.
Note the nested bean declaration of the SessionLocalTargetSource and that the transientPortletApplicationEntityRegistryTarget is declared as a prototype (singleton="false").
<bean id="transientPortletApplicationEntityRegistry" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- Toggle CGLIB proxying --> <property name="proxyTargetClass"> <value>true</value> </property> <property name="proxyInterfaces"> <list> <value>org.jasig.portal.portlet.registry.transitory.ITransientPortletApplicationEntityRegistry</value> </list> </property> <property name="targetSource"> <bean class="org.jasig.portal.spring.session.SessionLocalTargetSource"> <property name="targetBeanName"> <value>transientPortletApplicationEntityRegistryTarget</value> </property> </bean> </property> </bean> <!-- ITransientPortletApplicationEntityRegistry implementation --> <bean id="transientPortletApplicationEntityRegistryTarget" class="org.jasig.portal.portlet.registry.transitory.TransientPortletApplicationEntityRegistryImpl" singleton="false"> <property name="portletEntityControlFactory"> <ref local="transientPortletEntityControlFactory"/> </property> <property name="portletApplicationEntityRegistryFactory"> <ref bean="../portletApplicationEntityRegistryFactory"/> </property> <property name="userController"> <ref bean="/uP2Context/userController"/> </property> </bean>
web.xml Configuration
The following is a snippet from a web.xml configured to use the filter and listener described above. The filter should be the first one listed in the file to ensure the session exists for all code in the application.
<filter> <filter-name>Session Local Binding Filter</filter-name> <filter-class>org.jasig.portal.utils.SessionLocalBindingFilter</filter-class> </filter> <filter-mapping> <filter-name>Session Local Binding Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.jasig.portal.spring.session.SessionBoundBeanDestructionListener</listener-class> </listener>
Notes
The above classes will work well as long as code using the session scoped beans doesn't hold onto object references retrieved from the beans. These references are direct (no Proxy calls when using them) and if held into another request will no longer be from the correct HttpSession. We do have a solution for this which I will be documenting in the future.