2010-09-23 16 views
8

Non sono sicuro se ciò che sto facendo è sbagliato, o se ho perso un'annotazione o un elemento di configurazione da qualche parte. Ecco la situazione:JSF - Il bean gestito con ambito sessione non ha re-iniettate dipendenze nella deserializzazione della sessione

Ho un'applicazione JSF con un bean con ambito sessione denominato SessionData. Questo bean ha un riferimento al bean con ambito applicazione (di tipo ApplicationData) inserito al suo interno al momento della creazione. Funziona bene quando la sessione viene creata per la prima volta. L'iniezione di dipendenza è fatto con <managed-bean> elementi nel file faces-config.xml come illustrato di seguito:

<managed-bean> 
    <managed-bean-name>sessionData</managed-bean-name> 
    <managed-bean-class>my.package.SessionData</managed-bean-class> 
    <managed-bean-scope>session</managed-bean-scope> 
    <managed-property> 
     <property-name>applicationData</property-name> 
     <property-class>my.package.ApplicationData</property-class> 
     <value>#{applicationData}</value> 
    </managed-property> 
</managed-bean> 
<managed-bean> 
    <managed-bean-name>applicationData</managed-bean-name> 
    <managed-bean-class>my.package.ApplicationData</managed-bean-class> 
    <managed-bean-scope>application</managed-bean-scope> 
</managed-bean> 

Perché non ha senso avere il mio SessionData oggetto includono l'oggetto ApplicationData quando è serializzato, ho segnato il riferimento ApplicationData come transitoria nel mio SessionData oggetto:

transient private ApplicationData applicationData; 

Tutto è bene fino a quando si arresta l'applicazione web (nel mio container 6.x Tomcat) e le sessioni sono serializzato. Quando riavvio l'applicazione e le sessioni vengono deserializzate, il mio riferimento a ApplicationData non viene re-iniettato da JSF. So che la deserializzazione dovrebbe lasciare campi transitori senza un valore. C'è un modo per segnalare a JSF che questo oggetto con ambito sessione richiede che le sue dipendenze vengano nuovamente impostate dopo la deserializzazione?

Sto utilizzando MyFaces JSF 1.2 e Tomcat 6.0.26 come contenitore di applicazioni Web.

+1

È stato suggerito di fornire un metodo readObject() e di impostare manualmente l'oggetto ApplicationData lì durante la deserializzazione utilizzando FacesContext. Non penso che funzionerà poiché FacesContext è disponibile solo durante la vita di una richiesta. La deserializzazione sta accadendo all'avvio dell'applicazione. –

+2

corretto, ecco perché ho cancellato la mia risposta. Sembra più complicato (quindi +1 per la domanda) – Bozho

risposta

5

Sebbene la soluzione offerta da Bozho possa funzionare, non voglio introdurre oggetti proxy in un'applicazione che al momento non li sta utilizzando. La mia soluzione è tutt'altro che ideale, ma fa il lavoro.

ho lasciato il campo transitoria posto:

transient private ApplicationData _applicationData; 

Ho anche lasciato il setter in luogo così JSF può inizialmente impostare il riferimento quando l'oggetto SessionData viene creato la prima volta:

public void setApplicationData(ApplicationData applicationData) { 
    _applicationData = applicationData; 
} 

Il cambiamento che ho fatto è stato nel metodo getter. I metodi nell'oggetto SessionData ora devono interrompere l'accesso direttamente al campo _applicationData e ottenere il riferimento tramite il getter. Il getter prima verificherà un riferimento null. Se è nullo, il bean gestito viene ottenuto tramite lo FacesContext. Il vincolo qui è che lo FacesContext è disponibile solo durante la vita di una richiesta.

