7 votes

Spring view scope n'est pas détruit lorsque la session est invalidée dans websphere

J'utilise Springboot 1.5.9.RELEASE , JSF 2.2.20 (glassfish) , Primefaces 8 en cours WebSphere 8.5.5.13 et j'ai défini une vue personnalisée, basée sur le lien suivant :

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

}

- Annotation SpringScopeView :

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

- Définissez la portée personnalisée :

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

- Voir l'échantillon de haricot de portée :

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

}

PROBLÈME : lors de la déconnexion, tous les beans d'étendue de session sont détruits et un appel à @PreDestroy est effectuée, tandis que les beans de la portée de la vue @PreDestroy ne sont pas appelés !

ce problème se produit uniquement sur WebSphere 8.5.5.13, mais lorsque l'on exécute la même application sur tomcat 9, tout fonctionne comme prévu.

0voto

Michał Zaborowski Points 2760

@PreDestroy est délicat. Avez-vous vu ce question ? Ils disent qu'à un moment ou à un autre, il devrait être déclenché. Je suppose que ce n'est pas le cas pour vous.

Que faire ?

Évitez @PreDestroy je fais de mon mieux pour ne pas les utiliser. Je déteste les problèmes de ce genre - quelque chose, parfois, se déclenche, même si ça marche aujourd'hui, une mise à jour peut le casser...

D'habitude, ce que je fais, c'est de suivre les sessions qui sont en place et qui fonctionnent. Tout le reste peut être fermé - en appelant la méthode. De cette façon, j'ai des tests, des tests d'intégration, et je peux également suivre le nombre d'utilisateurs actifs. Comme d'habitude - plus de travail, meilleur contrôle.

Pour moi, cela va séparément de JSF, et de Spring. Cela @PreDestroy est la caractéristique du printemps. Je pense que quelque chose garde la référence à l'objet, et c'est pourquoi il n'est pas détruit. Et que le jneat est séparé de cela. La preuve ici est que pour les beans Spring purs, cela fonctionne...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X