2010-10-15 1 views
6

Est-il possible de créer une fabrique ou un proxy qui peut décider si le thread s'exécute dans une demande (Web) ou un processus de fond (par exemple.) puis en fonction de ces informations, il crée un bean session ou un bean prototype?Spring: le binaire Inject dépendait du contexte (processus session/web ou thread/arrière-plan local)

Exemple (pseudo config Spring :)

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

<bean id="currentUserInfoFactory" /> 

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

J'espère que cela ma question plus facile à comprendre ...


Ma solution

Il est jamais trop tard pour mettre à jour ses propres questions;). Je l'ai résolu avec deux instances différentes de session client, une session client SessionScoped et une session SingletonScoped. Les deux sont des haricots normaux.

<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> 

Le ClientSession alors décider si la portée singleton ou session:

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

où le type de session pourrait être recueillie par ceci:

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

Toutes les classes implémentent une interface appelée IClientSession. Les beans singletonScoped et sessionScoped s'étendent d'une BaseClientSession où l'implémentation est trouvée.

Chaque service peut alors utiliser la session client-à-dire:

@Resource 
private ClientSession clientSession; 

    ... 

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

Maintenant, si nous allons un peu plus loin, nous pouvons écrire quelque chose comme un émulateur pour la session. Cela peut être fait en initialisant la session clientSession (qui n'est dans aucun contexte d'une requête) la session singleton. Maintenant, tous les services peuvent utiliser le même clientSession et nous pouvons encore « imiter » un utilisateur à savoir:

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

Un autre conseil: prendre soin de filetage en SingletonScoped clientSession exemple! Wouw, je pensais que je pourrais le faire avec moins de lignes;) Si vous aimez en savoir plus sur cette approche, n'hésitez pas à me contacter.

Répondre

1

Votre rephrase est en effet beaucoup plus simple :)

Votre currentUserInfoFactory pourrait utiliser RequestContextHolder.getRequestAttributes(). Si une session est présente et associée au thread appelant, cela retournera un objet non nul, et vous pourrez alors récupérer en toute sécurité le bean de portée session du contexte. Si elle renvoie une valeur nulle, alors vous devriez aller chercher le bean prototype-scoped à la place.

Ce n'est pas très soigné, mais c'est simple, et ça devrait marcher.

+0

Je vais essayer. Parfois, la solution la plus simple est la meilleure et si cela fonctionne, pourquoi n'est-ce pas soigné? D'autres solutions? –

+1

@Frank: Le code qui utilise 'RequestContextHolder' est généralement difficile à tester. Pour cette raison, il devrait être découragé. – skaffman

1

Créer deux chargeurs de contexte personnalisés qui lient la même portée défintion à différentes implémentations:

public final class SessionScopeContextLoader extends GenericXmlContextLoader { 

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

Ensuite, vous émettez un pour singleton (faire votre propre champ avec seulement statics)

correspondant alors que vous venez spécifiez le chargeur de contexte approprié dans le démarrage xml pour chacun des deux contextes.

+0

Où dois-je spécifier le chargeur de contexte approprié dans WebContext? Je ne comprends pas vraiment le point sur la portée personnalisée, désolé. J'ai trouvé votre conversion ici http://stackoverflow.com/questions/450557/custom-spring-scopes mais cela ne m'a pas non plus permis d'obtenir le point non plus ... –

2

J'ai créé une petite solution de contournement universelle pour injecter des haricots dépend du contexte.

Devinez nous avons deux haricots:

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

Nous voulons utiliser « userInfo » haricot pour les actions des utilisateurs Web et « userInfoSessionLess » haricot pour les services de base par exemple. Wa veulent aussi écrire du code et ne veulent pas penser sur le contexte, par exemple:

@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(); 
    ... 
} 

Maintenant, nous avons besoin de créer une portée de session personnalisée pour faire cela a fonctionné:

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; 
    } 
} 

Enregistrer un nouveau champ d'application:

<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> 

maintenant, nous devons modifier definions haricots comme ceci:

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

C'est tout. Le bean avec le nom "SessionLess" sera utilisé pour tous les beans de portée "mySession" si nous utilisons le bean en dehors du thread de demande web actuel.

+0

N'a pas encore essayé ta solution mais elle me semble très mignonne . Merci de l'avoir partagé! –

Questions connexes