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.