Spring MVC自定义作用域Bean

4

我希望创建自己的自定义作用域 Bean,它将使用 HTTP 会话(一种 Flash 范围)。

根据 Spring 手册,我需要实现 org.springframework.beans.factory.config.Scope 接口。

public class CustomScope implements Scope {

    @Override
    public Object get(String arg0, ObjectFactory<?> arg1) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public String getConversationId() {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public void registerDestructionCallback(String arg0, Runnable arg1) {
        // TODO Auto-generated method stub
    }
    @Override
    public Object remove(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public Object resolveContextualObject(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }
}

我的问题是如何在这个bean中获取HTTP会话?我知道如果我在ServletContext范围内创建bean,那么我将实现ServletContextAware接口。
请帮忙 :)
3个回答

9

我希望这对未来的某个人有用,因此我想分享它。

我对此进行了一些研究,并发现不幸的是,无法为Spring MVC获取HTTP会话。

我的目的是使用PRG模式为我的Spring MVC控制器实现Flash Scope。

在Spring论坛中进行更多研究后,我发现可以使用HandlerInterceptor来完成它。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import java.util.*;
import java.util.Map.Entry;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class FlashScopeInterceptor implements HandlerInterceptor {

    public static final String DEFAULT_ATTRIBUTE_NAME = "flashScope";
    public static final String DEFAULT_SESSION_ATTRIBUTE_NAME = FlashScopeInterceptor.class.getName();
    public static final int DEFAULT_RETENTION_COUNT = 2;

    private String sessionAttributeName = DEFAULT_SESSION_ATTRIBUTE_NAME;
    private String attributeName = DEFAULT_ATTRIBUTE_NAME;
    private int retentionCount = DEFAULT_RETENTION_COUNT;

    /**
     * Unbinds current flashScope from session. Rolls request's flashScope to
     * the next scope. Binds request's flashScope, if not empty, to the session.
     * 
     */

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        if (request.getSession( false ) != null)
        {
            request.getSession().removeAttribute( this.sessionAttributeName );
        }
        Object requestAttribute = request.getAttribute( this.attributeName );
        if (requestAttribute instanceof MultiScopeModelMap)
        {
            MultiScopeModelMap attributes = (MultiScopeModelMap) requestAttribute;
            if (!attributes.isEmpty())
            {
                attributes.next();
                if (!attributes.isEmpty())
                {
                    request.getSession( true ).setAttribute( this.sessionAttributeName, attributes );
                }
            }
        }
    }

    /**
     * merge modelAndView.model['flashScope'] to current flashScope
     */
    @Override
    public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        if (modelAndView != null)
        {
            Map<String, Object> modelFlashScopeMap = null;
            for (Iterator<Entry<String, Object>> iterator = ((Map<String, Object>) modelAndView.getModel()).entrySet()
                    .iterator(); iterator.hasNext();)
            {
                Entry<String, Object> entry = iterator.next();
                if (this.attributeName.equals( entry.getKey() ) && entry.getValue() instanceof Map)
                {
                    if (modelFlashScopeMap == null)
                    {
                        modelFlashScopeMap = (Map) entry.getValue();
                    }
                    else
                    {
                        modelFlashScopeMap.putAll( (Map) entry.getValue() );
                    }
                    iterator.remove();
                }
                else if (entry.getKey().startsWith( this.attributeName + "." ))
                {
                    String key = entry.getKey().substring( this.attributeName.length() + 1 );
                    if (modelFlashScopeMap == null)
                    {
                        modelFlashScopeMap = new HashMap<String, Object>();
                    }
                    modelFlashScopeMap.put( key, entry.getValue() );
                    iterator.remove();
                }
            }
            if (modelFlashScopeMap != null)
            {
                MultiScopeModelMap flashScopeMap;
                if (request.getAttribute( this.attributeName ) instanceof MultiScopeModelMap)
                {
                    flashScopeMap = (MultiScopeModelMap) request.getAttribute( this.attributeName );
                }
                else
                {
                    flashScopeMap = new MultiScopeModelMap( this.retentionCount );
                }
                flashScopeMap.putAll( modelFlashScopeMap );
                request.setAttribute( this.attributeName, flashScopeMap );
            }
        }
    }

    /**
     * binds session flashScope to current session, if not empty. Otherwise cleans up empty
     * flashScope
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession session = request.getSession( false );
        if (session != null)
        {
            Object sessionAttribute = session.getAttribute( this.sessionAttributeName );
            if (sessionAttribute instanceof MultiScopeModelMap)
            {
                MultiScopeModelMap flashScope = (MultiScopeModelMap) sessionAttribute;
                if (flashScope.isEmpty())
                {
                    session.removeAttribute( this.sessionAttributeName );
                }
                else
                {
                    request.setAttribute( this.attributeName, flashScope );
                }
            }
        }
        return true;
    }
}

现在是MultiScopeModelMap.java。
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.map.CompositeMap;
import org.apache.commons.collections.map.CompositeMap.MapMutator;

public class MultiScopeModelMap extends CompositeMap implements Serializable, MapMutator
{
    public MultiScopeModelMap(int num)
    {
        super();
        setMutator( this );
        for(int i = 0; i < num; ++i)
        {
            addComposited( new HashMap() );
        }
    }

    /** Shadows composite map. */
    private final LinkedList<Map> maps = new LinkedList<Map>();

    @Override
    public synchronized void addComposited( Map map ) throws IllegalArgumentException
    {
        super.addComposited( map );
        this.maps.addLast( map );
    }



    @Override
    public synchronized Map removeComposited( Map map )
    {
        Map removed = super.removeComposited( map );
        this.maps.remove( map );
        return removed;
    }



    /** 
     * Starts a new scope. 
     * All items added in the session before the previous session are removed.
     * All items added in the previous scope are still retrievable and removable.
     */ 
    public void next()
    {
        removeComposited( this.maps.getFirst() );
        addComposited( new HashMap() );
    }

    public Object put( CompositeMap map, Map[] composited, Object key, Object value )
    {
        if(composited.length < 1)
        {
            throw new UnsupportedOperationException("No composites to add elements to");
        }
        Object result = map.get( key );
        if(result != null)
        {
            map.remove( key );
        }
        composited[composited.length-1].put( key, value );
        return result;
    }

    public void putAll( CompositeMap map, Map[] composited, Map mapToAdd )
    {
        for(Entry entry: (Set<Entry>)mapToAdd.entrySet())
        {
            put(map, composited, entry.getKey(), entry.getValue());
        }
    }

    public void resolveCollision( CompositeMap composite, Map existing, Map added, Collection intersect )
    {
        existing.keySet().removeAll( intersect );       
    }

    @Override
    public String toString()
    {
        return new HashMap(this).toString();
    }


}

用法:

@RequestMapping(value="/login.do", method=RequestMethod.POST)
    public ModelAndView login(@Valid User user){
        ModelAndView mv = new ModelAndView("redirect:result.html");
        if (authService.authenticate(user.getUserName(), user.getPassword()))
            mv.addObject("flashScope.message", "Success");
        //else
            mv.addObject("flashScope.message", "Login Failed");
        return mv;
    }

@RequestMapping(value ="/result.html", method=RequestMethod.GET)
    public ModelAndView result(){
        ModelAndView mv = new ModelAndView("login/loginAction");
        return mv;
    }

在JSP中,使用方法非常简单:
${flashScope.message}

此外,您需要将FlashScopeInterceptor类配置为拦截器。
<bean id="flashScopeInterceptor" class="x.y.z.FlashScopeInterceptor" />
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
  <property name="interceptors">
    <list><ref bean="flashScopeInterceptor"/></list>
  </property>
</bean> 

1
如果您使用的是Spring 3.1或更高版本,其中引入了RedirectAttributes对象,则@nMoncho不再相关。 - danny.lesnik
是的,我知道,但应用程序已经在生产中了(而且我不知道更改Spring版本的风险有多大,可能不会很大)。 - nMoncho
如果您要使用RedirectAttributes,那么您的请求映射方法应该仅返回String而不是ModelAndView,如果您确实返回了ModelAndView,则必须更改MVC逻辑,这是有风险的。 - danny.lesnik
我已经尝试了这种方法,并一直成功到jsp渲染。我知道这很老,但我在这里可能做错了什么:http://stackoverflow.com/questions/19481378/need-help-debugging-a-jsp-model-rendering-issue - sydneyos

2

我同意(+1)。生成自定义作用域是一个非常高级的主题,不应该没有理由就去做。 - Sean Patrick Floyd
@Ralph,我明白这是提示 :),但是@Sean刚刚说这是高级话题,所以也许有更简单的方法来在Spring MVC中实现Flash范围 :) - danny.lesnik
1
@danny:也许如果你在问题中解释一下"flash scope"是什么。我知道它是来自JSF的东西,但是我不知道它是什么意思。 - skaffman
@danny.lesnik:哦,我找到了我的错误,我混淆了闪存范围和对话范围的概念,这是我的错。 - Ralph
请看一下我的回答,我认为它完成了这个故事。 - danny.lesnik
显示剩余3条评论

0

在Spring MVC中,您可以在作用域类方法中使用以下代码访问会话属性(适用于3.2):

RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
attributes.getAttribute("some key", NativeWebRequest.SCOPE_SESSION);
attributes.setAttribute("some key", YouObject, NativeWebRequest.SCOPE_SESSION);

RequestAttributes 的实现(ServletRequestAttributes)在内部将调用当前会话对象上的 set/getAttribute() 方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接