2011-01-04 4 views
5

Ho due istanze di un bean CDI SessionScoped per la stessa sessione. Avevo l'impressione che ci sarebbe stata un'istanza generata per me da CDI, ma ne ha generati due. Sto fraintendendo come funziona il CDI, o ho trovato un bug?CDI SessionScoped Bean ha come risultato due istanze nella stessa sessione

Ecco il codice del bean:

package org.mycompany.myproject.session; 

import java.io.Serializable; 
import javax.enterprise.context.SessionScoped; 
import javax.faces.context.FacesContext; 
import javax.inject.Named; 
import javax.servlet.http.HttpSession; 

@Named @SessionScoped public class MyBean implements Serializable { 
    private String myField = null; 

    public MyBean() { 
     System.out.println("MyBean constructor called"); 

     FacesContext fc = FacesContext.getCurrentInstance(); 
     HttpSession session = (HttpSession)fc.getExternalContext().getSession(false); 
     String sessionId = session.getId(); 
     System.out.println("Session ID: " + sessionId); 
    } 

    public String getMyField() { 
     return myField; 
    } 

    public void setMyField(String myField) { 
     this.myField = myField; 
    } 
} 

ecco il codice facelet:

<?xml version='1.0' encoding='UTF-8' ?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html" 
    xmlns:f="http://java.sun.com/jsf/core"> 
<f:view contentType="text/html" encoding="UTF-8"> 
    <h:head> 
     <title>Test</title> 
    </h:head> 
    <h:body> 
     <h:form id="form"> 
      <h:inputText value="#{myBean.myField}"/> 
      <h:commandButton value="Submit"/> 
     </h:form> 
    </h:body> 
</f:view> 
</html> 

Ecco l'uscita dalla distribuzione e la navigazione alla pagina:

INFO: Loading application org.mycompany_myproject_war_1.0-SNAPSHOT at /myproject 
INFO: org.mycompany_myproject_war_1.0-SNAPSHOT was successfully deployed in 8,237 milliseconds. 
INFO: MyBean constructor called 
INFO: Session ID: 175355b0e10fe1d0778238bf4634 
INFO: MyBean constructor called 
INFO: Session ID: 175355b0e10fe1d0778238bf4634 

Utilizzando GlassFish 3.0.1

+1

mi è stato effettivamente avvisato il problema sopra esposto da un correlato uno: chiamare un metodo non finale in un costruttore (o blocco di inizializzazione) provoca effetti indesiderati con CDI. Da allora ho letto che l'uso di un metodo non finale non è consigliato (http://download.oracle.com/javase/tutorial/java/javaOO/initial.html). Se uso un metodo non finalizzato per inizializzare una lista in un bean CDI, l'inizializzatore viene chiamato due volte! Nota: CDI non consente i metodi finali e genera un'eccezione di runtime che indica che il bean non è proxyable. La "correzione" consiste nel non chiamare il metodo non finale e fare tutto il lavoro nel blocco initilizer. – Ryan

+0

Ho notato che se definisco un metodo init annotato con @PostConstruct, viene chiamato solo una volta (nonostante due istanze del bean vengano create). Sto indovinando che CDI sta creando un pool di istanze del mio bean e chiama post costrutto mentre li estrae dal pool. Suppongo che associare l'istanza del bean che è ancora nel pool con la sessione HTTP corrente sia priva di significato. – Ryan

+1

Vedere la mia risposta più sotto. Le 2 istanze 1 è l'istanza contestuale, 2 è il proxy. Ovviamente @PostConstruct verrà chiamato solo per l'istanza contestuale e _non_ per il proxy. – struberg

risposta

4

Ryan, come già scritto da covener, il costruttore verrà chiamato anche per ogni proxy per quel bean. Questo è un comportamento standard di tutti i meccanismi proxy che forniscono non solo il proxy delle interfacce (come la roba java.lang.reflect.proxy) ma il vero proxy di classe.

Immaginate anche che il ct verrà chiamato per ogni serializzazione. Quindi se lavori su un cluster con carico pesante, lo vedrai molte volte. Quindi per favore usa @PostConstruct per i fagioli in generale.

LieGrue, strub

+0

Grazie per la risposta. Non sto ancora capendo nessuno dei due. Sono a conoscenza di metodi alternativi per preparare un bean da utilizzare, come la callback del ciclo di vita PostConstruct e l'evento di sistema preRenderView; e questo è fantastico. La mia domanda è perché due istanze di un bean con scope di sessione vengono create in un test molto semplice con un solo utente che colpisce il server. Non sto parlando di un server con carichi pesanti con bilanciamento del carico e clustering. È stato creato più di un proxy? Se è così, perché? O forse ogni proxy è supportato da due istanze del bean? Se è così, perché? – Ryan

+2

Ryan, so che tutta la roba del proxy non è così facile, ma ottenere 2 invocazioni del costruttore è perfetto. La prima istanza è l'istanza contestuale stessa.Questo è il bean che verrà memorizzato in SessionContext. La seconda istanza è il proxy. Se guardi da vicino il debugger o stampi la classe nel costruttore, vedrai che questa è di fatto una sottoclasse del tuo bean! – struberg

+0

Uno è il proxy e uno è il bean effettivo. Fatto. Sembra che tu debba fare attenzione a ciò che hai inserito in un costruttore quando usi il CDI. – Ryan

3

È probabile che l'implementazione CDI richiami il costruttore di default dei bean sottostanti durante la new up dei proxy da utilizzare per i punti di iniezione: questo è il comportamento predefinito di javassist utilizzato in weld e openwebbeans.

Evita il sollevamento pesante nel costruttore predefinito, spostandolo in @PostConstruct se è possibile!

+0

OK, ma perché vengono creati due? CDI crea un pool? – Ryan