简体   繁体   English

当 session 在 websphere 中失效时,Spring 视图 scope 不会被破坏

[英]Spring view scope is not destroyed when session gets invalidated in websphere

I am using Springboot 1.5.9.RELEASE , JSF 2.2.20 (glassfish) , Primefaces 8 running on WebSphere 8.5.5.13 , and I have defined custom view scope, based on the following link: I am using Springboot 1.5.9.RELEASE , JSF 2.2.20 (glassfish) , Primefaces 8 running on WebSphere 8.5.5.13 , and I have defined custom view scope, based on the following link:

https://github.com/jneat/spring-jsf 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: - 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 annotation: - SpringScopeView 注释:

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

- Define the custom scope: - 定义自定义 scope:

    @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);
    }
}

- View scope bean sample: - 查看 scope 豆样品:

@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: when performing logout all session scope beans get destroyed and a call to @PreDestroy is performed, while view scope beans @PreDestroy are not getting called!问题:执行注销时,所有 session scope bean 都被销毁并调用了@PreDestroy ,而视图@PreDestroy bean 没有被调用!

this issue happens on WebSphere 8.5.5.13 only, but when running the same application on tomcat 9, everything works as expected.此问题仅在 WebSphere 8.5.5.13 上发生,但在 tomcat 9 上运行相同的应用程序时,一切正常。

@PreDestroy is tricky. @PreDestroy很棘手。 Have you see this issue?你看到这个问题了吗? They say that eventually, at some point it should be triggered.他们说,最终,它应该在某个时候被触发。 I assume, that it is not ok for you.我认为,这对你来说不合适。

What to do?该怎么办?

Avoid @PreDestroy , I'm doing my best not to use them.避免使用@PreDestroy ,我正在尽我所能不使用它们。 I hate problems like this - something, sometimes is triggered, even if it works today, upgrade can break it...我讨厌这样的问题 - 有时会触发某些东西,即使它今天有效,升级也会破坏它......

Usually, what I do is to track sessions that are up, and running.通常,我所做的是跟踪已启动和正在运行的会话。 Everything else can be closed - by calling method.其他一切都可以关闭 - 通过调用方法。 That way I have tests, integration tests, also I can track number of active users.这样我就可以进行测试、集成测试,还可以跟踪活跃用户的数量。 As usual - more work, better control.像往常一样 - 更多的工作,更好的控制。

To me that goes separately JSF, and Spring.对我来说,JSF 和 Spring 是分开的。 That @PreDestroy is Spring feature. @PreDestroy是 Spring 功能。 My guess is that something keeps reference to the object, and that is why it is not destroyed.我的猜测是,有些东西一直引用 object,这就是它没有被破坏的原因。 And that jneat is separate from that.而那个 jneat 与那个是分开的。 Prove here is that for pure Spring beans it works...这里证明的是,对于纯 Spring bean,它可以工作......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM