Spring: внедрение компонента зависит от контекста (сеанс / веб или локальный поток / фоновый процесс) - PullRequest
7 голосов
/ 15 октября 2010

Можно ли создать фабрику или прокси-сервер, который может решить, выполняется ли поток в (веб-запросе) или в фоновом процессе (например, планировщик), а затем в зависимости от этой информации он создает сессионный компонент или компонент-прототип?

Пример (псевдо Spring 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>

Надеюсь, это облегчает понимание моего вопроса ...


Мое решение

Никогда не поздно обновить собственные вопросы;). Я решил это с помощью двух разных экземпляров сеанса клиента, одного сеанса клиента 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. Bean-компоненты singletonScoped и sessionScoped происходят от BaseClientSession, в котором обнаружена реализация.

Тогда каждая служба может использовать сеанс клиента, то есть:

@Resource
private ClientSession clientSession;

    ...

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

Теперь, если мы пойдем еще дальше, мы можем написать что-то вроде эмулятора для сессии. Это может быть сделано путем инициализации clientSession (который не находится в контексте запроса) одноэлементного сеанса. Теперь все сервисы могут использовать один и тот же clientSession, и мы все еще можем «эмулировать» пользователя, то есть:

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

Еще один совет: позаботьтесь о многопоточности в экземпляре ClientSession SingletonScoped! Вау, я думал, что смогу сделать это с меньшим количеством строк;) Если вы хотите узнать больше об этом подходе, не стесняйтесь связаться со мной.

Ответы [ 3 ]

3 голосов
/ 12 июня 2012

Я создал небольшой универсальный обходной путь для внедрения бинов, зависит от контекста.

Думаю, у нас есть два бина:

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

Мы хотим использовать бин "userInfo" для действий веб-пользователя иuserInfoSessionLess "bean для фоновых сервисов, например.Мы также хотим написать код и не хотим думать о контексте, например:

@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"/>

Вот и все.Бин с именем "SessionLess" будет использоваться для всех бинов "mySession", если мы используем бин вне реального потока веб-запросов.

2 голосов
/ 16 октября 2010

Ваша перефразировка действительно значительно проще:)

Ваш currentUserInfoFactory может использовать RequestContextHolder.getRequestAttributes(). Если сеанс присутствует и связан с вызывающим потоком, то он вернет ненулевой объект, и вы сможете безопасно извлечь сессионный компонент в контексте. Если он возвращает ноль, вам следует вместо этого получить bean-объект с прототипом.

Это не очень аккуратно, но просто и должно работать.

1 голос
/ 16 октября 2010

Создайте два пользовательских загрузчика контекста, которые связывают одно и то же определение области действия с различными реализациями:

public final class SessionScopeContextLoader extends GenericXmlContextLoader {

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

Затем вы делаете соответствующий для синглтона (создайте свою собственную область с помощью только статики)

Затем вы просто указываете соответствующий загрузчик контекста при запуске xml для каждого из двух контекстов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...