簡體   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:

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 {
}

- 定義自定義 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);
    }
}

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

}

問題:執行注銷時,所有 session scope bean 都被銷毀並調用了@PreDestroy ,而視圖@PreDestroy bean 沒有被調用!

此問題僅在 WebSphere 8.5.5.13 上發生,但在 tomcat 9 上運行相同的應用程序時,一切正常。

@PreDestroy很棘手。 你看到這個問題了嗎? 他們說,最終,它應該在某個時候被觸發。 我認為,這對你來說不合適。

該怎么辦?

避免使用@PreDestroy ,我正在盡我所能不使用它們。 我討厭這樣的問題 - 有時會觸發某些東西,即使它今天有效,升級也會破壞它......

通常,我所做的是跟蹤已啟動和正在運行的會話。 其他一切都可以關閉 - 通過調用方法。 這樣我就可以進行測試、集成測試,還可以跟蹤活躍用戶的數量。 像往常一樣 - 更多的工作,更好的控制。

對我來說,JSF 和 Spring 是分開的。 @PreDestroy是 Spring 功能。 我的猜測是,有些東西一直引用 object,這就是它沒有被破壞的原因。 而那個 jneat 與那個是分開的。 這里證明的是,對於純 Spring bean,它可以工作......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM