当会话在 websphere 中失效时,Spring 视图范围不会被破坏

Posted

技术标签:

【中文标题】当会话在 websphere 中失效时,Spring 视图范围不会被破坏【英文标题】:Spring view scope is not destroyed when session gets invalidated in websphere 【发布时间】:2021-08-25 08:16:01 【问题描述】:

我正在使用 Springboot 1.5.9.RELEASEJSF 2.2.20 (glassfish)Primefaces 8WebSphere 上运行8.5.5.13 ,并且我已经根据以下链接定义了自定义视图范围:

https://github.com/jneat/spring-jsf

public class ViewScope implements Scope, HttpSessionBindingListener 

private static final long serialVersionUID = 1L;

private static final Log logger = LogFactory.getLog(ViewScope.class);

private final WeakHashMap<HttpSession, Set<ViewScopeViewMapListener>> sessionToListeners = new WeakHashMap<>();

@Override
public Object get(String name, ObjectFactory objectFactory) 
    Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
    //noinspection SynchronizationOnLocalVariableOrMethodParameter
    if (viewMap.containsKey(name)) 
        return viewMap.get(name);
     else 
        synchronized (viewMap) 
            if (viewMap.containsKey(name)) 
                return viewMap.get(name);
             else 
                logger.trace("Creating bean " + name);
                Object object = objectFactory.getObject();
                viewMap.put(name, object);
                return object;
            
        
    


@Override
public String getConversationId() 
    return null;


/**
 * Removing bean from the scope and unregister it destruction callback without executing them.
 *
 * @see Scope for more details
 */
@Override
public Object remove(String name) 
    Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
    if (viewMap.containsKey(name)) 
        Object removed;
        synchronized (viewMap) 
            if (viewMap.containsKey(name)) 
                removed = FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
             else 
                return null;
            
        

        HttpSession httpSession = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(true);
        Set<ViewScopeViewMapListener> sessionListeners;
        sessionListeners = sessionToListeners.get(httpSession);
        if (sessionListeners != null) 
            Set<ViewScopeViewMapListener> toRemove = new HashSet<>();
            for (ViewScopeViewMapListener listener : sessionListeners) 
                if (listener.getName().equals(name)) 
                    toRemove.add(listener);
                    FacesContext.getCurrentInstance().getViewRoot().unsubscribeFromViewEvent(PreDestroyViewMapEvent.class, listener);
                
            
            synchronized (sessionListeners) 
                sessionListeners.removeAll(toRemove);
            
        

        return removed;
    
    return null;


/**
 * Register callback to be executed only on the whole scope destroying (not single object).
 *
 * @see Scope for more details
 */
@Override
public void registerDestructionCallback(String name, Runnable callback) 
    logger.trace("registerDestructionCallback for bean " + name);

    UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
    ViewScopeViewMapListener listener = new ViewScopeViewMapListener(viewRoot, name, callback, this);

    viewRoot.subscribeToViewEvent(PreDestroyViewMapEvent.class, listener);

    HttpSession httpSession = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(true);

    final Set<ViewScopeViewMapListener> sessionListeners;

    if (sessionToListeners.containsKey(httpSession)) 
        sessionListeners = sessionToListeners.get(httpSession);
     else 
        synchronized (sessionToListeners) 
            if (sessionToListeners.containsKey(httpSession)) 
                sessionListeners = sessionToListeners.get(httpSession);
             else 
                sessionListeners = new HashSet<>();
                sessionToListeners.put(httpSession, sessionListeners);
            
        
    

    synchronized (sessionListeners) 
        sessionListeners.add(listener);
    

    if (!FacesContext.getCurrentInstance().getExternalContext().getSessionMap().containsKey("sessionBindingListener")) 
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("sessionBindingListener", this);
    



@Override
public Object resolveContextualObject(String key) 
    return null;


@Override
public void valueBound(HttpSessionBindingEvent event) 
    logger.trace("Session event bound " + event.getName());


/**
 * Seems like it called after our listeners were unbounded from HTTP session.
 * Looks like view scope is destroyed. But should we call callback or not is a big question.
 *
 * @see HttpSessionBindingListener for more details
 */
@Override
public void valueUnbound(HttpSessionBindingEvent event) 
    logger.trace("Session event unbound " + event.getName());
    final Set<ViewScopeViewMapListener> listeners;
    synchronized (sessionToListeners) 
        if (sessionToListeners.containsKey(event.getSession())) 
            listeners = sessionToListeners.get(event.getSession());
            sessionToListeners.remove(event.getSession());
         else 
            listeners = null;
        
    
    if (listeners != null) 
        // I just hope that JSF context already done this job
        for (ViewScopeViewMapListener listener : listeners) 
            // As long as our callbacks can run only once - this is not such big deal
            listener.doCallback();
        
    


/**
 * Will remove the listener from the session set and unregister it from UIViewRoot.
 */
public void unregisterListener(ViewScopeViewMapListener listener) 
    logger.debug("Removing listener from map");
    HttpSession httpSession = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(false);
    FacesContext.getCurrentInstance().getViewRoot().unsubscribeFromViewEvent(PreDestroyViewMapEvent.class, listener);
    if (httpSession != null) 
        synchronized (sessionToListeners) 
            if (sessionToListeners.containsKey(httpSession)) 
                sessionToListeners.get(httpSession).remove(listener);
            
        
    

- ViewScopeViewMapListener :

public class ViewScopeViewMapListener implements ViewMapListener 

    private static final Log logger = LogFactory.getLog(ViewScope.class);

    private final String name;

    private final Runnable callback;

    private boolean callbackCalled = false;

    private final WeakReference<UIViewRoot> uiViewRootWeakReference;

    private final ViewScope viewScope;

    public ViewScopeViewMapListener(UIViewRoot root, String name, Runnable callback, ViewScope viewScope) 
        this.name = name;
        this.callback = callback;
        this.uiViewRootWeakReference = new WeakReference<>(root);
        this.viewScope = viewScope;
    

    public synchronized void doCallback() 
        logger.trace("Going call callback for bean " + name);
        if (!callbackCalled) 
            try 
                callback.run();
             finally 
                callbackCalled = true;
            
        
    

    public String getName() 
        return name;
    

    @Override
    public boolean isListenerForSource(Object source) 
        return (source == uiViewRootWeakReference.get());
    

    @Override
    public void processEvent(SystemEvent event) throws AbortProcessingException 
        if (event instanceof PreDestroyViewMapEvent) 
            logger.trace("Going call callback for bean " + name);
            doCallback();
            viewScope.unregisterListener(this);
        
    


- SpringScopeView 注释:

@Qualifier
@Scope("view")
@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SpringScopeView 

- 定义自定义范围:

    @Configuration
public class MyViewScope extends CustomScopeConfigurer 

    public InitViewScope() 
        log.info("Init ViewScope");
        Map<String, Object> map = new HashMap<>();
        map.put("view", new ViewScope());
        super.setScopes(map);
    

- 查看范围 bean 示例:

@Component("employeeRequest")
@SpringScopeView
public class EmployeeRequestBean implements Serializable 

    private static final long serialVersionUID = 6609775672949354713L;

    

    @Autowired
    private CurrentUserBean currentUserBean;

    @Autowired
    private RequestRepository requestRepository;

    @Autowired
    private ActionRepository actionRepository;

    
    @Autowired
    private SMSServiceClient smsServiceClient;

    private List<AttachmentDTO> uploadedFilesList = new ArrayList<AttachmentDTO>();

    private Request request;
    
    private Action action;


    @PostConstruct
    public void init() 
        try 
            String selectedRequestId = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap()
                    .get("selectedRequestId");
            if (StringUtils.isBlank(selectedRequestId))
                return;
            request = requestRepository.getOne(Long.parseLong(selectedRequestId));
            showRequest();
         catch (Exception e) 
            log.error("Exception in init EmployeeRequestBean: ", e);
            throw new RuntimeException(e);
        
    

    @PreDestroy
    public void preDestroy() 
        System.err.println("####### EmployeeRequestBean DESTROYED #########");
    
    

问题:在执行注销时,所有会话范围 bean 都会被销毁,并且会执行对 @PreDestroy 的调用,而不会调用视图范围 bean @PreDestroy

此问题仅在 WebSphere 8.5.5.13 上发生,但在 tomcat 9 上运行相同的应用程序时,一切正常。

【问题讨论】:

【参考方案1】:

@PreDestroy 很棘手。你看到this 问题了吗?他们说,最终,它应该在某个时候被触发。我想,这不适合你。

怎么办?

避免使用@PreDestroy,我正在尽我所能不使用它们。我讨厌这样的问题 - 有时会触发某些东西,即使它今天有效,升级也会破坏它......

通常,我所做的是跟踪已启动和正在运行的会话。其他一切都可以关闭 - 通过调用方法。这样我就可以进行测试、集成测试,还可以跟踪活跃用户的数量。像往常一样 - 更多的工作,更好的控制。

对我来说,JSF 和 Spring 是分开的。 @PreDestroy 是 Spring 功能。我的猜测是某些东西一直引用该对象,这就是它没有被破坏的原因。而那个 jneat 与那个是分开的。这里证明,对于纯 Spring bean,它可以工作......

【讨论】:

以上是关于当会话在 websphere 中失效时,Spring 视图范围不会被破坏的主要内容,如果未能解决你的问题,请参考以下文章

如何记录 tomcat 会话失效或用户注销

注销后 Cookie 会话不会失效

Oracle会话和连接池之间的关系

IllegalStateException:getAttribute:会话已经失效

当外部服务尚未回复时,Websphere 7.0 恰好在 180 秒后抛出 503

Spring 4,Websphere 8.5.5 上的 Hibernate JPA JarInputStreamBasedArchiveDescriptor 错误