/** 
* Get a reference to the ApplicationData object 
* @return ApplicationData 
* @throws IllegalStateException May be thrown if this method is called 
* outside of a request and the ApplicationData object needs to be 
* obtained via the FacesContext 
*/ 
private ApplicationData getApplicationData() { 
    if (_applicationData == null) { 
     _applicationData = JSFUtilities.getManagedBean(
      "applicationData", // name of managed bean 
      ApplicationData.class); 
     if (_applicationData == null) { 
      throw new IllegalStateException(
       "Cannot get reference to ApplicationData object"); 
     } 
    } 
    return _applicationData; 
} 

se qualcuno avesse voglia, qui è il codice per il mio metodo getManagedBean():

/** 
* <p>Retrieve a JSF managed bean instance by name. If the bean has 
* never been accessed before then it will likely be instantiated by 
* the JSF framework during the execution of this method.</p> 
* 
* @param managedBeanKey String containing the name of the managed bean 
* @param clazz Class object that corresponds to the managed bean type 
* @return T 
* @throws IllegalArgumentException Thrown when the supplied key does 
* not resolve to any managed bean or when a managed bean is found but 
* the object is not of type T 
*/ 
public static <T> T getManagedBean(String managedBeanKey, Class<T> clazz) 
     throws IllegalArgumentException { 
    Validate.notNull(managedBeanKey); 
    Validate.isTrue(!managedBeanKey.isEmpty()); 
    Validate.notNull(clazz); 
    FacesContext facesContext = FacesContext.getCurrentInstance(); 
    if (facesContext == null) { 
     return null; 
    } 
    Validate.notNull(facesContext.getApplication()); 
    ELResolver resolver = facesContext.getApplication().getELResolver(); 
    Validate.notNull(resolver); 
    ELContext elContext = facesContext.getELContext(); 
    Validate.notNull(elContext); 
    Object managedBean = resolver.getValue(
     elContext, null, managedBeanKey); 
    if (!elContext.isPropertyResolved()) { 
     throw new IllegalArgumentException(
      "No managed bean found for key: " + managedBeanKey); 
    } 
    if (managedBean == null) { 
     return null; 
    } else { 
     if (clazz.isInstance(managedBean)) { 
      return clazz.cast(managedBean); 
     } else { 
      throw new IllegalArgumentException(
       "Managed bean is not of type [" + clazz.getName() + 
       "] | Actual type is: [" + managedBean.getClass().getName()+ 
       "]"); 
     } 
    } 
} 

E non scegliere sul mio Convalida chiama. Li toglierò dopo che avrò finito lo sviluppo! :)

+4

C'è un collegamento in ['Application # evaluateExpressionGet()'] (http://download.oracle.com/javaee/6/api/javax/faces/application/ Application.html # evaluateExpressionGet% 28javax.faces.context.FacesContext,% 20java.lang.String,% 20java.lang.Class% 29). Vedi anche [questa risposta] (http://stackoverflow.com/questions/2633112/jsf-get-managed-bean-by-name/2633733#2633733). – BalusC

1

è possibile aggiungere un metodo:

private void readObject(java.io.ObjectInputStream in) 
throws IOException, ClassNotFoundException { 
    in.defaultReadObject(); 
    applicationData = initializeApplicationData(); 
} 

E in initializeApplicationData è possibile utilizzare un oggetto proxy dinamica. Usando CGLIB o javassist crea un proxy che, prima di ogni chiamata di metodo, imposta un campo interno - il reale ApplicationData. Se è null, quindi ottenere la corrente FacesContext (che sarà accessibile a quel punto) e ottenere il bean gestito da lì:

FacesContext facesContext = FacesContext.getCurrentInstance(); 
originalApplicationData = (ApplicationData)facesContext.getApplication() 
    .createValueBinding("#{applicationData}").getValue(facesContext); 

e delegare suoi metodi.

Questa è una brutta soluzione, ma penso che funzionerà.

+0

Questa sarà la mia prima volta che uso javassist, quindi avrò bisogno di tempo per digerire quella parte della soluzione. Grazie. –

+0

è abbastanza facile. Segui il tutorial: il tuo caso proxy è il più semplice :) – Bozho

Problemi correlati