当会话在 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.RELEASE、JSF 2.2.20 (glassfish)、Primefaces 8 在 WebSphere 上运行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 视图范围不会被破坏的主要内容,如果未能解决你的问题,请参考以下文章
IllegalStateException:getAttribute:会话已经失效
当外部服务尚未回复时,Websphere 7.0 恰好在 180 秒后抛出 503
Spring 4,Websphere 8.5.5 上的 Hibernate JPA JarInputStreamBasedArchiveDescriptor 错误