当 session 在 websphere 中失效时,Spring View 范围不会被破坏

标签 spring spring-boot jsf websphere view-scope

我正在使用 Springboot 1.5.9.RELEASE , JSF 2.2.20(玻璃鱼) , Primefaces 8 运行于 WebSphere 8.5.5.13
,并且我已经根据以下链接定义了自定义 View 范围:
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 #########");
    }
    
}
问题:执行注销时,所有 session 作用域 bean 都会被破坏并调用 @PreDestroy执行,而 View 范围 bean @PreDestroy没有被调用!
此问题仅在 WebSphere 8.5.5.13 上发生,但在 tomcat 9 上运行相同的应用程序时,一切都按预期工作。

最佳答案

@PreDestroy很棘手。你看到了吗this问题?他们说最终应该在某个时候触发它。我想,这对你来说是不行的。
该怎么办?
避免 @PreDestroy ,我尽量不使用它们。我讨厌这样的问题 - 有些东西,有时会被触发,即使它今天有效,升级也会破坏它......
通常,我所做的是跟踪已启动和正在运行的 session 。其他一切都可以关闭 - 通过调用方法。这样我就可以进行测试、集成测试,还可以跟踪活跃用户的数量。像往常一样 - 更多的工作,更好的控制。
对我来说,分别是 JSF 和 Spring。那个@PreDestroy是 Spring 功能。我的猜测是有些东西保持对对象的引用,这就是它没有被销毁的原因。那个 jneat 与那个是分开的。这里证明,对于纯 Spring bean,它是有效的......

关于当 session 在 websphere 中失效时,Spring View 范围不会被破坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67885285/

相关文章:

java - 表达语言 : how to simplify this statement ("in"-like clause needed)

java - 保存前未设置关系 ID

java - 如何使用 Spring @Autowire 设置 TaskScheduler 以变量 n 秒运行方法?

java - 在使用 spring-integration 创建的 Socket 上设置哪些超时?

java - 使用自定义范围/角色保护 Spring Boot 应用程序

java - 表达式在 f :facet? JSF 中不起作用

java - 如何使用 @DataJpaTest 和 SpringFox @EnableSwagger2 在 Spring Boot 1.4 中进行切片测试

java - Spring 启动: web-app + webservice

spring-boot - 在错误情况下从 WebClient 获取响应主体的正确方法是什么?

jsf - 如何在 JSF 2.2 中呈现 HTML 按钮元素?