Spring:根据上下文(会话/ Web或本地线程/后台进程)注入bean

时间:2010-10-15 07:06:47

标签: spring session background

是否可以创建一个工厂或代理来决定线程是在(Web)请求还是后台进程(即调度程序)中运行,然后根据该信息创建会话bean或原型bean?

示例(伪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>

我希望这会让我的问题更容易理解......


我的解决方案

更新自己的问题永远不会迟到;)。我用两个不同的客户端会话实例,一个SessionScoped客户端会话和一个SingletonScoped会话解决了它。两者都是正常豆类。

<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>
然后,ClientSession将决定单例或会话范围:

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

可以通过以下方式收集会话类型:

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

所有类都实现了一个名为IClientSession的接口。 singletonScoped和sessionScoped bean都是从发现实现的BaseClientSession扩展而来。

然后,每个服务都可以使用客户端会话,即:

@Resource
private ClientSession clientSession;

    ...

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

现在,如果我们更进一步,我们可以为会话编写类似于模拟器的东西。这可以通过初始化单个会话的clientSession(在请求的上下文中)来完成。现在所有服务都可以使用相同的clientSession,我们仍然可以“模拟”用户,即:

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

还有一条建议:在SingletonScoped clientSession实例中注意线程化!哇,我以为我能用更少的线条做到这一点;)如果您想了解更多有关此方法的信息,请随时与我联系。

3 个答案:

答案 0 :(得分:3)

我创建了一个小的通用解决方法来注入bean取决于上下文。

猜猜我们有两个豆子:

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

我们希望将“userInfo”bean用于Web用户操作,将“userInfoSessionLess”bean用于后台服务。 Wa也想编写代码而不想考虑上下文,例如:

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

现在我们需要创建自定义会话范围以使其工作:

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

注册新范围:

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

现在我们需要修改这样的bean定义:

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

这就是全部。如果我们在实际的Web请求线程之外使用bean,那么名为“SessionLess”的Bean将用于所有“mySession”作用域bean。

答案 1 :(得分:2)

你的改述确实相当简单:)

您的currentUserInfoFactory可以使用RequestContextHolder.getRequestAttributes()。如果会话存在并与调用线程关联,那么这将返回一个非null对象,然后您可以安全地从上下文中检索会话范围的bean。如果它返回null,那么你应该获取原型范围的bean。

它不是很整洁,但它很简单,而且应该有用。

答案 2 :(得分:1)

创建两个自定义上下文加载器,将相同的范围定义绑定到不同的实现:

public final class SessionScopeContextLoader extends GenericXmlContextLoader {

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

然后你为单身人士制作一个相应的单身人士(用静态制作自己的范围)

然后,您只需在xml启动中为两个上下文中的每一个指定适当的上下文加载器。