2010-10-15 2 views
6

È possibile creare una factory o un proxy che può decidere se il thread è in esecuzione in (Web) Request o background-process (cioè.) e quindi, in base a tali informazioni, crea un bean di sessione o un bean prototipo?Spring: Inject bean dipeso dal contesto (sessione/web o processo thread/background locale)

Esempio (pseudo primavera config :)

<bean id="userInfoSession" scope="session" /> 
<bean id="userInfoStatic" scope="prototype" /> 

<bean id="currentUserInfoFactory" /> 

<bean id="someService" class="..."> 
    <property name="userInfo" ref="currentUserInfoFactory.getCurrentUserInfo()" /> 
</bean> 

Spero che questo rende la mia domanda più facile da capire ...


mia soluzione

Non è mai troppo tardi per aggiornare le proprie domande;). L'ho risolto con due diverse istanze di sessione client, una sessione client SessionScoped e una sessione SingletonScoped. Entrambi sono fagioli normali.

<bean id="sessionScopedClientSession" class="com.company.product.session.SessionScopedClientSession" scope="session"> 
    <aop:scoped-proxy /> 
</bean> 

<bean id="singletonScopedClientSession" class="com.company.product.session.SingletonScopedClientSession" /> 

<bean id="clientSession" class="com.company.product.session.ClientSession"> 
    <property name="sessionScopedClientSessionBeanName" value="sessionScopedClientSession" /> 
    <property name="singletonScopedClientSessionBeanName" value="singletonScopedClientSession" /> 
</bean> 

Il ClientSession deciderà quindi se singolo o sessione di portata:

private IClientSession getSessionAwareClientData() { 
    String beanName = (isInSessionContext() ? sessionScopedClientSessionBeanName : singletonScopedClientSessionBeanName); 
    return (IClientSession) ApplicationContextProvider.getApplicationContext().getBean(beanName); 
} 

Dove tipo di sessione potrebbero essere raccolte attraverso questo:

private boolean isInSessionContext() { 
    return RequestContextHolder.getRequestAttributes() != null; 
} 

Tutte le classi implementano un'interfaccia denominata IClientSession. Sia i bean singletonScoped che sessionScoped si estendono da una BaseClientSession in cui viene trovata l'implementazione.

Ogni servizio quindi possibile utilizzare la sessione client cioè:

@Resource 
private ClientSession clientSession; 

    ... 

public void doSomething() { 
    Long orgId = clientSession.getSomethingFromSession(); 
} 

Ora, se andiamo un passo avanti possiamo scrivere qualcosa di simile a un emulatore per la sessione. Questo può essere fatto inizializzando la sessione client (che non è nel contesto di una richiesta) della sessione singleton. Ora tutti i servizi possono utilizzare lo stesso clientSession e siamo ancora in grado di "emulare" un utente cioè:

 clientSessionEmulator.startEmulateUser(testUser); 
     try { 
      service.doSomething(); 
     } finally { 
      clientSessionEmulator.stopEmulation(); 
     } 

più Un consiglio: si abbia cura di filettatura in caso SingletonScoped clientSession! Wouw, pensavo di poterlo fare con meno linee;) Se ti piace saperne di più su questo approccio non esitare a contattarmi.

risposta

1

tuo riformulare è infatti notevolmente più semplice :)

tuo currentUserInfoFactory potrebbe fare uso di RequestContextHolder.getRequestAttributes(). Se una sessione è presente e associata al thread chiamante, verrà restituito un oggetto non null e sarà quindi possibile recuperare in modo sicuro il bean con ambito sessione dal contesto. Se restituisce un valore null, è necessario recuperare il bean con ambito prototipo.

Non è molto pulito, ma è semplice e dovrebbe funzionare.

+0

Ci proverò. A volte la soluzione più semplice è la migliore e se funziona, perché non è pulito? Alcune altre soluzioni? –

+1

@Frank: il codice che utilizza 'RequestContextHolder' è generalmente difficile da testare. Per questo motivo, dovrebbe essere scoraggiato. – skaffman

1

Creare due caricatori di contesto personalizzati che legano lo stesso defintion scopo di diverse implementazioni:

public final class SessionScopeContextLoader extends GenericXmlContextLoader { 

    protected void customizeContext(final GenericApplicationContext context) { 
    final SessionScope testSessionScope = new SessionScope(); 
    context.getBeanFactory().registerScope("superscope", testSessionScope); 
    } 
    ... 
} 

Poi fate una corrispondente per Singleton (fare il vostro proprio ambito con solo statica)

Poi basta specificare il caricatore di contesto appropriato nell'avvio xml per ciascuno dei due contesti.

+0

Dove devo specificare il caricatore di contesto appropriato in WebContext? Non capisco il punto sull'ambito personalizzato, mi dispiace. Ho trovato la tua conversione qui http://stackoverflow.com/questions/450557/custom-spring-scopes ma non mi ha raggiunto il punto ... –

2

Ho creato una piccola soluzione universale per iniettare i fagioli in base al contesto.

Indovina abbiamo due fave:

<bean class="xyz.UserInfo" id="userInfo" scope="session" /> 
<bean class="xyz.UserInfo" id="userInfoSessionLess" /> 

Vogliamo usare fagiolo "userInfo" per le azioni degli utenti web e fagioli "userInfoSessionLess" per i servizi in background, per esempio. Wa anche voler scrivere codice e non vogliono pensare a contesto, ad esempio:

@Autowired 
//You will get "java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request?" for session less services. 
//We can fix it and autowire "userInfo" or "userInfoSessionLess" depends on context... 
private UserInfo userInfo; 

public save(Document superSecureDocument) { 
    ... 
    superSecureDocument.lastModifier = userInfo.getUser(); 
    ... 
} 

Ora abbiamo bisogno di creare ambito sessione personalizzato per rendere ha funzionato:

public class MYSessionScope extends SessionScope implements ApplicationContextAware { 
    private static final String SESSION_LESS_POSTFIX = "SessionLess"; 
    private ApplicationContext applicationContext; 
    public Object get(String name, ObjectFactory objectFactory) { 
    if (isInSessionContext()) { 
     log.debug("Return session Bean... name = " + name); 
     return super.get(name, objectFactory); 
    } else { 
     log.debug("Trying to access session Bean outside of Request Context... name = " + name + " return bean with name = " + name + SESSION_LESS_POSTFIX); 
     return applicationContext.getBean(name.replace("scopedTarget.", "") + SESSION_LESS_POSTFIX); 
    } 
    } 
    private boolean isInSessionContext() { 
    return RequestContextHolder.getRequestAttributes() != null; 
    } 
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 
    this.applicationContext = applicationContext; 
    } 
} 

Registrati nuovo ambito di applicazione:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> 
    <property name="scopes"> 
     <map> 
      <entry key="mySession"> 
       <bean class="com.galantis.gbf.web.MYSessionScope" /> 
      </entry> 
     </map> 
    </property> 
</bean> 

Ora abbiamo bisogno di modificare fagioli definions come questo:

<bean class="xyz.UserInfo" id="userInfo" scope="mySession" autowire-candidate="true"/> 
<bean class="xyz.UserInfo" id="userInfoSessionLess" autowire-candidate="false"/> 

Questo è tutto. Il bean con il nome "SessionLess" verrà utilizzato per tutti i bean con scope "mySession" se utilizziamo il bean al di fuori del thread di richiesta web effettivo.

+0

Non ho ancora provato la tua soluzione ma mi sembra molto carina . Grazie per averlo condiviso! –