How to thread-safely share an attribute between the beforePhase() and the afterPhase() methods of a PhaseListener?

StackOverflow https://stackoverflow.com/questions/19176335

質問

I need to share an attribute between the beforePhase() and the afterPhase() methods of my PhaseListener, for a same JSF request.

Is the following snippet thread-safe?

public class MyPhaseListener implements PhaseListener {

  private MyObject o = null;

  @Override
  public void beforePhase(PhaseEvent event) {
    if (condition) {
      o = new MyObject();
    }
  }

  @Override
  public void afterPhase(PhaseEvent event) {
    if (o != null) {
      o.process();
      o = null;
    }
  }

  @Override
  public PhaseId getPhaseId() {
    return PhaseId.RESTORE_VIEW;
  }

}

If not, what are other solutions?

役に立ちましたか?

解決

This is definitely not threadsafe. There's only one phase listener instance applicationwide which is shared across multiple requests. Basically, a phase listener is like an @ApplicationScoped managed bean.

Just set it as a context attribute.

public class MyPhaseListener implements PhaseListener {

  @Override
  public void beforePhase(PhaseEvent event) {
    if (condition) {
      event.getFacesContext().setAttribute("o", new MyObject());
    }
  }

  @Override
  public void afterPhase(PhaseEvent event) {
    MyObject o = (MyObject) event.getFacesContext().getAttribute("o");
    if (o != null) {
      o.process();
    }
  }

  @Override
  public PhaseId getPhaseId() {
    return PhaseId.RESTORE_VIEW;
  }

}

他のヒント

You could use ThreadLocal for this, but it tends to have issues in environments having different classloaders, to name it: memory leak. Be sure to check for that in the given environment...

Also, you should make it sure that if the processing can be interrupted (e.g. exception...) between the beforePhase() and afterPhase() methods, the ThreadLocal should be handled appropriately...

This is what it would look like:

public class MyPhaseListener implements PhaseListener {

  //if null is a valid value, no initial setting is needed
  private ThreadLocal<Object> myStateObject = new ThreadLocal<Object> ();

  @Override
  public void beforePhase(PhaseEvent event) {

    //might be needed, to guarrantee no residue from an aborted processing is in there
    myState.set(null); 
    if (condition) {
      myState.set(<Object representing the state>);
    }
  }

  @Override
  public void afterPhase(PhaseEvent event) {
    try {
        Object stateObject = myState.get();
        if (stateObejct!=null) {
          //do what you have to 
        }
    } finally {
       //to be sure
       myState.remove();
    }
  }
}

In this article the author uses ThreadLocal too...

Also, this article is also a great eye-opener, explaining why not to share mutable instance-level information:

One thing to remember though, is that PhaseListener instances are application-wide Singletons that are referenced by the JSF Lifecycle, which itself is an application-wide Singleton.

EDIT just saw Boolean got updated to Object, adjusted example

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top