动机
Spring现在不支持session会话范围的bean和component.你可以在singleton或者prototype生命周期之间选择,但是你不能让你的bean绑定到web应用程序的session生命周期上去.在Spring 2.0版本中已经计划集成这样一个特性.我们试着指出一些可能的解决方案来工作在基于WebWork的应用程序中.首先我们会考查在Spring社区中发现的通用解决方案,来处理HTTPSession和诸如此类的事情.然后我们会讨论在XWork/WebWork里发现的特殊情况和需求,以及这些会如何影响可能的解决方案.我们会展示一些XWork/WebWork对给定问题的特定解决方案.
|
Spring 2.0(前一个版本是1.3)的第一个里程碑版本将会在2005十二月的第二个星期发布.已经证实它会包含一个Session会话范围的组件,使用了Proxy(CGLIB或者JDK)方法.
|
对于web应用程序的通用解决方案
Spring 2.0 的方法
Interface21(译注:Spring作者的公司)在Spring 2.0中添加了session(和request)会话范围的bean的支持.这个方法通过org.springframework.aop.target.scope.ScopedProxyFactoryBean 创建了一个session会话范围的bean的CGLIB或者JDK动态代理,并设置scopeMap为org.springframework.web.context.scope.SessionScopeMap.
因为jar是向后兼容的,因为只需要简单地构建Spring并替换WebWork带来的jar.(当你读到这里的时候Spring 2.0 M1可能已经发布了).
有两种方法来设置这个,也就是使用或者不使用简化的XML.第一个方法是使用传统的bean定义,这对于理解内部发生了什么是很有用的.
一个shopping cart例子里经过改良的applicationContext.xml,使用了传统的XML DTD的例子显示在下面.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="shoppingCart" class="org.springframework.aop.target.scope.ScopedProxyFactoryBean"
singleton="false">
<property name="scopeKey" value="shoppingCart"/>
<property name="targetBeanName" value="__shoppingCart"/>
<property name="scopeMap">
<bean class="org.springframework.web.context.scope.SessionScopeMap"/>
</property>
</bean>
<bean id="__shoppingCart" class="com.opensymphony.webwork.example.ajax.cart.DefaultCart"
singleton="false"/>
</beans>
一个使用简化XML方式的shopping cart例子的改良applicationContext.xml显示在下面.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="catalog" class="com.opensymphony.webwork.example.ajax.catalog.TestCatalog"
singleton="true"/>
<bean id="shoppingCart" class="com.opensymphony.webwork.example.ajax.cart.DefaultCart"
singleton="true">
<aop:scope type="session"/>
</bean>
</beans>
你还要修改web.xml来包含下面的filter.
<filter>
<filter-name>springFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
使用一个ServletFilter来定制 TargetSource
一个很"clean"的一般是为web应用程序提出的解决方案可以在 JA-SIG 发现. 这个解决方案有很好的文档,可以在 这里 发现. 在后面你可以找到一个采用它的方法.
XWork/WebWork 特殊的解决方案
前言
WebWork是基于XWork的,而XWork不是绑定到web层的.因此当处理session会话范围的对象时,WW用户可能会使用XWork的session抽象特性来保持他们的应用程序独立于web环境.这也就是我们为什么要讨论一些下面列出的XW/WW特定的解决方案.
定制 TargetSource, WebWork 的方法
这是一个上面指出的TargetSource解决方案的一个改良版本,它和已有的WebWork session相结合,不需要一个额外的filter或者listener.使用方法是几乎相似的,为你的对象创建一个接口,并确保你使用的总是接口而不是具体的实现,否则自动装配会失败.你可以研究 WebWorkTargetSource Shopping Cart Example 以找到更多的关于这个如何工作的信息.
package org.tuxbot.webwork.spring;
/* Portions Copyright 2005 The JA-SIG Collaborative. All rights reserved.
* See license distributed with this file and
* available online at http: */
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.target.AbstractPrototypeBasedTargetSource;
import org.springframework.beans.factory.DisposableBean;
import com.opensymphony.xwork.ActionContext;
import java.util.Map;
/**
* This target source is to be used in collaberation with WebWork.
* The target source binds the target bean to the Session retrieved from
* WebWork. By default the bean is bound to the session
* using the name of the target bean as part of the key. This can be overridden by setting
* the <code>sessionKey</code> property to a not null value.
*
* @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
* @author Eric Molitor <a href="mailto:eric@tuxbot.com">eric@tuxbot.com</a>
* @version 1.0
*/
public class WebWorkTargetSource extends AbstractPrototypeBasedTargetSource implements DisposableBean {
private final static Log LOG = LogFactory.getLog(WebWorkTargetSource.class);
private transient Object noSessionInstance = null;
private String sessionKey = null;
private String compiledSessionKey = null;
public WebWorkTargetSource() {
this.updateBeanKey();
}
/**
* @return Returns the sessionKey.
*/
public String getSessionKey() {
return this.sessionKey;
}
/**
* @param sessionKey The sessionKey to set.
*/
public void setSessionKey(String sessionKey) {
this.sessionKey = sessionKey;
this.updateBeanKey();
}
/**
* @see org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource#setTargetBeanName(java.lang.String)
*/
public void setTargetBeanName(String targetBeanName) {
super.setTargetBeanName(targetBeanName);
this.updateBeanKey();
}
/**
* @see org.springframework.aop.TargetSource#getTarget()
*/
public Object getTarget() throws Exception {
final Map session = ActionContext.getContext().getSession();
if (session == null) {
LOG.warn("No Session found for thread '" + Thread.currentThread().getName() + "'");
if (this.noSessionInstance == null) {
this.noSessionInstance = this.newPrototypeInstance();
if (LOG.isDebugEnabled()) {
LOG.debug("Created instance of '" + this.getTargetBeanName() + "', not bound to any webWorkSession.");
}
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("Found instance of '" + this.getTargetBeanName() + "', not bound to any webWorkSession.");
}
}
return this.noSessionInstance;
}
else {
String beanKey = this.compiledSessionKey;
Object instance = session.get(beanKey);
if (instance == null) {
instance = this.newPrototypeInstance();
session.put(beanKey, instance);
if (LOG.isDebugEnabled()) {
LOG.debug("Created instance of '" + this.getTargetBeanName() + "', bound to webWorkSession for '"
+ Thread.currentThread().getName() + "' using key '" + beanKey + "'.");
}
}
else if (LOG.isDebugEnabled()) {
LOG.debug("Found instance of '" + this.getTargetBeanName() + "', bound to webWorkSession for '"
+ Thread.currentThread().getName() + "' using key '" + beanKey + "'.");
}
return instance;
}
}
/**
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
public void destroy() throws Exception {
if (this.noSessionInstance != null && this.noSessionInstance instanceof DisposableBean) {
if (LOG.isDebugEnabled()) {
LOG.debug("Destroying sessionless bean instance '" + this.noSessionInstance + "'");
}
((DisposableBean)this.noSessionInstance).destroy();
}
}
/**
* Generates the key to store the bean in the session with.
*/
private void updateBeanKey() {
if (this.sessionKey == null) {
final StringBuffer buff = new StringBuffer();
buff.append(this.getClass().getName());
buff.append("_");
buff.append(this.getTargetBeanName());
this.compiledSessionKey = buff.toString();
}
else {
this.compiledSessionKey = this.sessionKey;
}
}
}
自定义 ApplicationContext 实现
TODO: Document
自定义 WW/XW ObjectFactory
TODO: Document
Session 支持的 Bean Factory
这个想法就是简单地创建一个 获取/创建(session) 的bean factory:
package net.itneering.core.spring.session;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import com.opensymphony.xwork.ActionContext;
import java.util.Map;
import java.io.Serializable;
/**
* SessionBackedBeanFactory tries to lookup beans by name in XWork session. If not found,
* it tries to instantiate new bean and attaches it to said session.
*
* @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
*/
public class SessionBackedBeanFactory implements Serializable, BeanFactoryAware {
BeanFactory beanFactory = null;
/**
* Find a component by name in session scoped storage implementation. If not found, try to instantiate new one by
* {@link org.springframework.beans.factory.BeanFactory#getBean(String)}. Then found component will be attached
* to session store implementation.
*
* @param componentName
* @return The requested component, if found.
*/
public Object getSessionComponent( String componentName ) {
Object result = getSession().get(componentName);
if ( result == null ) {
result = beanFactory.getBean(componentName);
storeComponent(componentName, result);
}
return result;
}
public void storeComponent(String componentName, Object component ) {
getSession().put(componentName, component);
}
/**
* Actual implementation of the session scoped storage Map.
* Lookup {@link com.opensymphony.xwork.ActionContext#getSession()}.
*
* @return The Map for keeping session objects.
*/
public Map getSession() {
return ActionContext.getContext().getSession();
}
/**
* Callback that supplies the owning factory to a bean instance.
* <p>Invoked after population of normal bean properties but before an init
* callback like InitializingBean's afterPropertiesSet or a custom init-method.
*
* @param beanFactory owning BeanFactory (may not be null).
* The bean can immediately call methods on the factory.
*
* @throws org.springframework.beans.BeansException
* in case of initialization errors
* @see org.springframework.beans.factory.BeanInitializationException
*/
public void setBeanFactory( BeanFactory beanFactory ) throws BeansException {
this.beanFactory = beanFactory;
}
}
示例的applicationContext 设置(注意session会话范围的bean设置为singleton="false"):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="sessionBeanProxy" class="net.itneering.core.spring.session.SessionBackedBeanFactory" singleton="true"/>
<bean id="securityContextComponent" class="net.itneering.security.component.DefaultSecurityContextComponent" singleton="false" />
</beans>
使用它的例子action:
package net.itneering.xwork.action;
import com.opensymphony.xwork.ActionSupport;
import net.itneering.core.spring.session.SessionBackedBeanFactory;
import net.itneering.security.component.DefaultSecurityContextComponent;
/**
* Simple sessionBeanProxy aware action.
*
* @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
* @version $Revision: 1.1 $
*/
public class SecurityAwareAction extends ActionSupport implements PrincipalAware {
private static final Logger log = Logger.getLogger(SecurityAwareAction.class);
protected SessionBackedBeanFactory sessionBeanProxy;
/**
* For Spring wiring usage.
*
* @param sessionBeanProxy The sessionBeanProxyto use.
*/
public void setSessionBeanProxy( SessionBackedBeanFactory sessionBeanProxy ) {
this.sessionBeanProxy = sessionBeanProxy;
}
/**
* Getter for actions security context.
*
* @return The securityContextComponent set by IoC.
*/
public SecurityContextComponent getSecurityContextComponent() {
return sessionBeanProxy!= null ? sessionBeanProxy.getSessionComponent("securityContextComponent") : null;
}
/**
* Get the current User Principal for this session.
*
* @return The User Principal.
*/
public UserEntity getPrincipal() {
try {
return getSecurityContextComponent().getPrincipal();
} catch ( NullPointerException e ) {
return null;
}
}
...
}
对于使用广泛的session会话范围组件,你可能为了方便而集成SessionBackedBeanFactory:
package net.itneering.security.component;
import net.itneering.core.spring.session.SessionBackedBeanFactory;
/**
* SecurityAwareSessionBeanProxy.
*
* @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
*/
public class SecurityAwareSessionBeanProxy extends SessionBackedBeanFactory {
String securityContextComponentName = "securityContextComponent";
/**
* Make component name configurable by spring setup
*/
public void setSecurityContextComponentName( String securityContextComponentName ) {
this.securityContextComponentName = securityContextComponentName;
}
public SecurityContextComponent getSecurityContextComponent() {
return (SecurityContextComponent) getSessionComponent(securityContextComponentName);
}
}
同样示例的 applicationContext 配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="mySecurityContextComponent" class="net.itneering.security.component.DefaultSecurityContextComponent" singleton="false" />
<bean id="sessionBeanProxy" class="net.itneering.security.component.SecurityAwareSessionBeanProxy" singleton="true">
<property name="securityContextComponentName" value="mySecurityContextComponent" />
</bean>
</beans>
使用的action示例:
package net.itneering.xwork.action;
import com.opensymphony.xwork.ActionSupport;
import net.itneering.security.component.SecurityAwareSessionBeanProxy;
import net.itneering.security.component.DefaultSecurityContextComponent;
/**
* Simple sessionBeanProxy aware action.
*
* @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
* @version $Revision: 1.1 $
*/
public class SecurityAwareAction extends ActionSupport implements PrincipalAware {
private static final Logger log = Logger.getLogger(SecurityAwareAction.class);
protected SecurityAwareSessionBeanProxy sessionBeanProxy;
/**
* For Spring wiring usage.
*
* @param sessionBeanProxy The sessionBeanProxy to use.
*/
public void setSessionBeanProxy( SecurityAwareSessionBeanProxy sessionBeanProxy ) {
this.sessionBeanProxy = sessionBeanProxy;
}
/**
* Get the current User Principal for this session.
*
* @return The User Principal.
*/
public UserEntity getPrincipal() {
try {
return sessionBeanProxy.getSecurityContextComponent().getPrincipal();
} catch ( NullPointerException e ) {
return null;
}
}
...
}
就像前面所说,这个解决方案是非常简单的.你不需要绑定到web层,配置也实在简单,也没有必要在applicationContext.xml里面定义proxy.
主要的缺点就是你不能直接装配session会话范围的bean到你的action,你必须间接使用session支持的bean factory.而且,当你处理XWork session抽象层的时候,你经常必须关心来设置一个action context.
自动代理session支持的组件Factory
有人有这样一个实现吗? (Eric Molitor)
这个意图有一点不同,因此我试图澄清标题.好主意,虽然... (Rene Gielen).
译注:下面这段就不翻译了
The theory here is to create a custom Pointcut class that utilizes the ComponentConfiguration retrieved from the DefaultComponentManager (which loads the Component list from components.xml). The getClassFilter() matches anything that implements one of the Components in the ComponentConfiguration. The Pointcut is then registered as an advisor for all beans (AutoProxy via Springs DefaultAdvisorAutoProxyCreator). The Advice implementation looks at which Component is implmented and fetches the apporiate value out of the Session and calls the Components setter method.
TODO: Document, create example