【问题标题】:Spring view scope is not destroyed when session gets invalidated in websphere当会话在 websphere 中失效时,Spring 视图范围不会被破坏
【发布时间】: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 上运行相同的应用程序时,一切正常。

【问题讨论】:

    标签: spring spring-boot jsf websphere view-scope


    【解决方案1】:

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

    怎么办?

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

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

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

    【讨论】:

      猜你喜欢
      • 2013-01-29
      • 1970-01-01
      • 2015-10-06
      • 2012-10-16
      • 1970-01-01
      • 2011-07-05
      • 2019-02-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多