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



我使用的是运行在WebSphere 8.5.5.13上的Springboot 1.5.9.RELEASEJSF 2.2.20(glassfish(Primefaces 8,我已经基于以下链接定义了自定义视图范围:

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 #########");
}

}

ISSUE:执行注销时,所有会话作用域bean都会被销毁,并执行对@PreDestroy的调用,而视图作用域bean@PreDestroy不会被调用!

这个问题只发生在WebSphere 8.5.5.13上,但当在tomcat 9上运行同一个应用程序时,一切都能正常工作。

@PreDestroy很棘手。你看到这个问题了吗?他们说,最终,在某个时刻,它应该被触发。我想,这对你来说不太好。

该怎么办?

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

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

对我来说,JSF和Spring是分开的。@PreDestroy是Spring特性。我的猜测是,有东西一直引用着这个物体,这就是为什么它没有被破坏的原因。这个jneat和那个是分开的。这里证明了,对于纯春豆来说,它是有效的。。。

最新更新